switched theme

This commit is contained in:
2026-04-02 13:14:18 +03:00
parent 0c26d89eae
commit e31dbbb16c
192 changed files with 215173 additions and 1463 deletions
+108
View File
@@ -0,0 +1,108 @@
(function () {
'use strict';
var body = document.getElementById('term-body');
if (!body) return;
var path = window.location.pathname;
var promptUser = body.dataset.prompt || 'user@pwnbox';
var hostname = body.dataset.hostname || 'pwnbox';
var whoami = body.dataset.whoami || 'l0st_r3ader';
var links = [
{ href: body.dataset.linkHome, label: '~/home' },
{ href: body.dataset.linkPosts, label: '~/posts' },
{ href: body.dataset.linkTags, label: '~/tags' },
];
var steps = [
{ type: 'cmd', text: 'cd ' + path },
{ type: 'out-error', text: 'bash: cd: ' + path + ': No such file or directory' },
{ type: 'cmd', text: 'whoami' },
{ type: 'out', text: whoami },
{ type: 'cmd', text: 'ls ~/recovery/' },
{ type: 'ls' },
];
var CHAR_DELAY = 28;
var LINE_DELAY = 180;
function ps1() {
var s = document.createElement('span');
s.className = 'term-ps1';
s.textContent = promptUser + '@' + hostname + ':~$ ';
return s;
}
function appendCmdLine(text, callback) {
var row = document.createElement('div');
row.className = 'term-row';
row.appendChild(ps1());
var input = document.createElement('span');
input.className = 'term-input';
row.appendChild(input);
body.appendChild(row);
var i = 0;
function tick() {
if (i < text.length) {
input.textContent += text[i++];
setTimeout(tick, CHAR_DELAY);
} else {
setTimeout(callback, LINE_DELAY);
}
}
tick();
}
function appendOut(text, extraClass, callback) {
var div = document.createElement('div');
div.className = 'term-out' + (extraClass ? ' ' + extraClass : '');
div.textContent = text;
body.appendChild(div);
setTimeout(callback, LINE_DELAY);
}
function appendLs(callback) {
var div = document.createElement('div');
div.className = 'term-out-ls';
links.forEach(function (l) {
var a = document.createElement('a');
a.href = l.href;
a.textContent = l.label;
div.appendChild(a);
});
body.appendChild(div);
setTimeout(callback, LINE_DELAY);
}
function appendCursor() {
var row = document.createElement('div');
row.className = 'term-row';
row.appendChild(ps1());
var cur = document.createElement('span');
cur.className = 'term-cursor';
cur.setAttribute('aria-hidden', 'true');
row.appendChild(cur);
body.appendChild(row);
}
function runSteps(i) {
if (i >= steps.length) {
appendCursor();
return;
}
var step = steps[i];
var next = function () {
runSteps(i + 1);
};
if (step.type === 'cmd') appendCmdLine(step.text, next);
else if (step.type === 'out-error') appendOut(step.text, 'term-out-error', next);
else if (step.type === 'out') appendOut(step.text, 'term-out-muted', next);
else if (step.type === 'ls') appendLs(next);
else next();
}
runSteps(0);
})();
-1
View File
@@ -1 +0,0 @@
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("katex")):"function"==typeof define&&define.amd?define(["katex"],t):"object"==typeof exports?exports.renderMathInElement=t(require("katex")):e.renderMathInElement=t(e.katex)}("undefined"!=typeof self?self:this,(function(e){return function(){"use strict";var t={757:function(t){t.exports=e}},n={};function r(e){var o=n[e];if(void 0!==o)return o.exports;var i=n[e]={exports:{}};return t[e](i,i.exports,r),i.exports}r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,{a:t}),t},r.d=function(e,t){for(var n in t)r.o(t,n)&&!r.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)};var o={};r.d(o,{default:function(){return p}});var i=r(757),a=r.n(i);const l=function(e,t,n){let r=n,o=0;const i=e.length;for(;r<t.length;){const n=t[r];if(o<=0&&t.slice(r,r+i)===e)return r;"\\"===n?r++:"{"===n?o++:"}"===n&&o--,r++}return-1},s=/^\\begin{/;var d=function(e,t){let n;const r=[],o=new RegExp("("+t.map((e=>e.left.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"))).join("|")+")");for(;n=e.search(o),-1!==n;){n>0&&(r.push({type:"text",data:e.slice(0,n)}),e=e.slice(n));const o=t.findIndex((t=>e.startsWith(t.left)));if(n=l(t[o].right,e,t[o].left.length),-1===n)break;const i=e.slice(0,n+t[o].right.length),a=s.test(i)?i:e.slice(t[o].left.length,n);r.push({type:"math",data:a,rawData:i,display:t[o].display}),e=e.slice(n+t[o].right.length)}return""!==e&&r.push({type:"text",data:e}),r};const c=function(e,t){const n=d(e,t.delimiters);if(1===n.length&&"text"===n[0].type)return null;const r=document.createDocumentFragment();for(let e=0;e<n.length;e++)if("text"===n[e].type)r.appendChild(document.createTextNode(n[e].data));else{const o=document.createElement("span");let i=n[e].data;t.displayMode=n[e].display;try{t.preProcess&&(i=t.preProcess(i)),a().render(i,o,t)}catch(o){if(!(o instanceof a().ParseError))throw o;t.errorCallback("KaTeX auto-render: Failed to parse `"+n[e].data+"` with ",o),r.appendChild(document.createTextNode(n[e].rawData));continue}r.appendChild(o)}return r},f=function(e,t){for(let n=0;n<e.childNodes.length;n++){const r=e.childNodes[n];if(3===r.nodeType){let o=r.textContent,i=r.nextSibling,a=0;for(;i&&i.nodeType===Node.TEXT_NODE;)o+=i.textContent,i=i.nextSibling,a++;const l=c(o,t);if(l){for(let e=0;e<a;e++)r.nextSibling.remove();n+=l.childNodes.length-1,e.replaceChild(l,r)}else n+=a}else if(1===r.nodeType){const e=" "+r.className+" ";-1===t.ignoredTags.indexOf(r.nodeName.toLowerCase())&&t.ignoredClasses.every((t=>-1===e.indexOf(" "+t+" ")))&&f(r,t)}}};var p=function(e,t){if(!e)throw new Error("No element provided to render");const n={};for(const e in t)t.hasOwnProperty(e)&&(n[e]=t[e]);n.delimiters=n.delimiters||[{left:"$$",right:"$$",display:!0},{left:"\\(",right:"\\)",display:!1},{left:"\\begin{equation}",right:"\\end{equation}",display:!0},{left:"\\begin{align}",right:"\\end{align}",display:!0},{left:"\\begin{alignat}",right:"\\end{alignat}",display:!0},{left:"\\begin{gather}",right:"\\end{gather}",display:!0},{left:"\\begin{CD}",right:"\\end{CD}",display:!0},{left:"\\[",right:"\\]",display:!0}],n.ignoredTags=n.ignoredTags||["script","noscript","style","textarea","pre","code","option"],n.ignoredClasses=n.ignoredClasses||[],n.errorCallback=n.errorCallback||console.error,n.macros=n.macros||{},f(e,n)};return o=o.default}()}));
File diff suppressed because it is too large Load Diff
+68
View File
@@ -0,0 +1,68 @@
document.addEventListener('DOMContentLoaded', () => {
// Select all <pre> elements that contain code blocks
const preBlocks = document.querySelectorAll('pre');
const copyIcon =
'<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="8" height="4" x="8" y="2" rx="1" ry="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/></svg>';
const checkIcon =
'<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>';
preBlocks.forEach((pre) => {
// Create the copy button
const button = document.createElement('button');
button.innerHTML = copyIcon;
button.className = 'copy-code-btn';
button.setAttribute('aria-label', 'Copy code');
button.setAttribute('title', 'Copy code');
// Add the button to the <pre> block
// Assuming pre is position relative in CSS
pre.appendChild(button);
button.addEventListener('click', async () => {
// Find the <code> element within the <pre>
const code = pre.querySelector('code');
if (!code) return;
// Get the text content, removing any leading/trailing whitespace
const text = code.innerText;
try {
if (navigator.clipboard && window.isSecureContext) {
// Modern Async Clipboard API
await navigator.clipboard.writeText(text);
} else {
// Fallback for insecure contexts (like local network IPs via HTTP)
const textArea = document.createElement('textarea');
textArea.value = text;
// Move outside the screen to make it invisible
textArea.style.position = 'absolute';
textArea.style.left = '-999999px';
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
textArea.remove();
}
// Add visual feedback
button.innerHTML = checkIcon;
button.classList.add('copied');
button.setAttribute('aria-label', 'Copied');
button.setAttribute('title', 'Copied!');
// Revert back after 2 seconds
setTimeout(() => {
button.innerHTML = copyIcon;
button.classList.remove('copied');
button.setAttribute('aria-label', 'Copy code');
button.setAttribute('title', 'Copy code');
}, 2000);
} catch (err) {
console.error('Failed to copy code: ', err);
}
});
});
});
+10
View File
File diff suppressed because one or more lines are too long
+32
View File
@@ -0,0 +1,32 @@
(function () {
'use strict';
const refs = document.querySelectorAll('sup.footnote-reference');
if (!refs.length) return;
refs.forEach(function (sup) {
const a = sup.querySelector('a');
if (!a) return;
const targetId = a.getAttribute('href').replace(/^#/, '');
const refId = 'fnref-' + targetId;
sup.id = refId;
const def = document.getElementById(targetId);
if (!def) return;
// Append ↩ to the last <p> inside the definition
const paras = def.querySelectorAll('p');
const lastPara = paras[paras.length - 1];
if (!lastPara) return;
const backlink = document.createElement('a');
backlink.href = '#' + refId;
backlink.className = 'footnote-backref';
backlink.setAttribute('aria-label', 'Back to reference');
backlink.textContent = '↩';
lastPara.appendChild(document.createTextNode('\u00a0'));
lastPara.appendChild(backlink);
});
})();
+13
View File
@@ -0,0 +1,13 @@
(function () {
'use strict';
document.querySelectorAll('.post-body h2[id], .post-body h3[id]').forEach(function (heading) {
var a = document.createElement('a');
a.href = '#' + heading.id;
a.className = 'heading-anchor';
a.setAttribute('aria-label', 'Link to this section');
a.setAttribute('tabindex', '-1');
a.textContent = '#';
heading.appendChild(a);
});
})();
-1
View File
File diff suppressed because one or more lines are too long
+170
View File
@@ -0,0 +1,170 @@
!(function (e, t) {
'object' == typeof exports && 'object' == typeof module
? (module.exports = t(require('katex')))
: 'function' == typeof define && define.amd
? define(['katex'], t)
: 'object' == typeof exports
? (exports.renderMathInElement = t(require('katex')))
: (e.renderMathInElement = t(e.katex));
})('undefined' != typeof self ? self : this, function (e) {
return (function () {
'use strict';
var t = {
771: function (t) {
t.exports = e;
},
},
r = {};
function n(e) {
var i = r[e];
if (void 0 !== i) return i.exports;
var a = (r[e] = { exports: {} });
return (t[e](a, a.exports, n), a.exports);
}
((n.n = function (e) {
var t =
e && e.__esModule
? function () {
return e.default;
}
: function () {
return e;
};
return (n.d(t, { a: t }), t);
}),
(n.d = function (e, t) {
for (var r in t)
n.o(t, r) && !n.o(e, r) && Object.defineProperty(e, r, { enumerable: !0, get: t[r] });
}),
(n.o = function (e, t) {
return Object.prototype.hasOwnProperty.call(e, t);
}));
var i = {};
return (
(function () {
n.d(i, {
default: function () {
return s;
},
});
var e = n(771),
t = n.n(e),
r = function (e, t, r) {
for (var n = r, i = 0, a = e.length; n < t.length; ) {
var o = t[n];
if (i <= 0 && t.slice(n, n + a) === e) return n;
('\\' === o ? n++ : '{' === o ? i++ : '}' === o && i--, n++);
}
return -1;
},
a = /^\\begin{/,
o = function (e, t) {
for (
var n,
i = [],
o = new RegExp(
'(' +
t
.map(function (e) {
return e.left.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
})
.join('|') +
')',
);
-1 !== (n = e.search(o));
) {
n > 0 && (i.push({ type: 'text', data: e.slice(0, n) }), (e = e.slice(n)));
var l = t.findIndex(function (t) {
return e.startsWith(t.left);
});
if (-1 === (n = r(t[l].right, e, t[l].left.length))) break;
var d = e.slice(0, n + t[l].right.length),
s = a.test(d) ? d : e.slice(t[l].left.length, n);
(i.push({ type: 'math', data: s, rawData: d, display: t[l].display }),
(e = e.slice(n + t[l].right.length)));
}
return ('' !== e && i.push({ type: 'text', data: e }), i);
},
l = function (e, r) {
var n = o(e, r.delimiters);
if (1 === n.length && 'text' === n[0].type) return null;
for (var i = document.createDocumentFragment(), a = 0; a < n.length; a++)
if ('text' === n[a].type) i.appendChild(document.createTextNode(n[a].data));
else {
var l = document.createElement('span'),
d = n[a].data;
r.displayMode = n[a].display;
try {
(r.preProcess && (d = r.preProcess(d)), t().render(d, l, r));
} catch (e) {
if (!(e instanceof t().ParseError)) throw e;
(r.errorCallback(
'KaTeX auto-render: Failed to parse `' + n[a].data + '` with ',
e,
),
i.appendChild(document.createTextNode(n[a].rawData)));
continue;
}
i.appendChild(l);
}
return i;
},
d = function e(t, r) {
for (var n = 0; n < t.childNodes.length; n++) {
var i = t.childNodes[n];
if (3 === i.nodeType) {
for (
var a = i.textContent, o = i.nextSibling, d = 0;
o && o.nodeType === Node.TEXT_NODE;
)
((a += o.textContent), (o = o.nextSibling), d++);
var s = l(a, r);
if (s) {
for (var f = 0; f < d; f++) i.nextSibling.remove();
((n += s.childNodes.length - 1), t.replaceChild(s, i));
} else n += d;
} else
1 === i.nodeType &&
(function () {
var t = ' ' + i.className + ' ';
-1 === r.ignoredTags.indexOf(i.nodeName.toLowerCase()) &&
r.ignoredClasses.every(function (e) {
return -1 === t.indexOf(' ' + e + ' ');
}) &&
e(i, r);
})();
}
},
s = function (e, t) {
if (!e) throw new Error('No element provided to render');
var r = {};
for (var n in t) t.hasOwnProperty(n) && (r[n] = t[n]);
((r.delimiters = r.delimiters || [
{ left: '$$', right: '$$', display: !0 },
{ left: '\\(', right: '\\)', display: !1 },
{ left: '\\begin{equation}', right: '\\end{equation}', display: !0 },
{ left: '\\begin{align}', right: '\\end{align}', display: !0 },
{ left: '\\begin{alignat}', right: '\\end{alignat}', display: !0 },
{ left: '\\begin{gather}', right: '\\end{gather}', display: !0 },
{ left: '\\begin{CD}', right: '\\end{CD}', display: !0 },
{ left: '\\[', right: '\\]', display: !0 },
]),
(r.ignoredTags = r.ignoredTags || [
'script',
'noscript',
'style',
'textarea',
'pre',
'code',
'option',
]),
(r.ignoredClasses = r.ignoredClasses || []),
(r.errorCallback = r.errorCallback || console.error),
(r.macros = r.macros || {}),
d(e, r));
};
})(),
(i = i.default)
);
})();
});
+11562
View File
File diff suppressed because it is too large Load Diff
+192132
View File
File diff suppressed because one or more lines are too long
+34
View File
@@ -0,0 +1,34 @@
document.addEventListener('DOMContentLoaded', () => {
const progressBar = document.getElementById('reading-progress-bar');
if (!progressBar) return;
const updateProgress = () => {
// Calculate how far the user has scrolled down the page
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
// Calculate the total scrollable height
const scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
const clientHeight = document.documentElement.clientHeight || window.innerHeight;
const windowHeight = scrollHeight - clientHeight;
// Calculate percentage and update width
const scrollPercentage = windowHeight > 0 ? (scrollTop / windowHeight) * 100 : 0;
progressBar.style.width = scrollPercentage + '%';
};
// Update initially in case the user refreshes midway down the page
updateProgress();
// Throttle the scroll event listener a tiny bit for performance
let isTicking = false;
window.addEventListener('scroll', () => {
if (!isTicking) {
window.requestAnimationFrame(() => {
updateProgress();
isTicking = false;
});
isTicking = true;
}
});
});
+34
View File
@@ -0,0 +1,34 @@
document.addEventListener('DOMContentLoaded', () => {
const scrollToTopBtn = document.getElementById('scroll-to-top');
if (!scrollToTopBtn) return;
// Show button when scrolled down 400px
const toggleVisibility = () => {
if (window.scrollY > 400) {
scrollToTopBtn.classList.add('visible');
} else {
scrollToTopBtn.classList.remove('visible');
}
};
// Smooth scroll to top when clicked
scrollToTopBtn.addEventListener('click', () => {
window.scrollTo({
top: 0,
behavior: 'smooth',
});
});
// Throttle the scroll event listener a tiny bit for performance
let isTicking = false;
window.addEventListener('scroll', () => {
if (!isTicking) {
window.requestAnimationFrame(() => {
toggleVisibility();
isTicking = false;
});
isTicking = true;
}
});
});
+223
View File
@@ -0,0 +1,223 @@
document.addEventListener('DOMContentLoaded', () => {
const searchToggleBtn = document.querySelector('.search-toggle');
if (!searchToggleBtn) return;
// Check if elasticlunr is available
if (typeof elasticlunr === 'undefined') {
console.warn('Elasticlunr not loaded. Search will not work.');
return;
}
// Modal elements
const overlay = document.getElementById('search-overlay');
const input = document.getElementById('search-input');
const resultsContainer = document.getElementById('search-results');
if (!overlay || !input || !resultsContainer) return;
let index = null;
let isIndexLoading = false;
let selectedIndex = -1;
let resultItems = [];
// Initialize search index (loaded lazily on first open)
const initSearch = async () => {
if (index || isIndexLoading) return;
isIndexLoading = true;
try {
// Fetch the Zola-generated index
const response = await fetch('/search_index.en.js');
const jsText = await response.text();
// The file defines `window.searchIndex = {...}`
// We use a safe evaluation approach if it's purely assignment
// Or just append it as a script tag
const script = document.createElement('script');
script.innerHTML = jsText;
document.body.appendChild(script);
// Wait for it to parse
setTimeout(() => {
if (window.searchIndex) {
index = elasticlunr.Index.load(window.searchIndex);
// Search immediately if there's text
if (input.value.trim().length > 0) {
performSearch();
}
}
}, 50);
} catch (e) {
console.error('Failed to load search index:', e);
}
};
// Open Modal
const openSearch = () => {
overlay.classList.add('active');
// Slight delay ensures the browser focuses the input after the display transition starts
setTimeout(() => {
input.focus();
}, 50);
initSearch();
document.body.style.overflow = 'hidden'; // Prevent background scrolling
};
// Close Modal
const closeSearch = () => {
overlay.classList.remove('active');
input.value = '';
resultsContainer.innerHTML = '';
document.body.style.overflow = '';
selectedIndex = -1;
resultItems = [];
};
// Bind Toggle Button
searchToggleBtn.addEventListener('click', (e) => {
e.preventDefault();
if (overlay.classList.contains('active')) {
closeSearch();
} else {
openSearch();
}
});
// Close on overlay click
overlay.addEventListener('click', (e) => {
if (e.target === overlay) closeSearch();
});
// Handle Keyboard Shortcuts
document.addEventListener('keydown', (e) => {
// Ctrl+K -> Open Search
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
e.preventDefault();
openSearch();
}
if (!overlay.classList.contains('active')) return;
// Escape -> Close Search
if (e.key === 'Escape') {
closeSearch();
}
// Arrow Navigation
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
e.preventDefault();
if (resultItems.length === 0) return;
if (e.key === 'ArrowDown') {
selectedIndex = (selectedIndex + 1) % resultItems.length;
} else {
selectedIndex = (selectedIndex - 1 + resultItems.length) % resultItems.length;
}
updateSelection();
}
// Enter -> Navigate to selected
if (e.key === 'Enter') {
e.preventDefault();
if (selectedIndex >= 0 && resultItems[selectedIndex]) {
window.location.href = resultItems[selectedIndex].href;
} else if (resultItems.length > 0) {
// Default to first item if none selected
window.location.href = resultItems[0].href;
}
}
});
const updateSelection = () => {
resultItems.forEach((el, i) => {
if (i === selectedIndex) {
el.classList.add('selected');
el.scrollIntoView({ block: 'nearest' });
} else {
el.classList.remove('selected');
}
});
};
// Perform the Search
const performSearch = () => {
if (!index) {
resultsContainer.innerHTML = '<div class="no-results">Loading search index...</div>';
return;
}
const query = input.value.trim();
if (query.length === 0) {
resultsContainer.innerHTML = '';
selectedIndex = -1;
resultItems = [];
return;
}
const options = {
bool: 'OR',
expand: true,
};
const results = index.search(query, options);
if (results.length === 0) {
resultsContainer.innerHTML = `<div class="no-results">No results found for "${query}"</div>`;
selectedIndex = -1;
resultItems = [];
return;
}
// Build HTML
let html = '';
results.forEach((res) => {
// The doc is stored inside the index.documentStore
// For Zola + elasticlunr, res.ref IS the URL.
const url = res.ref;
const doc = index.documentStore.docs[res.ref];
if (!doc) return;
// Truncate summary if needed
let body = doc.body || '';
if (body.length > 150) {
body = body.substring(0, 150) + '...';
}
const title = doc.title || url;
html += `
<a href="${url}" class="search-result-item">
<div class="result-title">${title}</div>
<div class="result-summary">${body}</div>
</a>
`;
});
resultsContainer.innerHTML = html;
// Cache DOM elements for keyboard navigation
resultItems = Array.from(resultsContainer.querySelectorAll('.search-result-item'));
selectedIndex = -1; // Reset selection on new search
// Mouse hover updates selection index
resultItems.forEach((item, i) => {
item.addEventListener('mouseenter', () => {
selectedIndex = i;
updateSelection();
});
});
};
// Debounce the input
let debounceTimer;
input.addEventListener('input', () => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => {
performSearch();
}, 150);
});
});
+63
View File
@@ -0,0 +1,63 @@
document.addEventListener('DOMContentLoaded', () => {
const themeToggleBtn = document.getElementById('theme-toggle');
const htmlElement = document.documentElement;
const applyTheme = (theme) => {
if (theme === 'dark') {
htmlElement.classList.add('dark');
} else {
htmlElement.classList.remove('dark');
}
localStorage.setItem('theme', theme);
};
// Check local storage for theme preference
const currentTheme = localStorage.getItem('theme');
if (currentTheme === 'dark' || currentTheme === 'light') {
applyTheme(currentTheme);
} else {
// Fallback to system preference
const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)');
applyTheme(prefersDarkScheme.matches ? 'dark' : 'light');
}
// Toggle theme on click
if (themeToggleBtn) {
themeToggleBtn.addEventListener('click', () => {
const nextTheme = htmlElement.classList.contains('dark') ? 'light' : 'dark';
applyTheme(nextTheme);
});
}
// Mobile nav toggle
const navToggle = document.getElementById('nav-toggle');
const navLinks = document.getElementById('nav-links');
if (navToggle && navLinks) {
navToggle.addEventListener('click', () => {
const isOpen = navLinks.classList.toggle('is-open');
navToggle.setAttribute('aria-expanded', isOpen);
});
// Close nav when a link is clicked
navLinks.querySelectorAll('.nav-item').forEach((link) => {
link.addEventListener('click', () => {
navLinks.classList.remove('is-open');
navToggle.setAttribute('aria-expanded', 'false');
});
});
}
// Sync cookieconsent dark mode reactively with the site theme
const syncCCTheme = () => {
const ccMain = document.getElementById('cc-main');
if (!ccMain) return;
ccMain.classList.toggle('cc--darkmode', htmlElement.classList.contains('dark'));
};
new MutationObserver(syncCCTheme).observe(htmlElement, {
attributes: true,
attributeFilter: ['class'],
});
// Initial sync in case banner is already mounted
setTimeout(syncCCTheme, 100);
});
+59
View File
@@ -0,0 +1,59 @@
(function () {
'use strict';
const sidebar = document.querySelector('.post-toc-sidebar');
if (!sidebar) return;
const links = Array.from(sidebar.querySelectorAll('a[data-toc-id]'));
if (!links.length) return;
const headingIds = links.map((a) => a.dataset.tocId);
const headings = headingIds.map((id) => document.getElementById(id)).filter(Boolean);
let activeId = null;
function setActive(id) {
if (id === activeId) return;
activeId = id;
links.forEach((a) => {
if (a.dataset.tocId === id) {
a.classList.add('toc-active');
} else {
a.classList.remove('toc-active');
}
});
}
const observer = new IntersectionObserver(
(entries) => {
// Find the topmost visible heading
const visible = headings.filter((h) => {
const rect = h.getBoundingClientRect();
return rect.top >= 0 && rect.top < window.innerHeight * 0.6;
});
if (visible.length > 0) {
setActive(visible[0].id);
} else {
// If scrolled past all headings, keep last active
const above = headings.filter((h) => h.getBoundingClientRect().top < 0);
if (above.length > 0) {
setActive(above[above.length - 1].id);
}
}
},
{
rootMargin: '-10% 0px -60% 0px',
threshold: 0,
},
);
headings.forEach((h) => observer.observe(h));
// Set initial active on load
const firstVisible = headings.find((h) => {
const rect = h.getBoundingClientRect();
return rect.top >= 0;
});
if (firstVisible) setActive(firstVisible.id);
})();