summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArne Rief <riearn@proton.me>2025-12-22 14:02:07 +0100
committerArne Rief <riearn@proton.me>2025-12-22 14:02:07 +0100
commit038054b8206a9c25e84adeb0f0f355abd22d6143 (patch)
tree47700e52b88e0db08f0ca113de22f4c0d05cc405
parent32c1a5dd203435e8bef324306d1516e28ce14615 (diff)
CSS & JS splitting, small fix section badge
-rw-r--r--assets/css/footer.css31
-rw-r--r--assets/css/header.css41
-rw-r--r--assets/css/list-navigation.css18
-rw-r--r--assets/css/main.css348
-rw-r--r--assets/css/navmenu.css26
-rw-r--r--assets/css/page.css19
-rw-r--r--assets/css/post-card.css71
-rw-r--r--assets/css/post.css88
-rw-r--r--assets/css/search.css51
-rw-r--r--assets/js/main.js151
-rw-r--r--assets/js/search.js118
-rw-r--r--assets/js/theme.js32
-rw-r--r--layouts/_default/all-posts.html2
-rw-r--r--layouts/_default/search.html2
-rw-r--r--layouts/index.json2
-rw-r--r--layouts/partials/head/css.html28
-rw-r--r--layouts/partials/list/post-card.html17
-rw-r--r--layouts/partials/list/recent-posts.html2
-rw-r--r--layouts/tags/term.html2
19 files changed, 533 insertions, 516 deletions
diff --git a/assets/css/footer.css b/assets/css/footer.css
new file mode 100644
index 0000000..88d705c
--- /dev/null
+++ b/assets/css/footer.css
@@ -0,0 +1,31 @@
+/* FOOTER */
+.site__footer {
+ display: flex;
+ align-items: center;
+ justify-content: space-around;
+ margin-top: var(--gap-medium);
+
+ @media (max-width: 768px) {
+ flex-direction: column;
+ gap: var(--gap-default);
+ }
+}
+
+.follow-me-list {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: var(--gap-medium);
+
+ @media (max-width: 768px) {
+ flex-wrap: wrap;
+ }
+}
+
+/* Footer Icons */
+.follow-me-item img {
+ object-fit: contain;
+ height: 2rem;
+ width: 2rem;
+}
+
diff --git a/assets/css/header.css b/assets/css/header.css
new file mode 100644
index 0000000..8cb9e9d
--- /dev/null
+++ b/assets/css/header.css
@@ -0,0 +1,41 @@
+/* HEADER */
+.site__header {
+ display: flex;
+ align-items: center;
+ justify-content: space-around;
+ margin-bottom: var(--gap-medium);
+}
+
+.site-selections,
+.site-selections * {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: var(--gap-medium);
+}
+
+.site-title {
+ color: inherit !important;
+ font-size: var(--font-size-h1);
+ font-weight: bold;
+ text-decoration: none;
+}
+
+/* Flag Icon */
+.language-select__language-item img {
+ height: 2rem;
+}
+
+.theme-toggle {
+ background-color: var(--text-color);
+ border: none;
+ border-radius: var(--border-radius-max);
+ color: var(--bg-main);
+ cursor: pointer;
+ padding: 0.2rem;
+}
+
+.theme-toggle svg {
+ display: none;
+}
+
diff --git a/assets/css/list-navigation.css b/assets/css/list-navigation.css
new file mode 100644
index 0000000..2f8495c
--- /dev/null
+++ b/assets/css/list-navigation.css
@@ -0,0 +1,18 @@
+/* List navigation */
+.pagination {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.recent-posts__view-all-link,
+.tag-page__view-all-link {
+ display: block;
+ margin: 0 auto;
+ width: max-content;
+}
+
+.tags-index__list > li {
+ margin-bottom: var(--gap-small);
+}
+
diff --git a/assets/css/main.css b/assets/css/main.css
index a17c9b3..c693221 100644
--- a/assets/css/main.css
+++ b/assets/css/main.css
@@ -25,7 +25,7 @@
--text-color: #1e1e1e;
}
-/* BASICS */
+/* BASE SETTINGS */
*,
*::before,
*::after {
@@ -51,7 +51,7 @@ html {
&[data-theme="dark"] {
--bg-main: #0a234a;
--bg-special: #1e4a73;
- --glow: rgba(79, 195, 247, 0.24);
+ --glow: rgba(79, 195, 247, 0.24);
--link-color: #4fc3f7;
--link-hover: #039be5;
--shadow-default: 0 2px 6px rgba(224, 224, 224, 0.15);
@@ -220,347 +220,3 @@ p > code {
width: fit-content;
}
-/* HEADER */
-.site__header {
- display: flex;
- align-items: center;
- justify-content: space-around;
- margin-bottom: var(--gap-medium);
-}
-
-.site-selections,
-.site-selections * {
- display: flex;
- align-items: center;
- justify-content: center;
- gap: var(--gap-medium);
-}
-
-.site-title {
- color: inherit !important;
- font-size: var(--font-size-h1);
- font-weight: bold;
- text-decoration: none;
-}
-
-/* Flag Icon */
-.language-select__language-item img {
- height: 2rem;
-}
-
-.theme-toggle {
- background-color: var(--text-color);
- border: none;
- border-radius: var(--border-radius-max);
- color: var(--bg-main);
- cursor: pointer;
- padding: 0.2rem;
-}
-
-.theme-toggle svg {
- display: none;
-}
-
-/* SITE NAVMENU */
-.header__navigation {
- background-color: var(--bg-special);
- border-radius: var(--border-radius-default);
- padding: var(--margin-padding-Y-small);
-}
-
-.header__navigation-list {
- display: flex;
- align-items: center;
- justify-content: space-evenly;
-
- @media (max-width: 768px) {
- /* Grid items stretch to at least 100px or take up full width */
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
- justify-items: center;
- gap: var(--gap-medium) var(--gap-small);
- }
-}
-
-.header__navigation-link--active {
- color: var(--text-color);
- text-decoration: none;
-}
-
-/* FOOTER */
-.site__footer {
- display: flex;
- align-items: center;
- justify-content: space-around;
- margin-top: var(--gap-medium);
-
- @media (max-width: 768px) {
- flex-direction: column;
- gap: var(--gap-default);
- }
-}
-
-.follow-me-list {
- display: flex;
- align-items: center;
- justify-content: center;
- gap: var(--gap-medium);
-
- @media (max-width: 768px) {
- flex-wrap: wrap;
- }
-}
-
-/* Footer Icons */
-.follow-me-item img {
- object-fit: contain;
- height: 2rem;
- width: 2rem;
-}
-
-/* POST CARD */
-.post-card {
- border: 3px solid var(--bg-special);
- border-radius: var(--border-radius-default);
- margin: 0 auto 1.5rem;
- padding: 0 var(--gap-default);
- width: 90%;
-
- & .post-card__title {
- font-size: var(--font-size-default);
- margin-top: var(--gap-default);
- }
-
- & .post-card__meta {
- display: flex;
- align-items: center;
- gap: var(--gap-small);
- }
-
- & .post-card__meta,
- .post-card__summary {
- font-size: var(--font-size-small);
- }
-
- & .post-card__meta-info {
- margin-bottom: 0;
- }
-
- @media (max-width: 768px) {
- padding: 0 var(--gap-small);
- }
-}
-
-.post-card__header {
- display: flex;
- flex-direction: column;
- align-items: start;
- justify-content: center;
- gap: 0.5rem;
- margin: var(--margin-padding-Y-small);
-}
-
-.post-card__tags-list {
- display: flex;
- align-items: center;
- justify-content: start;
- gap: var(--gap-default);
- margin: var(--margin-padding-Y-default) !important;
-
- @media (max-width: 768px) {
- flex-direction: column;
- align-items: start;
- }
-}
-
-.post-card__tags-item {
- background-color: var(--bg-special);
- border-radius: var(--border-radius-max);
- color: var(--text-color);
- font-size: var(--font-size-small);
- padding: 0.1rem 0.4rem;
-}
-
-.post-card__section-badge {
- background-color: var(--link-color);
- border-radius: var(--border-radius-max);
- color: var(--bg-main);
- font-size: var(--font-size-small);
- padding: 0.1rem 0.4rem;
-}
-
-/* PAGES Content */
-.page-not-found {
- text-align: center;
-}
-
-/* Homepage */
-.homepage__header {
- text-align: center;
-}
-
-.homepage__header-image {
- border: 4px solid var(--bg-special);
- border-radius: var(--border-radius-max);
- display: block;
- margin: 1rem auto;
- max-width: 150px;
- max-height: 150px;
-}
-
-/* Article */
-.post {
- position: relative; /* Needed for post__scroll-top */
-}
-
-.post__content ol > li,
-.post__content ul > li {
- margin-bottom: var(--gap-small);
-}
-
-.post__header {
- margin-bottom: var(--gap-large);
-}
-
-.post__navigation > hr {
- height: 2px;
- margin-bottom: var(--gap-large);
-}
-
-.post__navigation-list {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin: 0 1rem !important;
-
- @media (max-width: 768px) {
- flex-direction: column;
- gap: var(--gap-default);
- margin: 0 !important;
- }
-}
-
-.post__scroll-top {
- background-color: var(--bg-special);
- border-radius: var(--border-radius-max);
- box-shadow: var(--shadow-default);
- cursor: pointer;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- height: 2.5rem;
- width: 2.5rem;
- position: absolute;
- bottom: 6rem;
- right: 0;
- z-index: 1000;
-
- & svg {
- height: 2rem;
- width: 2rem;
- padding-bottom: 0.2rem;
- }
-
- /* Arrow up */
- & svg > path {
- stroke: var(--text-color);
-
- &:hover {
- stroke: var(--bg-main);
- }
- }
-
- &:hover {
- background-color: var(--link-hover);
- }
-}
-
-.post__tags {
- margin: var(--margin-padding-Y-big);
-}
-
-.post__tags-heading {
- font-weight: bold;
-}
-
-.post__tags-list {
- display: flex;
- gap: var(--gap-default);
-
- @media (max-width: 768px) {
- flex-direction: column;
- }
-}
-
-.post__tags-link {
- font-weight: normal;
-}
-
-/* List navigation */
-.pagination {
- display: flex;
- align-items: center;
- justify-content: space-between;
-}
-
-.recent-posts__view-all-link,
-.tag-page__view-all-link {
- display: block;
- margin: 0 auto;
- width: max-content;
-}
-
-.tags-index__list > li {
- margin-bottom: var(--gap-small);
-}
-
-/* Search */
-.search-form__input {
- font-size: var(--font-size-default);
- border: 1px solid var(--bg-special);
- border-radius: var(--border-radius-minimal);
- margin-right: var(--gap-small);
- padding: 0.2rem;
- height: var(--font-size-h1);
- width: 50%;
-
- &:focus {
- border-color: var(--link-color);
- box-shadow: var(--glow);
- outline: none;
- }
-
- @media (max-width: 768px) {
- width: 90%;
- }
-}
-
-.search-form__reset {
- background-color: var(--bg-special);
- border: none;
- border-radius: var(--border-radius-max);
- box-shadow: var(--shadow-default);
- color: var(--text-color);
- font-size: var(--font-size-default);
- font-weight: bold;
- padding: 0.1rem 0.5rem 0.3rem 0.5rem;
-
- &:hover {
- background-color: var(--link-hover);
- color: var(--bg-main);
- cursor: pointer;
- }
-}
-
-.search-input {
- width: 30vw;
-
- @media (max-width: 768px) {
- width: 100vw;
- }
-}
-
-.search-results__count {
- font-weight: bold;
- margin: var(--margin-padding-Y-default);
-}
diff --git a/assets/css/navmenu.css b/assets/css/navmenu.css
new file mode 100644
index 0000000..d092e6c
--- /dev/null
+++ b/assets/css/navmenu.css
@@ -0,0 +1,26 @@
+/* SITE NAVMENU */
+.header__navigation {
+ background-color: var(--bg-special);
+ border-radius: var(--border-radius-default);
+ padding: var(--margin-padding-Y-small);
+}
+
+.header__navigation-list {
+ display: flex;
+ align-items: center;
+ justify-content: space-evenly;
+
+ @media (max-width: 768px) {
+ /* Grid items stretch to at least 100px or take up full width */
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
+ justify-items: center;
+ gap: var(--gap-medium) var(--gap-small);
+ }
+}
+
+.header__navigation-link--active {
+ color: var(--text-color);
+ text-decoration: none;
+}
+
diff --git a/assets/css/page.css b/assets/css/page.css
new file mode 100644
index 0000000..daec989
--- /dev/null
+++ b/assets/css/page.css
@@ -0,0 +1,19 @@
+/* PAGES Content */
+.page-not-found {
+ text-align: center;
+}
+
+/* Homepage */
+.homepage__header {
+ text-align: center;
+}
+
+.homepage__header-image {
+ border: 4px solid var(--bg-special);
+ border-radius: var(--border-radius-max);
+ display: block;
+ margin: 1rem auto;
+ max-width: 150px;
+ max-height: 150px;
+}
+
diff --git a/assets/css/post-card.css b/assets/css/post-card.css
new file mode 100644
index 0000000..c8afb42
--- /dev/null
+++ b/assets/css/post-card.css
@@ -0,0 +1,71 @@
+/* POST CARD */
+.post-card {
+ border: 3px solid var(--bg-special);
+ border-radius: var(--border-radius-default);
+ margin: 0 auto 1.5rem;
+ padding: 0 var(--gap-default);
+ width: 90%;
+
+ & .post-card__title {
+ font-size: var(--font-size-default);
+ margin-top: var(--gap-default);
+ }
+
+ & .post-card__meta {
+ display: flex;
+ align-items: center;
+ gap: var(--gap-small);
+ }
+
+ & .post-card__meta,
+ .post-card__summary {
+ font-size: var(--font-size-small);
+ }
+
+ & .post-card__meta-info {
+ margin-bottom: 0;
+ }
+
+ @media (max-width: 768px) {
+ padding: 0 var(--gap-small);
+ }
+}
+
+.post-card__header {
+ display: flex;
+ flex-direction: column;
+ align-items: start;
+ justify-content: center;
+ gap: 0.5rem;
+ margin: var(--margin-padding-Y-small);
+}
+
+.post-card__tags-list {
+ display: flex;
+ align-items: center;
+ justify-content: start;
+ gap: var(--gap-default);
+ margin: var(--margin-padding-Y-default) !important;
+
+ @media (max-width: 768px) {
+ flex-direction: column;
+ align-items: start;
+ }
+}
+
+.post-card__tags-item {
+ background-color: var(--bg-special);
+ border-radius: var(--border-radius-max);
+ color: var(--text-color);
+ font-size: var(--font-size-small);
+ padding: 0.1rem 0.4rem;
+}
+
+.post-card__section-badge {
+ border-radius: var(--border-radius-max);
+ color: var(--link-color);
+ font-size: var(--font-size-small);
+ font-weight: bold;
+ padding: 0.1rem 0.4rem;
+}
+
diff --git a/assets/css/post.css b/assets/css/post.css
new file mode 100644
index 0000000..2c7fcd5
--- /dev/null
+++ b/assets/css/post.css
@@ -0,0 +1,88 @@
+/* Post/Article */
+.post {
+ position: relative; /* Needed for post__scroll-top */
+}
+
+.post__content ol > li,
+.post__content ul > li {
+ margin-bottom: var(--gap-small);
+}
+
+.post__header {
+ margin-bottom: var(--gap-large);
+}
+
+.post__navigation > hr {
+ height: 2px;
+ margin-bottom: var(--gap-large);
+}
+
+.post__navigation-list {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin: 0 1rem !important;
+
+ @media (max-width: 768px) {
+ flex-direction: column;
+ gap: var(--gap-default);
+ margin: 0 !important;
+ }
+}
+
+.post__scroll-top {
+ background-color: var(--bg-special);
+ border-radius: var(--border-radius-max);
+ box-shadow: var(--shadow-default);
+ cursor: pointer;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ height: 2.5rem;
+ width: 2.5rem;
+ position: absolute;
+ bottom: 6rem;
+ right: 0;
+ z-index: 1000;
+
+ & svg {
+ height: 2rem;
+ width: 2rem;
+ padding-bottom: 0.2rem;
+ }
+
+ /* Arrow up */
+ & svg > path {
+ stroke: var(--text-color);
+ }
+
+ &:hover {
+ background-color: var(--link-hover);
+
+ & svg > path {
+ stroke: var(--bg-main);
+ }
+ }
+}
+
+.post__tags {
+ margin: var(--margin-padding-Y-big);
+}
+
+.post__tags-heading {
+ font-weight: bold;
+}
+
+.post__tags-list {
+ display: flex;
+ gap: var(--gap-default);
+
+ @media (max-width: 768px) {
+ flex-direction: column;
+ }
+}
+
+.post__tags-link {
+ font-weight: normal;
+}
+
diff --git a/assets/css/search.css b/assets/css/search.css
new file mode 100644
index 0000000..7b6feab
--- /dev/null
+++ b/assets/css/search.css
@@ -0,0 +1,51 @@
+/* Search */
+.search-form__input {
+ font-size: var(--font-size-default);
+ border: 2px solid var(--bg-special);
+ border-radius: var(--border-radius-minimal);
+ margin-right: var(--gap-small);
+ padding: 0.2rem;
+ height: var(--font-size-h1);
+ width: 50%;
+
+ &:focus {
+ border-color: var(--link-color);
+ box-shadow: var(--glow);
+ outline: none;
+ }
+
+ @media (max-width: 768px) {
+ width: 90%;
+ }
+}
+
+.search-form__reset {
+ background-color: var(--bg-special);
+ border: none;
+ border-radius: var(--border-radius-max);
+ box-shadow: var(--shadow-default);
+ color: var(--text-color);
+ font-size: var(--font-size-default);
+ font-weight: bold;
+ padding: 0.1rem 0.5rem 0.3rem 0.5rem;
+
+ &:hover {
+ background-color: var(--link-hover);
+ color: var(--bg-main);
+ cursor: pointer;
+ }
+}
+
+.search-input {
+ width: 30vw;
+
+ @media (max-width: 768px) {
+ width: 100vw;
+ }
+}
+
+.search-results__count {
+ font-weight: bold;
+ margin: var(--margin-padding-Y-default);
+}
+
diff --git a/assets/js/main.js b/assets/js/main.js
index d849b4e..957c9b5 100644
--- a/assets/js/main.js
+++ b/assets/js/main.js
@@ -1,152 +1,7 @@
-(function () {
- "use strict";
-
- // TOGGLE DARK/LIGHT MODE
- function initThemeToggle() {
- const rootHtml = document.documentElement;
- const toggleThemeBtn = document.getElementById("theme-toggle");
-
- // If no saved theme, determine user preference, otherwise default to light
- const savedTheme = localStorage.getItem("theme");
- const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
- const initialTheme = savedTheme ?? (prefersDark ? "dark" : "light");
-
- function setTheme(theme) {
- const isDarkMode = theme === "dark";
- // toggleThemeBtn dataset comes with translated labels for site's language
- const label = isDarkMode ? toggleThemeBtn.dataset.labelLight : toggleThemeBtn.dataset.labelDark;
-
- rootHtml.setAttribute("data-theme", theme);
- toggleThemeBtn.setAttribute("aria-label", label); // display handled by CSS
- }
-
- // Apply initial theme
- setTheme(initialTheme);
-
- // Change theme on click and save user's choice
- toggleThemeBtn.addEventListener("click", () => {
- const newTheme = rootHtml.getAttribute("data-theme") === "dark" ? "light" : "dark";
- setTheme(newTheme);
- localStorage.setItem("theme", newTheme);
- });
- }
-
- // SEARCH
- function initSearch() {
- const input = document.getElementById("search-input");
- const resetBtn = document.getElementById("search-reset");
- const resultsCount = document.querySelector(".search-results__count");
- const resultsList = document.querySelector(".search-results__list");
- const template = document.getElementById("search-result-template");
-
- // Only initialize search on the search page
- if (!input || !template) return;
-
- let allPosts = [];
- let searchTimeout;
-
- // Get JSON file with data of all posts
- fetch(input.dataset.indexUrl ?? "/index.json")
- .then((res) => res.json())
- .then((data) => allPosts = data)
- .catch((err) => console.error("Search index.json failed to load", err));
-
- function clearResults() {
- resultsCount.hidden = true;
- resultsList.innerHTML = "";
- resultsList.hidden = true;
- }
-
- function renderSearchResults(matches) {
- clearResults();
-
- // Display how many results found for query
- resultsCount.hidden = false;
- resultsCount.querySelector("#search-results-number").textContent = String(matches.length ?? 0);
-
- // No posts matching query found
- if (!matches.length) return;
-
- // Hydrate post-card list item(s) from the template with JSON data
- matches.forEach((post) => {
- const li = template.content.firstElementChild.cloneNode(true);
-
- const link = li.querySelector(".post-card__link");
- link.href = post.url;
- link.innerHTML = post.title;
-
- const section = li.querySelector(".post-card__section-badge");
- if (section) section.textContent = post.section;
-
- const date = li.querySelector(".post-card__publish-date");
- date.textContent = new Date(post.date).toLocaleDateString();
- date.setAttribute("datetime", post.date);
-
- const summary = li.querySelector(".post-card__summary");
- if (summary) summary.textContent = post.summary;
-
- const tagsList = li.querySelector(".post-card__tags-list");
- if (tagsList) {
- tagsList.innerHTML = "";
- if (post.tags && post.tags.length) {
- post.tags.slice(0, 3).forEach((tag) => {
- const liTag = document.createElement("li");
- liTag.className = "post-card__tags-item";
- liTag.textContent = `#${tag}`;
- tagsList.appendChild(liTag);
- });
-
- if (post.tags.length > 3) {
- const more = document.createElement("li");
- more.className = "post-card__tags-item post-card__tags-more";
- more.textContent = `+${post.tags.length - 3}`;
- tagsList.appendChild(more);
- }
- }
- }
-
- resultsList.appendChild(li);
- resultsList.hidden = false;
- });
- }
-
- // Filter all posts for ones matching the user's search query
- function searchPosts(query) {
- const normalizedQuery = query.trim().toLowerCase();
-
- // Only search if user entered at least 3 chars
- if (normalizedQuery.length < 3) {
- clearResults();
- return;
- }
-
- const matches = allPosts.filter((post) => (
- post.title.toLowerCase().includes(normalizedQuery) ||
- (post.summary && post.summary.toLowerCase().includes(normalizedQuery))
- ));
-
- // At least 1 post matching query found
- renderSearchResults(matches);
- }
-
- input.addEventListener("input", (event) => {
- // Debounce search
- clearTimeout(searchTimeout);
- searchTimeout = setTimeout(() => {
- searchPosts(event.target.value);
- }, 300);
- });
-
- resetBtn.addEventListener("click", () => {
- input.value = "";
- input.focus();
- clearResults();
- });
-
- // Focus input on page load - it's what the user is here for
- input.focus();
- }
+import initThemeToggle from "./theme.js";
+import initSearch from "./search.js";
+(function () {
initThemeToggle();
initSearch();
})();
diff --git a/assets/js/search.js b/assets/js/search.js
new file mode 100644
index 0000000..f10695a
--- /dev/null
+++ b/assets/js/search.js
@@ -0,0 +1,118 @@
+/* Search Entire Site for Content */
+function initSearch() {
+ const input = document.getElementById("search-input");
+ const resetBtn = document.getElementById("search-reset");
+ const resultsCount = document.querySelector(".search-results__count");
+ const resultsList = document.querySelector(".search-results__list");
+ const template = document.getElementById("search-result-template");
+
+ // Only initialize search on the search page
+ if (!input || !template) return;
+
+ let allPosts = [];
+ let searchTimeout;
+
+ // Get JSON file with data of all posts
+ fetch(input.dataset.indexUrl ?? "/index.json")
+ .then((res) => res.json())
+ .then((data) => allPosts = data)
+ .catch((err) => console.error("Search index.json failed to load.", err));
+
+ function clearResults() {
+ resultsCount.hidden = true;
+ resultsList.innerHTML = "";
+ resultsList.hidden = true;
+ }
+
+ function renderSearchResults(matches) {
+ clearResults();
+
+ // Display how many results found for query
+ resultsCount.hidden = false;
+ resultsCount.querySelector("#search-results-number").textContent = String(matches.length ?? 0);
+
+ // No posts matching query found
+ if (!matches.length) return;
+
+ // Hydrate post-card list item(s) from the template with JSON data
+ matches.forEach((post) => {
+ const li = template.content.firstElementChild.cloneNode(true);
+
+ const link = li.querySelector(".post-card__link");
+ link.href = post.url;
+ link.innerHTML = post.title;
+
+ const section = li.querySelector(".post-card__section-badge");
+ if (section) section.textContent = post.section;
+
+ const date = li.querySelector(".post-card__publish-date");
+ date.textContent = new Date(post.date).toLocaleDateString();
+ date.setAttribute("datetime", post.date);
+
+ const summary = li.querySelector(".post-card__summary");
+ if (summary) summary.textContent = post.summary;
+
+ const tagsList = li.querySelector(".post-card__tags-list");
+ if (tagsList) {
+ tagsList.innerHTML = "";
+ if (post.tags && post.tags.length) {
+ post.tags.slice(0, 3).forEach((tag) => {
+ const liTag = document.createElement("li");
+ liTag.className = "post-card__tags-item";
+ liTag.textContent = `#${tag}`;
+ tagsList.appendChild(liTag);
+ });
+
+ if (post.tags.length > 3) {
+ const more = document.createElement("li");
+ more.className = "post-card__tags-item post-card__tags-more";
+ more.textContent = `+${post.tags.length - 3}`;
+ tagsList.appendChild(more);
+ }
+ }
+ }
+
+ resultsList.appendChild(li);
+ resultsList.hidden = false;
+ });
+ }
+
+ // Filter all posts for matches with the user's search query
+ function searchPosts(query) {
+ const normalizedQuery = query.trim().toLowerCase();
+
+ // Only search if user entered at least 3 chars
+ if (normalizedQuery.length < 3) {
+ clearResults();
+ return;
+ }
+
+ const matches = allPosts.filter((post) => (
+ post.title.toLowerCase().includes(normalizedQuery) ||
+ (post.summary && post.summary.toLowerCase().includes(normalizedQuery))
+ ));
+
+ // At least 1 post matching query found
+ renderSearchResults(matches);
+ }
+
+ input.addEventListener("input", (event) => {
+ clearTimeout(searchTimeout);
+ // Debounce search
+ searchTimeout = setTimeout(() => {
+ searchPosts(event.target.value);
+ }, 300);
+ });
+
+ resetBtn.addEventListener("click", () => {
+ input.value = "";
+ input.focus();
+ clearResults();
+ });
+
+ // Focus input on page load - it's what the user is here for
+ input.focus();
+}
+
+export default initSearch;
+
diff --git a/assets/js/theme.js b/assets/js/theme.js
new file mode 100644
index 0000000..36896c9
--- /dev/null
+++ b/assets/js/theme.js
@@ -0,0 +1,32 @@
+/* Toggle Dark/Light Mode */
+function initThemeToggle() {
+ const rootHtml = document.documentElement;
+ const toggleThemeBtn = document.getElementById("theme-toggle");
+
+ // If no saved theme, determine user preference, otherwise default to light
+ const savedTheme = localStorage.getItem("theme");
+ const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
+ const initialTheme = savedTheme ?? (prefersDark ? "dark" : "light");
+
+ function setTheme(theme) {
+ const isDarkMode = theme === "dark";
+ // toggleThemeBtn dataset comes with translated labels for site's language
+ const label = isDarkMode ? toggleThemeBtn.dataset.labelLight : toggleThemeBtn.dataset.labelDark;
+
+ rootHtml.setAttribute("data-theme", theme);
+ toggleThemeBtn.setAttribute("aria-label", label); // display handled by CSS
+ }
+
+ // Apply initial theme
+ setTheme(initialTheme);
+
+ // Change theme on click and save user's choice
+ toggleThemeBtn.addEventListener("click", () => {
+ const newTheme = rootHtml.getAttribute("data-theme") === "dark" ? "light" : "dark";
+ setTheme(newTheme);
+ localStorage.setItem("theme", newTheme);
+ });
+}
+
+export default initThemeToggle;
+
diff --git a/layouts/_default/all-posts.html b/layouts/_default/all-posts.html
index dcfe74e..4a94ab6 100644
--- a/layouts/_default/all-posts.html
+++ b/layouts/_default/all-posts.html
@@ -22,7 +22,7 @@
<ul class="all-posts__list" role="list">
{{- range $allPosts.ByDate.Reverse }}
<li class="all-posts__list-item">
- {{ partial "list/post-card.html" (dict "post" . "show_category" true) }}
+ {{ partial "list/post-card.html" (dict "post" . "show_section" true) }}
</li>
{{- end }}
</ul>
diff --git a/layouts/_default/search.html b/layouts/_default/search.html
index b501c8b..6004f2e 100644
--- a/layouts/_default/search.html
+++ b/layouts/_default/search.html
@@ -61,7 +61,7 @@
"Params" (dict "tags" (slice "TEMPLATE_TAG"))
-}}
<li class="search-results__list-item">
- {{- partial "list/post-card.html" (dict "post" $mockPost "show_category" true) }}
+ {{- partial "list/post-card.html" (dict "post" $mockPost "show_section" true) }}
</li>
</template>
diff --git a/layouts/index.json b/layouts/index.json
index 00a7048..e50a721 100644
--- a/layouts/index.json
+++ b/layouts/index.json
@@ -5,7 +5,7 @@
"title" .Title
"summary" (.Summary | plainify | htmlUnescape)
"url" .RelPermalink
- "section" (lang.translate (.Section | singularize) | default (.Section | singularize | title))
+ "section" (lang.Translate (.Section | singularize) | default (.Section | singularize | title))
"date" (.Date.Format "2006-01-02T15:04:05Z07:00")
"tags" .Params.tags
-}}
diff --git a/layouts/partials/head/css.html b/layouts/partials/head/css.html
index c3c6e33..d4e894e 100644
--- a/layouts/partials/head/css.html
+++ b/layouts/partials/head/css.html
@@ -1,9 +1,21 @@
-{{- with resources.Get "css/main.css" }}
- {{- if hugo.IsDevelopment }}
- <link rel="stylesheet" href="{{ .RelPermalink }}">
- {{- else }}
- {{- with . | minify | fingerprint }}
- <link rel="stylesheet" href="{{ .RelPermalink }}" integrity="{{ .Data.Integrity }}" crossorigin="anonymous">
- {{- end }}
- {{- end }}
+{{- with resources.Get "css/main.css" -}}
+ {{- $allStylesheets := slice
+ .
+ (resources.Get "css/header.css")
+ (resources.Get "css/navmenu.css")
+ (resources.Get "css/footer.css")
+ (resources.Get "css/post-card.css")
+ (resources.Get "css/page.css")
+ (resources.Get "css/post.css")
+ (resources.Get "css/list-navigation.css")
+ (resources.Get "css/search.css")
+ -}}
+ {{- $cssBundle := $allStylesheets | resources.Concat "css/bundle.css" -}}
+
+ {{- if hugo.IsDevelopment -}}
+ <link rel="stylesheet" href="{{ $cssBundle.RelPermalink }}">
+ {{- else -}}
+ {{- $css := $cssBundle | minify | fingerprint -}}
+ <link rel="stylesheet" href="{{ $css.RelPermalink }}" integrity="{{ $css.Data.Integrity }}" crossorigin="anonymous">
+ {{- end -}}
{{- end -}}
diff --git a/layouts/partials/list/post-card.html b/layouts/partials/list/post-card.html
index b35c724..6444701 100644
--- a/layouts/partials/list/post-card.html
+++ b/layouts/partials/list/post-card.html
@@ -1,14 +1,14 @@
{{- /*
-Card with a summary of and link to a post. Accepts a dict with the following optional parameters:
+Card with a summary of and link to a post. Accepts a dict with the following parameters:
-@context {Page} post: The post to display (default: .).
-@context {bool} show_category: Whether to display a badge with the name of the category/section the post was published under (default: true).
+@context {Page} post: The post to display.
+@context {bool} show_section: Whether to display a badge with the name of the section the post was published under (default: false).
-@example: {{ partial "list/post-card.html" (dict "post" . "show_category" true) }}
+@example: {{ partial "list/post-card.html" (dict "post" . "show_section" true) }}
*/ -}}
-{{- $post := .post }}
-{{- $showCategory := .show_category | default false }}
+{{- $post := .post -}}
+{{- $showSection := .show_section | default false -}}
<article class="post-card">
<header class="post-card__header">
<h3 class="post-card__title">
@@ -31,10 +31,9 @@ Card with a summary of and link to a post. Accepts a dict with the following opt
</span>
</p>
-->
- {{- if $showCategory }}
- {{- $section := $post.Section }}
+ {{- if $showSection }}
<span class="post-card__section-badge">
- {{ lang.Translate ($section | singularize) | default ($section | singularize | title) }}
+ {{ lang.Translate ($post.Section | singularize) | default ($post.Section | singularize | title) }}
</span>
{{- end }}
</div>
diff --git a/layouts/partials/list/recent-posts.html b/layouts/partials/list/recent-posts.html
index f42af13..464e9f7 100644
--- a/layouts/partials/list/recent-posts.html
+++ b/layouts/partials/list/recent-posts.html
@@ -24,7 +24,7 @@ List of specified number of the most recent and published posts. Accepts a dict
<ul class="recent-posts__list" role="list">
{{- range $recentPosts }}
<li class="recent-posts__list-item">
- {{- partial "list/post-card.html" (dict "post" . "show_category" true) }}
+ {{- partial "list/post-card.html" (dict "post" . "show_section" true) }}
</li>
{{- end }}
</ul>
diff --git a/layouts/tags/term.html b/layouts/tags/term.html
index 8a259c2..f4dfd73 100644
--- a/layouts/tags/term.html
+++ b/layouts/tags/term.html
@@ -20,7 +20,7 @@
<ul class="tag-page__posts-list">
{{- range . }}
<li class="tag-page__post">
- {{- partial "list/post-card.html" (dict "post" . "show_category" true) }}
+ {{- partial "list/post-card.html" (dict "post" . "show_section" true) }}
</li>
{{- end }}
</ul>