diff --git a/.eleventy.js b/.eleventy.js index 82ad0f0..4d442aa 100644 --- a/.eleventy.js +++ b/.eleventy.js @@ -8,12 +8,12 @@ module.exports = function (eleventyConfig) { // Collection: about window content eleventyConfig.addCollection("aboutContent", (api) => - api.getFilteredByGlob("src/about.md") + api.getFilteredByGlob("src/content/about.md") ); // Collection: contact window content eleventyConfig.addCollection("contactContent", (api) => - api.getFilteredByGlob("src/contact.md") + api.getFilteredByGlob("src/content/contact.md") ); // Collection: projects, sorted alphabetically by title diff --git a/.gitignore b/.gitignore index c3c028f..828a3e1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ deploy.sh node_modules/ _site/ .claude +CLAUDE.md diff --git a/src/_includes/fragment.njk b/src/_includes/fragment.njk new file mode 100644 index 0000000..add03be --- /dev/null +++ b/src/_includes/fragment.njk @@ -0,0 +1,14 @@ +{% if title %} +

{{ title }}

+{% if date or tags %} +

+ {%- if date %}{{ date | readableDate }}{% if tags %} · {% endif %}{% endif -%} + {%- if tags %}{{ tags | join(', ') }}{% endif %} +

+
+{% endif %} +{% endif %} +{{ content | safe }} +{%- if boringUrl %} +

→ plaintext version

+{% endif %} diff --git a/src/assets/js/blog.js b/src/assets/js/blog.js index 40c0d59..861c48c 100644 --- a/src/assets/js/blog.js +++ b/src/assets/js/blog.js @@ -1,98 +1,80 @@ (function () { - var listing = document.getElementById('blog-listing'); - var content = document.getElementById('blog-window-content'); - var panels = document.querySelectorAll('.blog-post-panel'); - var tagBtns = document.querySelectorAll('.blog-tag'); - var items = document.querySelectorAll('.post-list-item'); + "use strict"; - // Tag filtering - tagBtns.forEach(function (btn) { - btn.addEventListener('click', function () { - tagBtns.forEach(function (b) { b.classList.remove('active'); }); - btn.classList.add('active'); - var tag = btn.dataset.tag; - items.forEach(function (li) { - var tags = li.dataset.tags ? li.dataset.tags.split(' ') : []; - li.style.display = (tag === '*' || tags.indexOf(tag) !== -1) ? '' : 'none'; - }); + function syncWindow(container) { + container.scrollTop = 0; + var win = container.closest(".window"); + if (win && win._syncMore) win._syncMore(); + } + + function openSubPage(url, container, backUrl) { + WM.loadFragment(url, container, function () { + var back = document.createElement("button"); + back.className = "blog-back"; + back.dataset.backUrl = backUrl; + back.textContent = "\u2190 back"; + container.insertBefore(back, container.firstChild); + syncWindow(container); }); - }); + } - // Open a post - document.querySelectorAll('.blog-open-post').forEach(function (link) { - link.addEventListener('click', function (e) { + document.addEventListener("click", function (e) { + + // Open a blog post + var postLink = e.target.closest(".blog-open-post"); + if (postLink) { e.preventDefault(); - listing.classList.add('hidden'); - panels.forEach(function (p) { p.classList.add('hidden'); }); - var panel = document.getElementById('blog-post-' + link.dataset.postIdx); - if (panel) { - panel.classList.remove('hidden'); - content.scrollTop = 0; - var win = content.closest('.window'); - if (win && win._syncMore) win._syncMore(); - } - }); - }); + var container = postLink.closest(".window-content"); + openSubPage(postLink.dataset.postUrl, container, "/fragments/blog/"); + return; + } + + // Open a project + var projLink = e.target.closest(".project-open-item"); + if (projLink) { + e.preventDefault(); + var container = projLink.closest(".window-content"); + openSubPage(projLink.dataset.projectUrl, container, "/fragments/projects/"); + return; + } + + // Back button (shared by blog and projects) + var backBtn = e.target.closest(".blog-back"); + if (backBtn) { + var container = backBtn.closest(".window-content"); + WM.loadFragment(backBtn.dataset.backUrl, container, function () { + syncWindow(container); + }); + return; + } + + // Blog tag filtering + var blogTag = e.target.closest(".blog-tag"); + if (blogTag) { + var content = blogTag.closest(".window-content"); + content.querySelectorAll(".blog-tag").forEach(function (b) { b.classList.remove("active"); }); + blogTag.classList.add("active"); + var tag = blogTag.dataset.tag; + content.querySelectorAll(".post-list-item").forEach(function (li) { + var tags = li.dataset.tags ? li.dataset.tags.split(" ") : []; + li.style.display = (tag === "*" || tags.indexOf(tag) !== -1) ? "" : "none"; + }); + return; + } + + // Project tag filtering + var projTag = e.target.closest(".project-tag"); + if (projTag) { + var content = projTag.closest(".window-content"); + content.querySelectorAll(".project-tag").forEach(function (b) { b.classList.remove("active"); }); + projTag.classList.add("active"); + var tag = projTag.dataset.tag; + content.querySelectorAll(".project-list-item").forEach(function (li) { + var tags = li.dataset.tags ? li.dataset.tags.split(" ") : []; + li.style.display = (tag === "*" || tags.indexOf(tag) !== -1) ? "" : "none"; + }); + return; + } - // Back to listing - document.querySelectorAll('.blog-back').forEach(function (btn) { - btn.addEventListener('click', function () { - panels.forEach(function (p) { p.classList.add('hidden'); }); - listing.classList.remove('hidden'); - content.scrollTop = 0; - var win = content.closest('.window'); - if (win && win._syncMore) win._syncMore(); - }); - }); -}()); - -// Projects panel -(function () { - var listing = document.getElementById('projects-listing'); - var content = document.getElementById('projects-window-content'); - var panels = document.querySelectorAll('.project-detail-panel'); - var tagBtns = document.querySelectorAll('.project-tag'); - var items = document.querySelectorAll('.project-list-item'); - - if (!listing) return; - - // Tag filtering - tagBtns.forEach(function (btn) { - btn.addEventListener('click', function () { - tagBtns.forEach(function (b) { b.classList.remove('active'); }); - btn.classList.add('active'); - var tag = btn.dataset.tag; - items.forEach(function (li) { - var tags = li.dataset.tags ? li.dataset.tags.split(' ') : []; - li.style.display = (tag === '*' || tags.indexOf(tag) !== -1) ? '' : 'none'; - }); - }); - }); - - // Open a project - document.querySelectorAll('.project-open-item').forEach(function (link) { - link.addEventListener('click', function (e) { - e.preventDefault(); - listing.classList.add('hidden'); - panels.forEach(function (p) { p.classList.add('hidden'); }); - var panel = document.getElementById('project-item-' + link.dataset.projectIdx); - if (panel) { - panel.classList.remove('hidden'); - content.scrollTop = 0; - var win = content.closest('.window'); - if (win && win._syncMore) win._syncMore(); - } - }) - }); - - // Back to listing - document.querySelectorAll('.project-back').forEach(function (btn) { - btn.addEventListener('click', function () { - panels.forEach(function (p) { p.classList.add('hidden'); }); - listing.classList.remove('hidden'); - content.scrollTop = 0; - var win = content.closest('.window'); - if (win && win._syncMore) win._syncMore(); - }); }); }()); diff --git a/src/assets/js/wm.js b/src/assets/js/wm.js index 68cd444..02bde83 100644 --- a/src/assets/js/wm.js +++ b/src/assets/js/wm.js @@ -6,10 +6,33 @@ const WM = (() => { "use strict"; let topZ = 100; - const state = new Map(); // wid → { el, hidden, maximized, savedStyle } + const state = new Map(); // wid → { el, hidden, maximized, savedStyle, contentLoaded } let drag = null; let resize = null; + /* Fragment loader */ + + const fragmentCache = {}; + + function loadFragment(url, container, onDone) { + if (fragmentCache[url]) { + container.innerHTML = fragmentCache[url]; + if (onDone) onDone(); + return; + } + container.innerHTML = "

Loading\u2026

"; + fetch(url) + .then(r => r.ok ? r.text() : Promise.reject(r.status)) + .then(html => { + fragmentCache[url] = html; + container.innerHTML = html; + if (onDone) onDone(); + }) + .catch(() => { + container.innerHTML = "

Failed to load.

"; + }); + } + const mobile = () => window.matchMedia("(pointer: coarse)").matches; /* Init */ @@ -90,7 +113,7 @@ const WM = (() => { buildChrome(win); const hidden = win.classList.contains("hidden"); - state.set(id, { el: win, hidden, maximized: false, savedStyle: null }); + state.set(id, { el: win, hidden, maximized: false, savedStyle: null, contentLoaded: false }); // Draggable title bar (desktop only) const bar = win.querySelector(".window-titlebar"); @@ -156,6 +179,13 @@ const WM = (() => { s.el.classList.remove("hidden"); s.hidden = false; focusEl(s.el); + if (s.el.dataset.src && !s.contentLoaded) { + s.contentLoaded = true; + const contentEl = s.el.querySelector(".window-content"); + if (contentEl) loadFragment(s.el.dataset.src, contentEl, () => { + if (s.el._syncMore) s.el._syncMore(); + }); + } requestAnimationFrame(() => { clampToArea(s.el); if (s.el._syncMore) s.el._syncMore(); @@ -272,5 +302,5 @@ const WM = (() => { document.addEventListener("DOMContentLoaded", init); - return { show, hide, toggle, focus }; + return { show, hide, toggle, focus, loadFragment }; })(); diff --git a/src/blog/posts/OTW_behemoth.md b/src/blog/posts/OTW_behemoth.md index 9205269..7ee4645 100644 --- a/src/blog/posts/OTW_behemoth.md +++ b/src/blog/posts/OTW_behemoth.md @@ -1,5 +1,4 @@ --- -permalink: false title: "Over The Wire - Behemoth retrospective" date: 2026-03-30 tags: diff --git a/src/blog/posts/posts.11tydata.js b/src/blog/posts/posts.11tydata.js new file mode 100644 index 0000000..fc7f7ef --- /dev/null +++ b/src/blog/posts/posts.11tydata.js @@ -0,0 +1,4 @@ +module.exports = { + layout: "fragment.njk", + permalink: data => `/fragments/blog/${data.page.fileSlug}/` +}; diff --git a/src/blog/posts/posts.11tydata.json b/src/blog/posts/posts.11tydata.json deleted file mode 100644 index 17dcfe1..0000000 --- a/src/blog/posts/posts.11tydata.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "layout": "post-plain.njk" -} diff --git a/src/about.md b/src/content/about.md similarity index 77% rename from src/about.md rename to src/content/about.md index fa7a7a9..98c427f 100644 --- a/src/about.md +++ b/src/content/about.md @@ -1,5 +1,7 @@ --- -permalink: false +layout: fragment.njk +permalink: /fragments/about/ +boringUrl: /boring/about/ --- ## $whoami @@ -15,4 +17,4 @@ Reach out if you want to chat about exploit dev, vulnerability research, or retr - Anything \*NIX. Practically, mostly Linux. Ask me about old UNIX or BSD. - Systems programming, reverse engineering, exploitation - Network services and architecture -- Rather familiar with Windows internals as well \ No newline at end of file +- Rather familiar with Windows internals as well diff --git a/src/contact.md b/src/content/contact.md similarity index 90% rename from src/contact.md rename to src/content/contact.md index 3c02153..639576d 100644 --- a/src/contact.md +++ b/src/content/contact.md @@ -1,5 +1,7 @@ --- -permalink: false +layout: fragment.njk +permalink: /fragments/contact/ +boringUrl: /boring/contact/ --- ## Jake "**lordtet**" Holtham diff --git a/src/fragments/blog.njk b/src/fragments/blog.njk new file mode 100644 index 0000000..360d989 --- /dev/null +++ b/src/fragments/blog.njk @@ -0,0 +1,33 @@ +--- +permalink: /fragments/blog/ +eleventyExcludeFromCollections: true +--- +{% set tagCounts = collections.posts | tagCounts %} +{% if tagCounts.length %} +
+ + {% for tc in tagCounts %} + + {% endfor %} +
+{% endif %} + +{% if collections.posts.length %} + +{% else %} +

No posts yet — check back soon.

+{% endif %} + +

+ → plaintext version +

diff --git a/src/fragments/projects.njk b/src/fragments/projects.njk new file mode 100644 index 0000000..66ec1a0 --- /dev/null +++ b/src/fragments/projects.njk @@ -0,0 +1,32 @@ +--- +permalink: /fragments/projects/ +eleventyExcludeFromCollections: true +--- +{% set projectTagCounts = collections.projects | tagCounts %} +{% if projectTagCounts.length %} +
+ + {% for tc in projectTagCounts %} + + {% endfor %} +
+{% endif %} + +{% if collections.projects.length %} + +{% else %} +

No projects yet.

+{% endif %} + +

+ → plaintext version +

diff --git a/src/index.njk b/src/index.njk index ea5c168..e8469e3 100644 --- a/src/index.njk +++ b/src/index.njk @@ -24,163 +24,40 @@ permalink: / -{# ════════════════════════════════════════════════ #} -{# ABOUT WINDOW #} -{# ════════════════════════════════════════════════ #} -{# ════════════════════════════════════════════════ #} -{# BLOG WINDOW #} -{# ════════════════════════════════════════════════ #} -{# ════════════════════════════════════════════════ #} -{# PROJECTS WINDOW #} -{# ════════════════════════════════════════════════ #} -{# ════════════════════════════════════════════════ #} -{# CONTACT WINDOW #} -{# ════════════════════════════════════════════════ #} + diff --git a/src/projects/projects.11tydata.js b/src/projects/projects.11tydata.js new file mode 100644 index 0000000..d955ac4 --- /dev/null +++ b/src/projects/projects.11tydata.js @@ -0,0 +1,4 @@ +module.exports = { + layout: "fragment.njk", + permalink: data => `/fragments/projects/${data.page.fileSlug}/` +}; diff --git a/src/projects/projects.11tydata.json b/src/projects/projects.11tydata.json deleted file mode 100644 index 5df2e98..0000000 --- a/src/projects/projects.11tydata.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "permalink": false -} diff --git a/src/projects/testproj.md b/src/projects/testproj.md index cc1997b..3bb1b24 100644 --- a/src/projects/testproj.md +++ b/src/projects/testproj.md @@ -1,5 +1,4 @@ --- -permalink: false title: "Test Project 1" tags: - x86 diff --git a/src/projects/testproj2.md b/src/projects/testproj2.md index 281046c..21ae859 100644 --- a/src/projects/testproj2.md +++ b/src/projects/testproj2.md @@ -1,5 +1,4 @@ --- -permalink: false title: "Test Project 2" tags: - arm