mirror of
https://github.com/andwati/pwnforfunandprofit.xyz.git
synced 2026-10-18 02:12:52 +00:00
feat: Implement updated date display for pages, add feed page container styling, and create a project TODO list.
This commit is contained in:
@@ -1 +1 @@
|
||||
- [ ] Cookie consent banner supported by cookieconsent
|
||||
# pwnforfunandprofit
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
# TODO — pwnforfunandprofit.xyz
|
||||
|
||||
Feature backlog for the blog. Check off items as they're implemented.
|
||||
|
||||
---
|
||||
|
||||
## Discovery & Navigation
|
||||
|
||||
- [ ] **Client-side search** — enable `build_search_index = true` in `zola.toml`, add a search page/modal with Elasticlunr
|
||||
- [ ] **Reading time estimate** — use `page.reading_time` to display "X min read" in post headers
|
||||
- [ ] **Related posts** — show 2–3 posts with overlapping tags at the bottom of each post
|
||||
- [ ] **Post series support** — `series` front-matter field; render a "Part N of X" callout with links to all parts in the series
|
||||
- [ ] **Keyboard shortcuts** — `K` to open search, `T` to toggle theme; show a small hotkey hint overlay
|
||||
|
||||
---
|
||||
|
||||
## Content Enhancements
|
||||
|
||||
- [ ] **Callout / admonition shortcodes** — `{% note %}`, `{% warning %}`, `{% tip %}`, `{% danger %}` Tera shortcodes styled as terminal callout boxes
|
||||
|
||||
- [x] **Updated-at badge** — show "Last updated: <date>" on posts that have `updated` front-matter set, distinct from the published date
|
||||
|
||||
---
|
||||
|
||||
## Post List & Filtering
|
||||
|
||||
- [ ] **Tag filtering on posts page** — JS-powered tag chip filter; clicking a tag hides non-matching posts without a page reload
|
||||
- [ ] **Reading progress bar** — thin accent-colored bar at the top of the viewport that fills as you scroll through a post
|
||||
- [ ] **Post view count** — Cloudflare Worker + KV store to track and display view counts (fits existing `wrangler.toml` setup)
|
||||
- [ ] **Sort / filter controls on posts page** — toggle between newest-first, oldest-first, and filter by tag or difficulty
|
||||
|
||||
---
|
||||
|
||||
## UI / UX Polish
|
||||
|
||||
- [ ] **Scroll-to-top button** — floating button that appears after scrolling down, smooth-scrolls back to top
|
||||
- [ ] **Table of contents active-section highlight** — as you scroll, the current heading is highlighted in the TOC
|
||||
- [ ] **Terminal-style command palette** — `Ctrl+K` opens a fuzzy-search palette over posts and nav links
|
||||
- [ ] **Animated hero terminal prompt** — typewriter effect on the homepage subtitle / profile section
|
||||
- [ ] **Back-to-top keyboard shortcut** — pressing `G` then `G` (vim-style) scrolls to the top of the page
|
||||
- [ ] **Smooth page transitions** — CSS View Transitions API for navigation between pages
|
||||
- [ ] **Image lightbox** — click any post image to view it full-screen with a keyboard-dismissible overlay
|
||||
- [ ] **Footnote hover previews** — hover over a footnote ref to see its content in a tooltip without jumping to the bottom
|
||||
|
||||
---
|
||||
|
||||
## Distribution & Reach
|
||||
|
||||
- [ ] **Webmention support** — receive likes/reposts from the Fediverse / IndieWeb via webmention.io
|
||||
- [ ] **Newsletter / email subscription** — integrate Buttondown or self-hosted Listmonk with a subscribe form
|
||||
- [ ] **`/llms.txt` auto-generation** — generate an `/llms.txt` manifest from post front-matter for AI discovery (existing `llms/` dir)
|
||||
- [ ] **JSON Feed** — add `feed_filenames = ["rss.xml", "feed.json"]` to `zola.toml` for JSON Feed support alongside RSS
|
||||
- [ ] **Open Graph preview images per post** — auto-generate OG images with post title overlaid using a Cloudflare Worker or build script
|
||||
|
||||
---
|
||||
|
||||
## Developer Experience
|
||||
|
||||
- [ ] **Prettier / markdownlint** — add linting for content files to enforce consistent front-matter and formatting
|
||||
- [ ] **`zola check` in CI** — GitHub Actions job that runs `zola check` on every PR to catch broken internal links
|
||||
- [ ] **Automated post slug validation** — pre-commit hook to enforce kebab-case filenames and required front-matter fields
|
||||
- [ ] **Cloudflare Pages preview deployments** — auto-deploy PR branches to a `*.pages.dev` preview URL for review
|
||||
- [ ] **Content spell-check in CI** — run `cspell` or `vale` on markdown files in the GitHub Actions pipeline
|
||||
|
||||
---
|
||||
|
||||
## Accessibility & Performance
|
||||
|
||||
- [ ] **Skip-to-content link** — hidden link that becomes visible on focus, skips the nav for keyboard users
|
||||
- [ ] **`prefers-reduced-motion` support** — disable/reduce animations when the OS accessibility preference is set
|
||||
- [ ] **Color contrast audit** — run `axe` or Lighthouse against the full site and fix any contrast failures
|
||||
- [ ] **Lazy-load images** — add `loading="lazy"` to all post body images via a Tera shortcode or post-processing
|
||||
- [ ] **Self-host all remaining external resources** — audit for any remaining CDN-loaded fonts or scripts and bring them local
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 2026-03-11*
|
||||
@@ -14,11 +14,41 @@
|
||||
}
|
||||
|
||||
.post-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.4rem 0.75rem;
|
||||
color: var(--text-muted);
|
||||
font-size: 0.9rem;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.post-meta-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
.post-meta-label {
|
||||
font-style: normal;
|
||||
font-size: 0.75rem;
|
||||
font-family: var(--font-mono);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--accent-color);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.post-meta-sep {
|
||||
color: var(--border-color);
|
||||
font-style: normal;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.post-meta-updated time {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.post-toc {
|
||||
background: var(--surface-color);
|
||||
border: 1px solid var(--border-color);
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
// Post Feed Page Container
|
||||
.feed-page {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
// Post Feed
|
||||
.post-feed {
|
||||
display: flex;
|
||||
|
||||
+11
-1
@@ -88,7 +88,17 @@
|
||||
<h1 class="post-title">{{ page.title }}</h1>
|
||||
<div class="post-meta">
|
||||
{% if page.date %}
|
||||
<time datetime="{{ page.date }}">{{ page.date | date(format="%B %e, %Y") }}</time>
|
||||
<span class="post-meta-item">
|
||||
<span class="post-meta-label">Posted</span>
|
||||
<time datetime="{{ page.date }}">{{ page.date | date(format="%B %e, %Y") }}</time>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if page.updated and page.updated != page.date %}
|
||||
<span class="post-meta-sep" aria-hidden="true">·</span>
|
||||
<span class="post-meta-item post-meta-updated">
|
||||
<span class="post-meta-label">Updated</span>
|
||||
<time datetime="{{ page.updated }}">{{ page.updated | date(format="%B %e, %Y") }}</time>
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
+81
-79
@@ -42,90 +42,92 @@
|
||||
{% endblock head %}
|
||||
|
||||
{% block content %}
|
||||
<div class="hero" style="padding-bottom: 2rem;">
|
||||
<div class="hero-content">
|
||||
<h1 class="hero-title">Posts</h1>
|
||||
<p class="hero-subtitle">Exploring binary exploitation, reverse engineering, and offensive security.</p>
|
||||
<div class="feed-page">
|
||||
<div class="hero" style="padding-bottom: 2rem;">
|
||||
<div class="hero-content">
|
||||
<h1 class="hero-title">Posts</h1>
|
||||
<p class="hero-subtitle">Exploring binary exploitation, reverse engineering, and offensive security.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="post-feed">
|
||||
{% for page in paginator.pages %}
|
||||
<article class="feed-item hover-scale">
|
||||
<a href="{{ page.permalink }}" class="item-link">
|
||||
<time datetime="{{ page.date }}" class="item-date">{{ page.date | date(format="%b %d, %Y") }}</time>
|
||||
<h2 class="item-title">{{ page.title }}</h2>
|
||||
{% if page.description %}
|
||||
<p class="item-desc">{{ page.description }}</p>
|
||||
{% endif %}
|
||||
</a>
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% if paginator.number_pagers > 1 %}
|
||||
<nav class="pagination" aria-label="Posts pagination">
|
||||
{# ← Previous #}
|
||||
{% if paginator.previous %}
|
||||
<a class="pagination-arrow" href="{{ paginator.previous }}" aria-label="Previous page">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg>
|
||||
</a>
|
||||
{% else %}
|
||||
<span class="pagination-arrow is-disabled" aria-disabled="true">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg>
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
{# Page numbers with smart windowing #}
|
||||
<div class="pagination-pages">
|
||||
{% set cur = paginator.current_index %}
|
||||
{% set total = paginator.number_pagers %}
|
||||
|
||||
{% for i in range(start=1, end=total+1) %}
|
||||
{% set show = false %}
|
||||
{# Always show first and last #}
|
||||
{% if i == 1 or i == total %}{% set show = true %}{% endif %}
|
||||
{# Show window of 2 around current #}
|
||||
{% if i >= cur - 2 and i <= cur + 2 %}{% set show = true %}{% endif %}
|
||||
|
||||
{% if show %}
|
||||
{# Show ellipsis before this page if there's a gap #}
|
||||
{% if i > 1 and i > cur - 2 and i != 2 %}
|
||||
{# We need gap indicator — output ellipsis if prev shown page was not adjacent #}
|
||||
{% if i == cur - 2 and cur - 3 > 1 %}
|
||||
<span class="pagination-ellipsis">…</span>
|
||||
{% elif i == total and cur + 2 < total - 1 %}
|
||||
<span class="pagination-ellipsis">…</span>
|
||||
{% endif %}
|
||||
<div class="post-feed">
|
||||
{% for page in paginator.pages %}
|
||||
<article class="feed-item hover-scale">
|
||||
<a href="{{ page.permalink }}" class="item-link">
|
||||
<time datetime="{{ page.date }}" class="item-date">{{ page.date | date(format="%b %d, %Y") }}</time>
|
||||
<h2 class="item-title">{{ page.title }}</h2>
|
||||
{% if page.description %}
|
||||
<p class="item-desc">{{ page.description }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% if i == cur %}
|
||||
<span class="pagination-num is-current" aria-current="page">{{ i }}</span>
|
||||
{% else %}
|
||||
{% if i == 1 %}
|
||||
<a class="pagination-num" href="{{ paginator.first }}" aria-label="Page {{ i }}">{{ i }}</a>
|
||||
{% elif i == total %}
|
||||
<a class="pagination-num" href="{{ paginator.last }}" aria-label="Page {{ i }}">{{ i }}</a>
|
||||
{% else %}
|
||||
<a class="pagination-num" href="{{ paginator.first }}page/{{ i }}/" aria-label="Page {{ i }}">{{ i }}</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</a>
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{# Next → #}
|
||||
{% if paginator.next %}
|
||||
<a class="pagination-arrow" href="{{ paginator.next }}" aria-label="Next page">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg>
|
||||
</a>
|
||||
{% else %}
|
||||
<span class="pagination-arrow is-disabled" aria-disabled="true">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg>
|
||||
</span>
|
||||
{% endif %}
|
||||
</nav>
|
||||
{% if paginator.number_pagers > 1 %}
|
||||
<nav class="pagination" aria-label="Posts pagination">
|
||||
{# ← Previous #}
|
||||
{% if paginator.previous %}
|
||||
<a class="pagination-arrow" href="{{ paginator.previous }}" aria-label="Previous page">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg>
|
||||
</a>
|
||||
{% else %}
|
||||
<span class="pagination-arrow is-disabled" aria-disabled="true">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg>
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
<p class="pagination-meta">Page {{ paginator.current_index }} of {{ paginator.number_pagers }}</p>
|
||||
{% endif %}
|
||||
{# Page numbers with smart windowing #}
|
||||
<div class="pagination-pages">
|
||||
{% set cur = paginator.current_index %}
|
||||
{% set total = paginator.number_pagers %}
|
||||
|
||||
{% for i in range(start=1, end=total+1) %}
|
||||
{% set show = false %}
|
||||
{# Always show first and last #}
|
||||
{% if i == 1 or i == total %}{% set show = true %}{% endif %}
|
||||
{# Show window of 2 around current #}
|
||||
{% if i >= cur - 2 and i <= cur + 2 %}{% set show = true %}{% endif %}
|
||||
|
||||
{% if show %}
|
||||
{# Show ellipsis before this page if there's a gap #}
|
||||
{% if i > 1 and i > cur - 2 and i != 2 %}
|
||||
{# We need gap indicator — output ellipsis if prev shown page was not adjacent #}
|
||||
{% if i == cur - 2 and cur - 3 > 1 %}
|
||||
<span class="pagination-ellipsis">…</span>
|
||||
{% elif i == total and cur + 2 < total - 1 %}
|
||||
<span class="pagination-ellipsis">…</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if i == cur %}
|
||||
<span class="pagination-num is-current" aria-current="page">{{ i }}</span>
|
||||
{% else %}
|
||||
{% if i == 1 %}
|
||||
<a class="pagination-num" href="{{ paginator.first }}" aria-label="Page {{ i }}">{{ i }}</a>
|
||||
{% elif i == total %}
|
||||
<a class="pagination-num" href="{{ paginator.last }}" aria-label="Page {{ i }}">{{ i }}</a>
|
||||
{% else %}
|
||||
<a class="pagination-num" href="{{ paginator.first }}page/{{ i }}/" aria-label="Page {{ i }}">{{ i }}</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{# Next → #}
|
||||
{% if paginator.next %}
|
||||
<a class="pagination-arrow" href="{{ paginator.next }}" aria-label="Next page">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg>
|
||||
</a>
|
||||
{% else %}
|
||||
<span class="pagination-arrow is-disabled" aria-disabled="true">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg>
|
||||
</span>
|
||||
{% endif %}
|
||||
</nav>
|
||||
|
||||
<p class="pagination-meta">Page {{ paginator.current_index }} of {{ paginator.number_pagers }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
||||
Reference in New Issue
Block a user