TODO: Fix default pages.
TODO: comment variables
TODO: better default values
TODO: change to websafe fonts default
Don't forget your robots.txt https://www.ditig.com/robots-txt-template
https://motherfuckingwebsite.com/
http://bettermotherfuckingwebsite.com/
https://evenbettermotherfucking.website/
https://bestmotherfucking.website/
https://thebestmotherfucking.website/
https://perfectmotherfuckingwebsite.com/
https://ultimatemotherfuckingwebsite.com/
https://actualwebsite.org/
make the site better, the code easier, the Documentation more idiot proof.
Do not create new features. Maybe create companion tools instead.
I have only a rough understanding of what's going on. WebDev is the proof that Satan, or an all powerfull maniac exists. So... I don't know if I want to understand. Here is some guidance.
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>This loads marked.js, the only external dependency. It gives us the marked.parse() function later, which converts a raw Markdown string into HTML. It's loaded in the <head> (not at the bottom of <body>) so it's guaranteed to be available before any JS runs.
:root {
--bg: #0f1117;
--sidebar-w: 260px;
--header-h: 52px;
...
}All colours, font names, and layout dimensions are defined here as CSS custom properties (a.k.a. variables). Everywhere else in the stylesheet uses var(--name) instead of hardcoded values. This means you can retheme the entire site by changing just this one block — nothing else needs touching.
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }Two things happening here. box-sizing: border-box means that when you set width: 260px on something, padding and borders are included in that 260px — the default browser behaviour (content-box) would make it wider. The margin/padding reset removes the browser's built-in spacing so you start from a clean slate.
This is the core structural trick. The three panels — nav, main, aside — each have their own positioning strategy:
Header: position: fixed pins it to the top of the viewport regardless of scroll. z-index: 100 ensures it sits on top of everything.
Nav and Aside: Also position: fixed, each anchored to their respective sides. Their top is var(--header-h) so they start just below the header, and their height is calc(100vh - var(--header-h)) so they fill the remaining vertical space. They scroll independently via overflow-y: auto.
Main: This is the only normally-flowing element. It uses margin-left: var(--sidebar-w) and margin-right: var(--toc-w) to push its content into the gap between the two fixed sidebars. It doesn't need to know about them structurally — the margins just carve out the space.
All the heading, paragraph, code, table, and blockquote rules are scoped with the #content prefix, e.g. #content h2 { ... }. This is intentional — it means these styles only apply to the rendered Markdown area and won't accidentally affect the nav links or ToC, even though they also contain <a> tags and text.
One subtle detail on #content pre code:
#content pre { background: var(--code-bg); border: 1px solid var(--border); ... }
#content pre code { background: none; border: none; padding: 0; }marked.js wraps fenced code blocks in both a <pre> and a <code> tag. If you style both, you get double borders and double backgrounds. So the pre gets the visual treatment, and the code inside it is explicitly stripped back to nothing.
.toc-link[data-level="2"] { padding-left: 0.5rem; }
.toc-link[data-level="3"] { padding-left: 1.2rem; }
.toc-link[data-level="4"] { padding-left: 1.9rem; }This is an attribute selector. In the JS, each ToC link has a data-level attribute set to 2, 3, or 4. CSS picks this up and applies increasing left-padding accordingly, creating the visual nesting hierarchy without any wrapper elements or JS-applied classes.
<header>...</header>
<div class="shell">
<nav id="nav">...</nav>
<main><div id="content"></div></main>
<aside id="toc-panel"><div id="toc"></div></aside>
</div>The HTML is intentionally thin. The <nav> starts with just the "Pages" label; the <div id="content"> starts with "Loading…"; the <div id="toc"> is empty. JavaScript fills all three at runtime. This means the HTML itself is just a structural shell — all meaningful content is injected dynamically.
let pages = [];
let current = null;
let tocObserver = null;Three module-level variables shared across all functions. pages holds the parsed pages.json array. current tracks which file is loaded so navigate() can bail out early if you click the page you're already on. tocObserver holds the IntersectionObserver instance so it can be disconnected and replaced when you navigate to a new page.
This runs once on page load. It fetches pages.json, stores the result in pages, then calls buildNav() to populate the sidebar. After that it handles routing:
const hash = decodeURIComponent(location.hash.slice(1));
const target = pages.find(p => p.file === hash) || pages[0];location.hash is the #getting-started.md part of the URL. slice(1) strips the #. decodeURIComponent handles any %20-style encoding. If the hash matches a known page, that page loads; otherwise it falls back to the first page in the list.
The hashchange listener handles the back/forward buttons — whenever the URL hash changes (because the user pressed back), this fires and loads the correct page.
Loops over pages, creates an <a> element per entry, and appends them to the nav. Two things worth noting:
a.href = `#${encodeURIComponent(p.file)}`;
a.dataset.file = p.file;The href is set so that right-click → copy link works and the URL bar updates on click. The data-file attribute stores the raw filename for setActiveNav() to compare against. The click handler calls e.preventDefault() to stop the browser's default hash-jump behaviour and hands off to navigate() instead.
async function navigate(file, pushHash = true) {
if (file === current) return;
current = file;
...
if (pushHash) history.pushState(null, '', `#${encodeURIComponent(file)}`);The pushHash = true default parameter means most calls push a new history entry (so back/forward works). The one place it passes false is during init(), when the page is first loading from an existing URL — you don't want to push a duplicate entry onto the history stack in that case.
After updating state and the URL, it shows "Loading…", clears the ToC, then fetches the .md file with the Fetch API. If the fetch fails (404, network error), it shows an error message and returns early. Otherwise it passes the raw Markdown text to renderPage().
content.innerHTML = marked.parse(md);
content.querySelectorAll('h1,h2,h3,h4').forEach(h => {
if (!h.id) h.id = slugify(h.textContent);
});marked.parse(md) is the single call that converts the Markdown string to an HTML string, which is then written into the DOM. marked.js does not automatically add id attributes to headings, so the loop does that manually using slugify(). The if (!h.id) guard means you can manually add an id in your Markdown HTML if you want a specific anchor name.
buildToc() reads all headings from the just-rendered content, extracts their level (the 1 from H1, etc.) and ID, and creates a link for each. The data-level and data-target attributes are written here — level drives the CSS indentation, target is what the observer uses to highlight the right link.
setupTocObserver() is where the scroll-tracking happens. IntersectionObserver fires a callback whenever a watched element enters or leaves the viewport. The rootMargin is the key knob:
{ rootMargin: '-10% 0px -80% 0px', threshold: 0 }This shrinks the effective detection zone: 10% off the top, 80% off the bottom. The result is a narrow band near the upper portion of the screen. A heading only triggers the callback when it crosses into that band — which corresponds to "this heading is near the top of what I'm reading." When one fires, its corresponding ToC link gets the active class and the rest are removed.
The if (tocObserver) tocObserver.disconnect() at the top is critical — without it, navigating to a new page would stack observers on top of each other, and old headings from the previous page (now removed from the DOM) would keep generating callbacks.
return text.toLowerCase()
.replace(/[^\w\s-]/g, '')
.trim()
.replace(/[\s_]+/g, '-');Converts a heading like "Core Concepts!" into "core-concepts" for use as an HTML id. The first regex strips anything that's not a word character, space, or hyphen. The second replaces spaces and underscores with hyphens. This is what makes #core-concepts work as a clickable anchor.