summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArne Rief <riearn@proton.me>2025-08-20 21:36:19 +0200
committerArne Rief <riearn@proton.me>2025-08-20 21:36:19 +0200
commitdac4024abcfea76f14522a84bee4b7258c37b72d (patch)
tree1aa3c2e0af122d5c0feb4360850476fd55d3831e
parentf0506acd6f70da636b8fdb23439c85bbf2392b40 (diff)
Language selection & theme toggle
-rw-r--r--assets/js/main.js38
-rw-r--r--hugo.toml6
-rw-r--r--i18n/de.toml4
-rw-r--r--i18n/en.toml4
-rw-r--r--layouts/_default/baseof.html14
-rw-r--r--layouts/partials/footer.html4
-rw-r--r--layouts/partials/head/css.html3
-rw-r--r--layouts/partials/head/js.html7
-rw-r--r--layouts/partials/header.html9
-rw-r--r--layouts/partials/list/recent-posts.html8
-rw-r--r--layouts/partials/selectLanguage.html39
-rw-r--r--layouts/partials/selectTheme.html15
-rw-r--r--layouts/partials/single/tags.html2
-rw-r--r--layouts/robots.txt4
-rw-r--r--layouts/tags/list.html2
-rw-r--r--layouts/tags/term.html2
-rw-r--r--static/flags/de.svg7
-rw-r--r--static/flags/en.svg24
-rw-r--r--static/rss.svg7
19 files changed, 180 insertions, 19 deletions
diff --git a/assets/js/main.js b/assets/js/main.js
index e69de29..c3218f1 100644
--- a/assets/js/main.js
+++ b/assets/js/main.js
@@ -0,0 +1,38 @@
+(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);
+ toggleThemeBtn.querySelector(".icon-moon").style.display = isDarkMode ? "none" : "block";
+ toggleThemeBtn.querySelector(".icon-sun").style.display = isDarkMode ? "block" : "none";
+ }
+
+ // 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);
+ });
+ }
+
+ initThemeToggle();
+})();
+
diff --git a/hugo.toml b/hugo.toml
index ae54cbe..8120249 100644
--- a/hugo.toml
+++ b/hugo.toml
@@ -2,6 +2,8 @@ baseURL = "https://example.org/"
languageCode = "en-US"
title = "Your Website Name"
+enableRobotsTXT = true # Copy this line to your project's hugo.toml
+
[frontmatter]
# {{ .Date }} will look for publishDate or aliases first, fallback to date
date = ["publishdate", "pubdate", "published", "date"]
@@ -32,8 +34,8 @@ title = "Your Website Name"
weight = 40
[params]
+ # Set logo to empty string in your project's hugo.toml if you want to display site.Title instead
+ logo = "/logo.svg"
[params.author]
name = "Your Name"
email = "your@email.com"
- # Set logo to empty string in your hugo.toml if you want to display site.Title instead
- logo = "logo.svg"
diff --git a/i18n/de.toml b/i18n/de.toml
index fb65774..bda9260 100644
--- a/i18n/de.toml
+++ b/i18n/de.toml
@@ -40,6 +40,10 @@
posts_list = "Artikel mit diesem Schlagwort:"
tagged_with = "Getaggt mit:"
+[theme]
+ toggle_dark = "Dunklen Modus aktivieren"
+ toggle_light = "Hellen Modus aktivieren"
+
[404]
title = "Seite nicht gefunden"
description = "Die angefragte Seite existiert nicht."
diff --git a/i18n/en.toml b/i18n/en.toml
index 690db3c..fbf4cb8 100644
--- a/i18n/en.toml
+++ b/i18n/en.toml
@@ -40,6 +40,10 @@
posts_list = "Posts with this tag:"
tagged_with = "Tagged with:"
+[theme]
+ toggle_dark = "Switch to dark mode"
+ toggle_light = "Switch to light mode"
+
[404]
title = "Page not found"
description = "The requested page does not exist."
diff --git a/layouts/_default/baseof.html b/layouts/_default/baseof.html
index 3c4865b..99f09d9 100644
--- a/layouts/_default/baseof.html
+++ b/layouts/_default/baseof.html
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
- <title>{{ block "title" . }}{{ if .Title }}{{ .Title }} | {{ end }}{{ site.Title }}{{ end }}</title>
+ <title>{{ block "title" . }}{{ if and (not .IsHome) .Title }}{{ .Title }} | {{ end }}{{ site.Title }}{{ end }}</title>
{{- with .Description }}
<meta name="description" content="{{ . }}">
@@ -24,13 +24,25 @@
{{- end }}
{{- end }}
+ {{- with .OutputFormats.Get "rss" }}
+ {{- printf `<link rel=%q type=%q href=%q title=%q>` .Rel .MediaType.Type .Permalink site.Title | safeHTML }}
+ {{- end }}
+
{{- partial "head/meta.html" . }}
{{- partial "head/seo.html" . }}
+ <!-- Prevent FOUC, default light mode -->
+ <script>
+ const storedTheme = localStorage.getItem("theme");
+ const prefDarkmode = window.matchMedia("(prefers-color-scheme: dark)").matches;
+ document.documentElement.setAttribute("data-theme", storedTheme ?? (prefDarkmode ? "dark" : "light"));
+ </script>
{{- partialCached "head/css.html" . }}
+ {{ partialCached "head/js.html" . }}
</head>
<body class="{{ .Type | default "page" }}">
{{ partial "header.html" . }}
+ {{ partial "navmenu.html" (dict "menuID" "main" "page" .) }}
<main id="main-content" class="site__main" role="main">
{{ block "main" . }}{{ end }}
diff --git a/layouts/partials/footer.html b/layouts/partials/footer.html
index a934e59..7225d09 100644
--- a/layouts/partials/footer.html
+++ b/layouts/partials/footer.html
@@ -1,3 +1,7 @@
<footer class="site__footer" role="contentinfo">
<p>&copy; {{ now.Year }} {{ site.Params.author.name }}</p>
+
+ <a href="{{ site.Home.Permalink }}index.xml" aria-label="RSS Feed" title="RSS Feed">
+ <img src="/rss.svg" class="icon icon-rss" style="max-height: 32px" alt="RSS Feed" aria-hidden="true">
+ </a>
</footer>
diff --git a/layouts/partials/head/css.html b/layouts/partials/head/css.html
index 8e6c145..c3c6e33 100644
--- a/layouts/partials/head/css.html
+++ b/layouts/partials/head/css.html
@@ -6,5 +6,4 @@
<link rel="stylesheet" href="{{ .RelPermalink }}" integrity="{{ .Data.Integrity }}" crossorigin="anonymous">
{{- end }}
{{- end }}
-{{- end }}
-
+{{- end -}}
diff --git a/layouts/partials/head/js.html b/layouts/partials/head/js.html
index 6123e7f..0baf831 100644
--- a/layouts/partials/head/js.html
+++ b/layouts/partials/head/js.html
@@ -6,12 +6,11 @@
}}
{{- with . | js.Build $opts }}
{{- if hugo.IsDevelopment }}
- <script src="{{ .RelPermalink }}"></script>
+ <script defer src="{{ .RelPermalink }}"></script>
{{- else }}
{{- with . | fingerprint }}
- <script src="{{ .RelPermalink }}" integrity="{{ .Data.Integrity }}" crossorigin="anonymous"></script>
+ <script defer src="{{ .RelPermalink }}" integrity="{{ .Data.Integrity }}" crossorigin="anonymous"></script>
{{- end }}
{{- end }}
{{- end }}
-{{- end }}
-
+{{- end -}}
diff --git a/layouts/partials/header.html b/layouts/partials/header.html
index 7b728a6..9195345 100644
--- a/layouts/partials/header.html
+++ b/layouts/partials/header.html
@@ -1,7 +1,12 @@
<header class="site__header" role="banner">
<a href="{{ site.Home.RelPermalink }}" rel="home" class="site-{{ if site.Params.logo }}logo{{ else }}title{{ end }}">
- {{ with site.Params.logo}}{{ . }}{{ else }}{{ site.Title }}{{end}}
+ {{- with site.Params.logo }}
+ <img src="{{ . }}" alt="{{ site.Title }}" />
+ {{- else }}
+ {{ site.Title }}
+ {{- end }}
</a>
- {{ partial "navmenu.html" (dict "menuID" "main" "page" .) }}
+ {{- partial "selectTheme.html" . }}
+ {{- partial "selectLanguage.html" . }}
</header>
diff --git a/layouts/partials/list/recent-posts.html b/layouts/partials/list/recent-posts.html
index c1075e2..d774138 100644
--- a/layouts/partials/list/recent-posts.html
+++ b/layouts/partials/list/recent-posts.html
@@ -1,5 +1,5 @@
{{- /*
-List of Recent Posts. Accepts a dict with the following optional parameters:
+List of specified number of the most recent and published posts. Accepts a dict with the following optional parameters:
@context {int} count: Number of posts to display (default: 10).
@context {string} title: Section title (default: "Recent Articles").
@@ -11,7 +11,6 @@ List of Recent Posts. Accepts a dict with the following optional parameters:
{{- $count := .count | default 10 -}}
{{- $title := .title | default (lang.Translate "posts.recent" | default "Recent Articles") -}}
{{- $showViewAll := .show_view_all | default true -}}
-
<section class="recent-posts" aria-labelledby="recent-posts-heading">
<header class="recent-posts__header">
<h2 id="recent-posts-heading" class="recent-posts__title">
@@ -19,7 +18,6 @@ List of Recent Posts. Accepts a dict with the following optional parameters:
</h2>
</header>
- {{- /* Get published 10 most recent posts */ -}}
{{- $recentPosts := where site.RegularPages "Date" "!=" nil }}
{{- $recentPosts = where $recentPosts ".Date" "le" now }}
{{- $recentPosts = first $count $recentPosts }}
@@ -35,14 +33,14 @@ List of Recent Posts. Accepts a dict with the following optional parameters:
</ul>
{{- if $showViewAll }}
- <a href="{{ with site.GetPage "/posts" }}{{ .RelPermalink }}{{ else }}/posts/{{ end }}" class="recent_posts__view-all-link">
+ <a href="{{ with site.GetPage "/posts" }}{{ .RelPermalink }}{{ else }}/posts/{{ end }}" class="recent-posts__view-all-link">
{{ lang.Translate "posts.view_all" | default "View all posts" }}
</a>
{{- end }}
</div>
{{ else }}
<div class="recent-posts__empty">
- <p class="recent_posts__empty-message">
+ <p class="recent-posts__empty-message">
{{ lang.Translate "list.empty" | default "No posts in this section." }}
</p>
</div>
diff --git a/layouts/partials/selectLanguage.html b/layouts/partials/selectLanguage.html
new file mode 100644
index 0000000..752b605
--- /dev/null
+++ b/layouts/partials/selectLanguage.html
@@ -0,0 +1,39 @@
+{{- /*
+Selection of available languages.
+Dynamically links to corresponding page/post if a translated version exists, otherwise to homepage of that language as fallback.
+
+Note: `$activeLang` and `$allTranslationsForPage` are technically unnecessary variables, their values could be called with `$.Lang` and `$.Translations` respectively.
+These variables - together with the explicit naming of `.` context variables in other cases - were chosen for better readability and understandability of the code.
+*/ -}}
+
+{{- if gt (len site.Languages) 1 }}
+{{- $activeLang := .Lang }}
+{{- $allTranslationsForPage := .Translations }}
+ <div class="language-select" style="background-color: yellow;">
+ <ul>
+ {{- range site.Languages }}
+ {{- $currLangSlice := . }}
+ {{- if ne $currLangSlice.Lang $activeLang }}
+ {{- $targetPage := index (where site.Home.Translations "Lang" $currLangSlice.Lang) 0 }}
+ {{- $translatedPage := index (where $allTranslationsForPage "Lang" $currLangSlice.Lang) 0 }}
+ {{- if $translatedPage }}{{- $targetPage = $translatedPage }}{{- end }}
+ <li>
+ <a
+ href="{{ $targetPage.RelPermalink }}"
+ hreflang="{{ $targetPage.Lang }}"
+ aria-label="{{ $targetPage.LinkTitle }} ({{ or $targetPage.Language.LanguageName $targetPage.Lang }})"
+ >
+ <img
+ src="/flags/{{ $targetPage.Lang }}.svg"
+ class="icon icon-flag"
+ style="max-height: 32px"
+ alt="{{ $targetPage.LinkTitle }} ({{ or $targetPage.Language.LanguageName $targetPage.Lang }})"
+ aria-hidden="true"
+ />
+ </a>
+ </li>
+ {{- end }}
+ {{- end }}
+ </ul>
+ </div>
+{{- end }}
diff --git a/layouts/partials/selectTheme.html b/layouts/partials/selectTheme.html
new file mode 100644
index 0000000..e9114cc
--- /dev/null
+++ b/layouts/partials/selectTheme.html
@@ -0,0 +1,15 @@
+<button
+ type="button"
+ id="theme-toggle"
+ class="theme-toggle"
+ aria-label="{{ lang.Translate "theme.toggle_dark" }}"
+ data-label-dark="{{ lang.Translate "theme.toggle_dark" }}"
+ data-label-light="{{ lang.Translate "theme.toggle_light" }}"
+>
+ <svg class="icon icon-moon" xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 16 16" aria-hidden="true" style="display: none;">
+ <path fill="currentColor" d="M6 .278a.768.768 0 0 1 .08.858a7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277c.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316a.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71C0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z"></path>
+ </svg>
+ <svg class="icon icon-sun" xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 16 16" aria-hidden="true">
+ <path fill="currentColor" d="M8 12a4 4 0 1 0 0-8a4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z"/>
+ </svg>
+</button>
diff --git a/layouts/partials/single/tags.html b/layouts/partials/single/tags.html
index e90b909..f9c8105 100644
--- a/layouts/partials/single/tags.html
+++ b/layouts/partials/single/tags.html
@@ -7,7 +7,7 @@
<ul class="post__tags-list">
{{- range . }}
<li class="post__tags-item">
- <a href="{{ .RelPermalink }}" class="post__tags-link" rel="tag">
+ <a href="{{ .RelPermalink }}" class="post__tags-link" rel="tag">
{{ .Title }}
</a>
</li>
diff --git a/layouts/robots.txt b/layouts/robots.txt
new file mode 100644
index 0000000..fd9192c
--- /dev/null
+++ b/layouts/robots.txt
@@ -0,0 +1,4 @@
+User-agent: *
+Disallow:
+
+Sitemap: {{ site.BaseURL }}/sitemap.xml
diff --git a/layouts/tags/list.html b/layouts/tags/list.html
index d3af0e4..bfe9479 100644
--- a/layouts/tags/list.html
+++ b/layouts/tags/list.html
@@ -12,7 +12,7 @@
<section class="tags-index__content" aria-label="{{ lang.Translate "tags.all" | default "All Tags" }}">
{{- with site.Taxonomies.tags }}
- {{ $tags := .Alphabetical }}
+ {{- $tags := .Alphabetical }}
<ul class="tags-index__list">
{{- range $tags }}
<li class="tags-index__item">
diff --git a/layouts/tags/term.html b/layouts/tags/term.html
index e107123..1e0dba7 100644
--- a/layouts/tags/term.html
+++ b/layouts/tags/term.html
@@ -33,7 +33,7 @@
<footer class="tag-page__footer">
<nav class="tag-page__navigation" aria-label="{{ lang.Translate "tags.navigation" | default "Tag navigation" }}">
- <a href="{{ "/tags/" | relURL }}" class="tag-page__back-link">
+ <a href="{{ .Parent.RelPermalink }}" class="tag-page__list-all-link">
← {{ lang.Translate "tags.all_tags" | default "All tags" }}
</a>
</nav>
diff --git a/static/flags/de.svg b/static/flags/de.svg
new file mode 100644
index 0000000..9fc1a16
--- /dev/null
+++ b/static/flags/de.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Source: SVG Repo, https://www.svgrepo.com/svg/405490/flag-for-flag-germany -->
+<svg width="800px" height="800px" viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--twemoji" preserveAspectRatio="xMidYMid meet">
+ <path fill="#FFCD05" d="M0 27a4 4 0 0 0 4 4h28a4 4 0 0 0 4-4v-4H0v4z"></path>
+ <path fill="#ED1F24" d="M0 14h36v9H0z"></path>
+ <path fill="#141414" d="M32 5H4a4 4 0 0 0-4 4v5h36V9a4 4 0 0 0-4-4z"></path>
+</svg>
diff --git a/static/flags/en.svg b/static/flags/en.svg
new file mode 100644
index 0000000..0ea496a
--- /dev/null
+++ b/static/flags/en.svg
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Source: SVG Repo, https://www.svgrepo.com/svg/508511/flag-gb -->
+<svg width="800px" height="800px" viewBox="0 -4 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
+ <g clip-path="url(#clip0_503_2952)">
+ <rect width="28" height="20" rx="2" fill="white"/>
+ <mask id="mask0_503_2952" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="28" height="20">
+ <rect width="28" height="20" rx="2" fill="white"/>
+ </mask>
+ <g mask="url(#mask0_503_2952)">
+ <rect width="28" height="20" fill="#0A17A7"/>
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M-1.28244 -1.91644L10.6667 6.14335V-1.33333H17.3334V6.14335L29.2825 -1.91644L30.7737 0.294324L21.3263 6.66667H28V13.3333H21.3263L30.7737 19.7057L29.2825 21.9165L17.3334 13.8567V21.3333H10.6667V13.8567L-1.28244 21.9165L-2.77362 19.7057L6.67377 13.3333H2.95639e-05V6.66667H6.67377L-2.77362 0.294324L-1.28244 -1.91644Z" fill="white"/>
+ <path d="M18.668 6.33219L31.3333 -2" stroke="#DB1F35" stroke-width="0.666667" stroke-linecap="round"/>
+ <path d="M20.0128 13.6975L31.3666 21.3503" stroke="#DB1F35" stroke-width="0.666667" stroke-linecap="round"/>
+ <path d="M8.00555 6.31046L-3.83746 -1.67099" stroke="#DB1F35" stroke-width="0.666667" stroke-linecap="round"/>
+ <path d="M9.29006 13.6049L-3.83746 22.3105" stroke="#DB1F35" stroke-width="0.666667" stroke-linecap="round"/>
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M0 12H12V20H16V12H28V8H16V0H12V8H0V12Z" fill="#E6273E"/>
+ </g>
+ </g>
+ <defs>
+ <clipPath id="clip0_503_2952">
+ <rect width="28" height="20" rx="2" fill="white"/>
+ </clipPath>
+ </defs>
+</svg>
diff --git a/static/rss.svg b/static/rss.svg
new file mode 100644
index 0000000..9dbdb73
--- /dev/null
+++ b/static/rss.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Source: SVG Repo, https://www.svgrepo.com/svg/349492/rss -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+ <rect width="512" height="512" rx="15%" fill="#f80"/>
+ <circle cx="145" cy="367" r="35" fill="#ffffff"/>
+ <path fill="none" stroke="#ffffff" stroke-width="60" d="M109 241c89 0 162 73 162 162m114 0c0-152-124-276-276-276"/>
+</svg>