Downloaded theme

This commit is contained in:
2026-03-23 18:35:59 +01:00
parent 326fab0e42
commit d091efd432
86 changed files with 14512 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
document.addEventListener("DOMContentLoaded", function () {
if (typeof window.$ === "undefined" && typeof window.JustifiedGallery === "undefined") {
// using vanilla JustifiedGallery from CDN, globally exposed
}
var roots = document.querySelectorAll("[data-jg]");
if (!roots.length || typeof window.JustifiedGallery === "undefined") return;
roots.forEach(function (el) {
new window.JustifiedGallery(el, {
rowHeight: 260,
lastRow: "center",
margin: 16,
});
});
});

View File

@@ -0,0 +1,11 @@
document.addEventListener("DOMContentLoaded", function () {
if (typeof GLightbox === "undefined") return;
GLightbox({
selector: ".glightbox",
loop: true,
touchNavigation: true,
zoomable: true,
draggable: true,
});
});

View File

@@ -0,0 +1,137 @@
(function () {
var STORAGE_KEY = "theme";
/* ---------- Theme ---------- */
function getStoredTheme() {
try {
return localStorage.getItem(STORAGE_KEY);
} catch (e) {
return null;
}
}
function storeTheme(theme) {
try {
localStorage.setItem(STORAGE_KEY, theme);
} catch (e) {}
}
function getSystemTheme() {
return window.matchMedia &&
window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
}
function applyTheme(theme) {
document.documentElement.setAttribute("data-theme", theme);
var isDark = theme === "dark";
var lightIcons = document.querySelectorAll("[data-theme-icon-light]");
var darkIcons = document.querySelectorAll("[data-theme-icon-dark]");
lightIcons.forEach(function (el) {
el.style.display = isDark ? "" : "none";
});
darkIcons.forEach(function (el) {
el.style.display = isDark ? "none" : "";
});
}
function initThemeFromDOM() {
var attr = document.documentElement.getAttribute("data-theme");
if (attr === "dark" || attr === "light") {
applyTheme(attr);
return;
}
var stored = getStoredTheme();
applyTheme(stored || getSystemTheme());
}
function toggleTheme() {
var current =
document.documentElement.getAttribute("data-theme") || "light";
var next = current === "dark" ? "light" : "dark";
applyTheme(next);
storeTheme(next);
}
function initThemeToggle() {
var btns = document.querySelectorAll("[data-theme-toggle]");
if (!btns.length) return;
btns.forEach(function (btn) {
btn.addEventListener("click", toggleTheme);
});
}
/* ---------- Mobile nav ---------- */
function initMobileNav() {
var toggle = document.querySelector("[data-mobile-nav-toggle]");
var nav = document.querySelector("[data-mobile-nav]");
if (!toggle || !nav) return;
var open = false;
function setOpen(next) {
open = next;
nav.style.display = open ? "block" : "none";
}
toggle.addEventListener("click", function () {
setOpen(!open);
});
nav.addEventListener("click", function (e) {
if (e.target.tagName === "A") setOpen(false);
});
}
/* ---------- Dock ---------- */
function initDock() {
var dock = document.querySelector("[data-dock]");
if (!dock) return;
var toggle = dock.querySelector("[data-dock-toggle]");
var backTop = dock.querySelector('[data-dock-action="top"]');
var backBtn = dock.querySelector('[data-dock-action="back"]');
var open = false;
function setOpen(next) {
open = next;
dock.classList.toggle("dock--open", open);
}
if (toggle) {
toggle.addEventListener("click", function () {
setOpen(!open);
});
}
if (backTop) {
backTop.addEventListener("click", function (e) {
e.preventDefault();
window.scrollTo({
top: 0,
behavior: "smooth",
});
});
}
if (backBtn) {
backBtn.addEventListener("click", function (e) {
e.preventDefault();
window.history.back();
});
}
}
/* ---------- Init ---------- */
document.addEventListener("DOMContentLoaded", function () {
initThemeFromDOM();
initThemeToggle();
initMobileNav();
initDock();
});
})();

View File

