diff --git a/pygitweb/static/main.css b/pygitweb/static/main.css
index 2df553f..f385f0b 100644
--- a/pygitweb/static/main.css
+++ b/pygitweb/static/main.css
@@ -170,3 +170,77 @@ tbody tr:hover {
 .blob-view .blob-selection {
 	background-color: var(--pgw-blob-selection-bg, rgba(193, 173, 0, 0.35));
 }
+
+/* README markdown: linkable section headings + table of contents */
+.markdown-body .readme-heading-wrap {
+	position: relative;
+	display: block;
+}
+.markdown-body .readme-section-anchor {
+	position: absolute;
+	right: 100%;
+	padding-right: 0.35em;
+	opacity: 0;
+	color: var(--pgw-text-muted);
+	font-size: 1.3rem;
+	text-decoration: none;
+	transition: opacity 0.15s ease;
+}
+.markdown-body .readme-heading-wrap:hover .readme-section-anchor,
+.markdown-body .readme-section-anchor:hover {
+	opacity: 1;
+}
+.markdown-body .readme-section-anchor:focus {
+	opacity: 1;
+}
+
+.readme-toc-mount {
+	position: fixed;
+	left: var(--tblr-navbar-vertical-width, 15rem);
+	top: 0;
+	bottom: 0;
+	width: 12rem;
+	z-index: 1040;
+	pointer-events: none;
+	overflow: hidden;
+}
+.readme-toc-mount .readme-toc {
+	pointer-events: auto;
+	padding: 1rem 0.5rem 1rem 0;
+	max-height: 100%;
+	overflow-y: auto;
+}
+.readme-toc-list {
+	list-style: none;
+	margin: 0;
+	padding: 0;
+	font-size: 0.8rem;
+}
+.readme-toc-item {
+	margin: 0.2em 0;
+}
+.readme-toc-link {
+	display: block;
+	padding: 0.2em 0.4em;
+	color: var(--pgw-text-muted);
+	text-decoration: none;
+	border-radius: 4px;
+	white-space: nowrap;
+	overflow: hidden;
+	text-overflow: ellipsis;
+}
+.readme-toc-link:hover {
+	color: var(--pgw-link-hover);
+	background-color: var(--pgw-bg-raised, rgba(128, 128, 128, 0.1));
+}
+.readme-toc-item.readme-toc-active .readme-toc-link {
+	color: var(--pgw-link);
+	font-weight: 600;
+	background-color: var(--pgw-accent-bg, rgba(128, 128, 128, 0.15));
+}
+.readme-toc-level-1 { padding-left: 0; }
+.readme-toc-level-2 { padding-left: 0.5rem; }
+.readme-toc-level-3 { padding-left: 1rem; }
+.readme-toc-level-4 { padding-left: 1.5rem; }
+.readme-toc-level-5 { padding-left: 2rem; }
+.readme-toc-level-6 { padding-left: 2.5rem; }
diff --git a/pygitweb/static/readme-render.js b/pygitweb/static/readme-render.js
index d466bb1..9d87401 100644
--- a/pygitweb/static/readme-render.js
+++ b/pygitweb/static/readme-render.js
@@ -20,6 +20,124 @@
       }
     }
   }
