Where JS lives
| File | Loaded | Purpose |
|---|---|---|
Inline in baseof.html <script> | every page | Global functions (theme, menu) + restore from localStorage |
static/js/site.js | not loaded anywhere | Duplicates functions from baseof (legacy) |
Inline in _default/single.html {{ block "scripts" }} | article pages | Progress bar + ToC + copy buttons |
Inline in posts/list.html {{ block "scripts" }} | /posts/ | Pagefind search |
Inline in taxonomy/tag.html {{ block "scripts" }} | /tags/ | Pagefind search + tag filter |
Inline in certs/single.html {{ block "scripts" }} | /certs/* | Accordion toggle |
static/js/ns.js | /posts/linux-namespaces/ | Namespace explorer (data + render) |
baseof.html โ global functions
toggleTheme()
function toggleTheme()
Called by: onclick on #themeBtn (desktop) and #themeBtnMob (mobile).
What it does:
- Reads the current
data-themeattribute from<html> - Switches to the opposite (
darkโlight) - Saves to
localStorage.theme - Updates the icon on all
#themeBtn,#themeBtnMobbuttons
CSS link: document.documentElement.setAttribute('data-theme', next) triggers CSS variables [data-theme="light"].
setLang(lang, btn)
function setLang(lang, btn)
// Example call: setLang('ru', this)
Called by: onclick on .lang-btn buttons in desktop nav and mobile drawer.
What it does:
- Saves
langtolocalStorage.lang - Removes
.activefrom all.lang-btn - Adds
.activeto the button whose text equalslang.toUpperCase() - If the page is bilingual (has
<meta id="page-lang">) and the selected language differs from the current one โ redirects to thedata-lang-pairURL
Note: on non-bilingual pages the function only toggles the visual state of the buttons. No content translation โ the
langvariable is reserved for future use.
toggleMobMenu()
function toggleMobMenu()
Called by: onclick on #burgerBtn.
What it does: toggles .open class on three elements simultaneously:
#mobDrawerโ slides out the drawer#mobOverlayโ shows the backdrop#burgerBtnโ animates burger into an X
closeMobMenu()
function closeMobMenu()
Called by: onclick on #mobOverlay (clicking the overlay closes the menu).
What it does: removes .open from #mobDrawer, #mobOverlay, #burgerBtn.
IIFE โ restore state
(function () {
const t = localStorage.getItem('theme');
if (t === 'light') { ... }
const lang = localStorage.getItem('lang') || 'en';
if (lang === 'ru') { ... }
})();
Executes: immediately on page load (Immediately Invoked Function Expression).
What it does:
- If
localStorage.theme === 'light'โ applies light theme and sets โ๏ธ - If
localStorage.lang === 'ru'โ sets.activeon the RU button
Why IIFE and not plain code? Isolates variables (
t,lang) from the global scope.
_default/single.html โ inline scripts
Reading progress bar
const bar = document.createElement('div');
bar.style.cssText = 'position:fixed;top:0;left:0;height:2px;...';
document.body.appendChild(bar);
window.addEventListener('scroll', () => {
const h = document.body.scrollHeight - window.innerHeight;
bar.style.width = (h > 0 ? Math.round(window.scrollY / h * 100) : 0) + '%';
});
Creates a reading progress bar. Width = scrollY / (scrollHeight - viewportHeight) * 100%.
ToC builder
const heads = document.querySelectorAll('#articleBody h2, #articleBody h3');
if (heads.length > 2) { ... }
Condition: only if there are more than 2 headings.
Algorithm:
- Generates an HTML link list from
h2/h3(usingidattribute andtextContent) - Inserts into
#tocAside - If
window.innerWidth >= 860โ adds.has-tocto.page(two-column grid) resizelistener โ removes/adds.has-tocon width changeIntersectionObserverwithrootMargin: '-10% 0px -80% 0px'โ highlights the active heading
Why rootMargin: '-80% at the bottom? A heading is considered active when it’s in the top 20% of the viewport โ feels more natural while scrolling.
Copy buttons for raw pre blocks
document.querySelectorAll('pre').forEach(pre => {
if (pre.closest('.code-block')) return; // skip shortcode blocks
// wraps pre in .code-block and adds a copy button
});
function cpPre(btn) {
navigator.clipboard.writeText(pre.innerText)
.then(() => { btn.textContent = 'ok!'; ... });
}
Applied to plain ```bash ``` markdown blocks. The {{< code >}} shortcode handles copying itself via cpCode().
posts/list.html โ Pagefind search
let pf = null;
async function loadPagefind() {
if (pf) return;
pf = await import('/pagefind/pagefind.js');
await pf.init();
}
searchInput.addEventListener('input', async function() { ... });
Lazy loading: pagefind.js loads only on first input (not when the page opens). Saves bandwidth.
Flow:
- User types โ
inputevent loadPagefind()โ if not loaded yet, doesimport()pf.search(q)โ returns resultsawait Promise.all(results.map(r => r.data()))โ asynchronously fetches each result’s data- Renders into
#searchResults(absolute div below input)
Closing: document.addEventListener('click') โ a click outside .search-wrap hides results.
taxonomy/tag.html โ tag filter
const POSTS = [ /* embedded by Hugo template */ ];
let activeTag = '';
function renderArticles() {
const filtered = activeTag
? POSTS.filter(p => p.tags.includes(activeTag))
: POSTS;
// innerHTML render
}
document.querySelectorAll('.tag-filter').forEach(btn => {
btn.addEventListener('click', () => {
activeTag = btn.dataset.tag;
renderArticles();
});
});
Data from Hugo: the POSTS array is generated at build time. Each post contains:
tagsโ array of urlized names (for filtering)tagLabelsโ array of display names (for rendering)
btn.dataset.tag โ read from the data-tag="" attribute on the button. The “All” button has data-tag="" โ activeTag = '' โ all posts are shown.
certs/single.html โ accordion
function toggleTopic(btn) {
const topic = btn.closest('.cert-topic');
const body = topic.querySelector('.cert-topic-body');
if (!body) return; // topics without posts don't expand
const open = topic.classList.toggle('open');
body.style.maxHeight = open ? body.scrollHeight + 'px' : '0';
}
Animation via max-height: CSS animation max-height: 0 โ scrollHeight. scrollHeight = actual content height.
Why not height: auto? CSS cannot animate auto. max-height is the standard workaround.
ns.js โ Namespace Explorer
Path: static/js/ns.js
Loaded: only on /posts/linux-namespaces/ via <script src="{{ "js/ns.js" | relURL }}">
Contains:
nsDataโ array of objects with data for all namespaces (name, flag, icon, color, summary, desc…)cheatDataโ array of commands for the cheatsheet table- Render functions:
buildNsGrid(),buildNsMap(),buildCheatTable() toggleCard(el)โ expands/collapses a namespace cardIntersectionObserverโ progress bar (how many namespaces have been read)buildToc()โ builds the table of contents in aside