@@ -0,0 +1,253 @@
(function () {
var overlay, inputEl, resultsEl;
var indexLoaded = false;
var pages = [];
function ensureElements() {
if (!overlay) {
overlay = document.querySelector("[data-search-overlay]");
}
if (!inputEl && overlay) {
inputEl = overlay.querySelector("[data-search-input]");
}
if (!resultsEl && overlay) {
resultsEl = overlay.querySelector("[data-search-results]");
}
}
function loadIndex() {
if (indexLoaded) return;
indexLoaded = true;
fetch("/index.json")
.then(function (r) {
if (!r.ok) throw new Error("index.json not found");
return r.json();
})
.then(function (data) {
pages = (data && data.pages) || [];
})
.catch(function () {
pages = [];
});
}
function openOverlay() {
ensureElements();
if (!overlay) return;
overlay.classList.remove("search-overlay");
overlay.classList.add("search-overlay--open");
loadIndex();
if (inputEl) {
setTimeout(function () {
inputEl.focus();
}, 20);
}
}
function closeOverlay() {
ensureElements();
if (!overlay) return;
if (overlay.classList.contains("search-overlay--closing")) return;
overlay.classList.add("search-overlay--closing");
setTimeout(function () {
overlay.classList.remove("search-overlay--open");
overlay.classList.remove("search-overlay--closing");
overlay.classList.add("search-overlay");
if (inputEl) inputEl.value = "";
if (resultsEl) {
resultsEl.innerHTML =
'<div class="search-empty-state">' +
'<div class="search-empty-icon"><i class="fa-solid fa-magnifying-glass text-[1rem]"></i></div>' +
'<p class="search-empty-title">Start searching</p>' +
'<p class="search-empty-subtitle">Enter keywords to search articles.</p>' +
"</div>";
}
}, 180);
}
function filterPages(query) {
if (!pages.length) return [];
var q = (query || "").toLowerCase().trim();
if (!q) return [];
return pages
.filter(function (p) {
var t = (p.title || "").toLowerCase();
var s = (p.summary || "").toLowerCase();
return t.indexOf(q) !== -1 || s.indexOf(q) !== -1;
})
.slice(0, 20);
}
function highlightText(text, query) {
if (!query) return text;
var regex = new RegExp("(" + query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + ")", "gi");
return text.replace(regex, '<mark class="search-highlight">$1</mark>');
}
function getSectionIcon(section) {
var icons = {
blog: "fa-regular fa-note-sticky",
projects: "fa-regular fa-folder-open",
posts: "fa-regular fa-note-sticky",
};
return icons[section.toLowerCase()] || "fa-regular fa-file";
}
function truncateText(text, maxLength) {
if (!text || text.length <= maxLength) return text;
return text.substring(0, maxLength) + "...";
}
function renderResults(query) {
ensureElements();
if (!resultsEl) return;
var q = (query || "").trim();
if (!q) {
resultsEl.innerHTML =
'<div class="search-empty-state">' +
'<div class="search-empty-icon"><i class="fa-solid fa-magnifying-glass text-[1rem]"></i></div>' +
'<p class="search-empty-title">Start searching</p>' +
'<p class="search-empty-subtitle">Enter keywords to search articles.</p>' +
"</div>";
return;
}
var matches = filterPages(q);
if (!matches.length) {
resultsEl.innerHTML =
'<div class="search-empty-state">' +
'<div class="search-empty-icon"><i class="fa-solid fa-circle-exclamation text-[1rem]"></i></div>' +
'<p class="search-empty-title">No results found</p>' +
'<p class="search-empty-subtitle">Try different keywords or check your spelling.</p>' +
"</div>";
return;
}
var html = matches
.map(function (p, index) {
var title = highlightText(p.title || "Untitled", q);
var section = p.section || "";
var summary = truncateText(p.summary || "", 120);
var highlightedSummary = highlightText(summary, q);
var icon = getSectionIcon(section);
var date = p.date ? new Date(p.date).toLocaleDateString("en-US", {
year: "numeric",
month: "short",
day: "numeric"
}) : "";
return (
'<a href="' +
p.permalink +
'" class="search-result-item" data-result-index="' +
index +
'">' +
'<div class="search-result-header">' +
'<i class="' + icon + ' search-result-icon"></i>' +
'<div class="search-result-info">' +
'<div class="search-result-title">' +
title +
"</div>" +
'<div class="search-result-meta">' +
(section ? '<span class="search-result-section">' + section + "</span>" : "") +
(date ? '<span class="search-result-date">' + date + "</span>" : "") +
"</div>" +
"</div>" +
"</div>" +
(highlightedSummary ? '<div class="search-result-summary">' + highlightedSummary + "</div>" : "") +
"</a>"
);
})
.join("");
resultsEl.innerHTML = html;
// Add keyboard navigation
addKeyboardNavigation();
}
var selectedIndex = -1;
function addKeyboardNavigation() {
ensureElements();
if (!inputEl) return;
var items = resultsEl.querySelectorAll(".search-result-item");
inputEl.addEventListener("keydown", function(e) {
if (e.key === "ArrowDown") {
e.preventDefault();
selectedIndex = Math.min(selectedIndex + 1, items.length - 1);
updateSelection(items);
} else if (e.key === "ArrowUp") {
e.preventDefault();
selectedIndex = Math.max(selectedIndex - 1, -1);
updateSelection(items);
} else if (e.key === "Enter" && selectedIndex >= 0) {
e.preventDefault();
items[selectedIndex].click();
}
});
}
function updateSelection(items) {
items.forEach(function(item, index) {
if (index === selectedIndex) {
item.classList.add("search-result-item--selected");
item.scrollIntoView({ block: "nearest", behavior: "smooth" });
} else {
item.classList.remove("search-result-item--selected");
}
});
}
function initSearch() {
ensureElements();
if (!overlay) return;
// Close and ESC
overlay.querySelectorAll("[data-search-close]").forEach(function (el) {
el.addEventListener("click", closeOverlay);
});
document.addEventListener("keydown", function (e) {
if (e.key === "Escape") closeOverlay();
});
// Typing
if (inputEl) {
inputEl.addEventListener("input", function (e) {
renderResults(e.target.value || "");
});
}
// Ctrl/Cmd + K to open
document.addEventListener("keydown", function (e) {
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "k") {
e.preventDefault();
openOverlay();
}
});
// Expose global API for inline onclick
window.MinimalSearch = {
open: openOverlay,
close: closeOverlay,
};
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initSearch);
} else {
initSearch();
}
})();