+  function slugifyId(id) {
+    if (!id) return "";
+    return id.replace(/\s+/g, "-").replace(/[^\w-]/g, "").toLowerCase();
+  }
+  function ensureHeadingIds(container) {
+    var headings = container.querySelectorAll("h1, h2, h3, h4, h5, h6");
+    for (var i = 0; i < headings.length; i++) {
+      var h = headings[i];
+      if (!h.id) {
+        var text = (h.textContent || "").trim();
+        var base = slugifyId(text) || "section";
+        var id = base;
+        var n = 0;
+        while (container.ownerDocument.getElementById(id)) id = base + "-" + (++n);
+        h.id = id;
+      }
+    }
+  }
+  function addSectionLinks(container) {
+    var headings = container.querySelectorAll("h1, h2, h3, h4, h5, h6");
+    for (var i = 0; i < headings.length; i++) {
+      var h = headings[i];
+      var id = h.id;
+      if (!id) continue;
+      var wrap = document.createElement("span");
+      wrap.className = "readme-heading-wrap";
+      h.parentNode.insertBefore(wrap, h);
+      wrap.appendChild(h);
+      var anchor = document.createElement("a");
+      anchor.href = "#" + id;
+      anchor.className = "readme-section-anchor";
+      anchor.setAttribute("aria-label", "Copy link to section");
+      anchor.textContent = "\u00A7";
+      anchor.addEventListener("click", function(e) {
+        e.preventDefault();
+        var targetId = this.getAttribute("href").slice(1);
+        var url = window.location.origin + window.location.pathname + window.location.search + "#" + targetId;
+        if (navigator.clipboard && navigator.clipboard.writeText) {
+          navigator.clipboard.writeText(url);
+        } else {
+          window.location.hash = targetId;
+        }
+      });
+      wrap.insertBefore(anchor, h);
+    }
+  }
+  function buildToc(container) {
+    var headings = container.querySelectorAll("h1, h2, h3, h4, h5, h6");
+    if (headings.length === 0) return null;
+    var nav = document.createElement("nav");
+    nav.className = "readme-toc";
+    nav.setAttribute("aria-label", "Table of contents");
+    var list = document.createElement("ul");
+    list.className = "readme-toc-list";
+    var tagLevel = { H1: 1, H2: 2, H3: 3, H4: 4, H5: 5, H6: 6 };
+    for (var i = 0; i < headings.length; i++) {
+      var h = headings[i];
+      var id = h.id;
+      if (!id) continue;
+      var level = tagLevel[h.tagName] || 1;
+      var li = document.createElement("li");
+      li.className = "readme-toc-item readme-toc-level-" + level;
+      li.setAttribute("data-id", id);
+      var a = document.createElement("a");
+      a.href = "#" + id;
+      a.className = "readme-toc-link";
+      a.textContent = (h.textContent || "").trim();
+      a.addEventListener("click", function(e) {
+        e.preventDefault();
+        var targetId = this.getAttribute("href").slice(1);
+        var target = document.getElementById(targetId);
+        if (target) target.scrollIntoView({ behavior: "smooth", block: "start" });
+        if (history.replaceState) history.replaceState(null, "", "#" + targetId);
+      });
+      li.appendChild(a);
+      list.appendChild(li);
+    }
+    nav.appendChild(list);
+    return nav;
+  }
+  function mountTocAndScrollSpy(tocEl, container) {
+    var page = document.querySelector(".page");
+    if (!page) return;
+    var tocPlaceholder = document.createElement("div");
+    tocPlaceholder.className = "readme-toc-mount";
+    page.appendChild(tocPlaceholder);
+    tocPlaceholder.appendChild(tocEl);
+    var links = tocEl.querySelectorAll(".readme-toc-link");
+    var items = tocEl.querySelectorAll(".readme-toc-item");
+    function updateActive() {
+      var fromTop = window.scrollY + 8;
+      var current = null;
+      var headings = container.querySelectorAll("h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]");
+      for (var i = headings.length - 1; i >= 0; i--) {
+        if (headings[i].getBoundingClientRect().top + window.scrollY <= fromTop) {
+          current = headings[i].id;
+          break;
+        }
+      }
+      if (!current && headings.length) current = headings[0].id;
+      for (var j = 0; j < items.length; j++) {
+        var on = items[j].getAttribute("data-id") === current;
+        items[j].classList.toggle("readme-toc-active", on);
+      }
+    }
+    var ticking = false;
+    function onScroll() {
+      if (!ticking) {
+        requestAnimationFrame(function() {
+          updateActive();
+          ticking = false;
+        });
+        ticking = true;
+      }
+    }
+    window.addEventListener("scroll", onScroll, { passive: true });
+    updateActive();
+  }
   function render() {
     var container = document.getElementById("readme-container");
     var enc = container && container.getAttribute("data-readme-b64");
@@ -28,9 +146,13 @@
     if (!container || !enc || typeof showdown === "undefined") return;
     try {
       var raw = atob(enc);
-      var conv = new showdown.Converter();
+      var conv = new showdown.Converter({ ghCompatibleHeaderId: true });
       container.innerHTML = conv.makeHtml(raw);
+      ensureHeadingIds(container);
       rewriteRelativeLinks(container, blobBase, blobDir);
+      addSectionLinks(container);
+      var toc = buildToc(container);
+      if (toc) mountTocAndScrollSpy(toc, container);
     } catch (e) {}
   }
   if (el) {
