mirror of
https://github.com/andwati/pwnforfunandprofit.xyz.git
synced 2026-10-18 02:12:52 +00:00
feat: Implement sticky sidebar TOC with active section highlighting and add related template files
This commit is contained in:
@@ -1,6 +1,4 @@
|
||||
# Future Enhancements Backlog
|
||||
|
||||
Here is a curated list of suggestions to further refine the blog's typography, user experience, feature set, and performance. Check items off as they are implemented!
|
||||
|
||||
## Typography & Reading Experience
|
||||
|
||||
@@ -8,33 +6,27 @@ Here is a curated list of suggestions to further refine the blog's typography, u
|
||||
- [ ] **Text Wrap Balancing** — Apply `text-wrap: balance` to headings and `text-wrap: pretty` to paragraphs to prevent awkward orphaned words.
|
||||
- [ ] **Fluid Typography Scale** — Refactor font sizes to use CSS `clamp()` functions for perfectly smooth scaling across all devices without relying on breakpoints.
|
||||
- [ ] **Line Highlighting in Code Blocks** — Implement a custom shortcode or modify the syntax highlighter to allow highlighting specific lines of code (vital for exploit tutorials).
|
||||
- [ ] **Reading Progress Time Remaining** — Instead of just a progress bar, dynamically calculate and display "X mins remaining" based on current scroll depth.
|
||||
- [ ] **Footnote Backlinks** — Add a smooth-scroll return link (e.g., `[↩]`) inside the expanded footnote tooltip or at the bottom of the article to jump back to the reading position.
|
||||
|
||||
## UI / UX Polish
|
||||
|
||||
- [ ] **Image Zoom / Lightbox** — Implement a Medium-style image zoom (e.g., using `medium-zoom` library) allowing users to enlarge detailed screenshots by clicking them.
|
||||
- [ ] **Heading Anchor Links** — Add an interactive `#` link that appears when hovering over `<h2>` and `<h3>` tags to make linking to specific sections easier.
|
||||
- [ ] **Custom Terminal Scrollbars** — Style the browser's default scrollbar to match the dark terminal aesthetic using `::-webkit-scrollbar` and `scrollbar-color`.
|
||||
- [ ] **Sticky Sidebar Table of Contents** — On wide desktop layouts, detach the Table of Contents and make it sticky on the right or left side so it follows the user as they read.
|
||||
- [ ] **Code Block Copy Confirmation** — Add a subtle visual state change (like a checkmark or a "Copied!" popover toast) when a user successfully copies a code block.
|
||||
|
||||
- [ ] **Custom 404 Page** — Design an interactive terminal-style 404 page ("Command not found") that fits the pwnforfunandprofit theme.
|
||||
|
||||
## Features & Content
|
||||
|
||||
- [ ] **Post Series Support** — Add a `series` front-matter field to automatically group related tutorials (e.g., "Buffer Overflows Part 1, 2, 3") and render a "Part N of X" navigation box.
|
||||
- [ ] **Interactive Exploit Demos** — If applicable, embed small interactive WebAssembly (WASM) or xterm.js terminals demonstrating basic concepts directly in the browser.
|
||||
- [ ] **"Recently Viewed" Posts** — Use `localStorage` to remember the last 3 posts a user visited and display them on the homepage.
|
||||
- [ ] **Drafts & Unlisted Posts** — Configure Zola to ignore posts marked with `draft = true` or create an `unlisted` tag that hides posts from the main feed but keeps them accessible via direct link.
|
||||
- [ ] **Exploit Scripts Download** — A dedicated button or shortcode block that bundles all code snippets from an article into a single downloadable `.py` or `.c` file.
|
||||
|
||||
- [ ] **Interactive CVE Timeline** — A shortcode to render animated timeline infographics for big exploit write-ups or breach post-mortems.
|
||||
- [ ] **Configurable Comments** — Modify the Utterances/Giscus integration to respect a `comments = false` flag in the post front-matter, allowing you to disable discussion on specific pages.
|
||||
|
||||
|
||||
## Performance & SEO
|
||||
|
||||
- [ ] **Automatic WebP Image Optimization** — Refactor image rendering to use Zola's `resize_image` function to automatically generate highly compressed `webp` versions of all uploaded images at build time.
|
||||
|
||||
- [ ] **Lazy Loading** — Ensure all `<img>` and `<iframe>` tags across the site use `loading="lazy"` to drastically improve initial page load times.
|
||||
- [ ] **Asset Preloading** — Add `<link rel="preload">` tags in the `<head>` for critical assets like the primary font files to eliminate text rendering flashes (FOUT/FOIT).
|
||||
- [ ] **PWA / Offline Support** — Add a simple Service Worker and a `manifest.json` so readers can "install" the blog to their home screen and read cached articles offline.
|
||||
- [ ] **RSS Feed Thumbnail Images** — Modify the `rss.xml` template to include post cover images (or a default banner) so aggregators display your articles more attractively.
|
||||
|
||||
|
||||
- [ ] **Minify HTML & CSS** — Enable and configure Zola's built-in minification settings (`minify_html = true`) to shave off extra bytes in production.
|
||||
|
||||
@@ -261,6 +261,110 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────
|
||||
// Sidebar TOC Layout
|
||||
// ──────────────────────────────────────────
|
||||
|
||||
.post-layout {
|
||||
display: contents;
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 800px) 240px;
|
||||
gap: 3rem;
|
||||
align-items: start;
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.post-detail {
|
||||
max-width: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
// Hide inline TOC — sidebar takes over
|
||||
.post-toc {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.post-toc-sidebar {
|
||||
display: none;
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
display: block;
|
||||
position: sticky;
|
||||
top: 5.5rem;
|
||||
max-height: calc(100vh - 7rem);
|
||||
overflow-y: auto;
|
||||
padding-top: 4rem; // align with article content start
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--border-color) transparent;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: var(--border-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.toc-nav {
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
// Nested level (h2 children)
|
||||
ul {
|
||||
padding-left: 1rem;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: 0.35rem;
|
||||
}
|
||||
|
||||
a {
|
||||
display: block;
|
||||
color: var(--text-muted);
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.4;
|
||||
padding: 0.2rem 0;
|
||||
border-left: 2px solid transparent;
|
||||
padding-left: 0.75rem;
|
||||
transition: color 0.2s ease, border-color 0.2s ease;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
&.toc-active {
|
||||
color: var(--accent-color);
|
||||
border-left-color: var(--accent-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.toc-sidebar-title {
|
||||
font-size: 0.75rem;
|
||||
font-family: var(--font-mono);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
color: var(--text-muted);
|
||||
margin: 0 0 1rem 0;
|
||||
padding-left: 0.75rem;
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────
|
||||
// Media Wrappers (YouTube, etc)
|
||||
// ──────────────────────────────────────────
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
(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);
|
||||
})();
|
||||
+10
-1
@@ -82,6 +82,11 @@
|
||||
<!-- Reading Progress Bar -->
|
||||
<script defer src="{{ get_url(path="js/progress.js") }}"></script>
|
||||
|
||||
<!-- Sticky TOC active-section highlighting -->
|
||||
{% if page.toc %}
|
||||
<script defer src="{{ get_url(path="js/toc.js") }}"></script>
|
||||
{% endif %}
|
||||
|
||||
{% endblock head %}
|
||||
|
||||
|
||||
@@ -90,6 +95,7 @@
|
||||
<div class="reading-progress-bar" id="reading-progress-bar"></div>
|
||||
</div>
|
||||
|
||||
<div class="post-layout">
|
||||
<article class="post-detail">
|
||||
<header class="post-header">
|
||||
<h1 class="post-title">{{ page.title }}</h1>
|
||||
@@ -137,9 +143,12 @@
|
||||
</div>
|
||||
{% include "partials/social-share.html" %}
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Utterances comments (posts only) -->
|
||||
{% include "partials/comments.html" %}
|
||||
</footer>
|
||||
</article>
|
||||
|
||||
{% include "partials/toc-sidebar.html" %}
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
{% if page.toc %}
|
||||
<aside class="post-toc-sidebar" aria-label="Table of contents">
|
||||
<nav class="toc-nav">
|
||||
<h2 class="toc-sidebar-title">Contents</h2>
|
||||
<ul>
|
||||
{% for h1 in page.toc %}
|
||||
<li>
|
||||
<a href="{{ h1.permalink | safe }}" data-toc-id="{{ h1.id }}">{{ h1.title }}</a>
|
||||
{% if h1.children %}
|
||||
<ul>
|
||||
{% for h2 in h1.children %}
|
||||
<li><a href="{{ h2.permalink | safe }}" data-toc-id="{{ h2.id }}">{{ h2.title }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</nav>
|
||||
</aside>
|
||||
{% endif %}
|
||||
Reference in New Issue
Block a user