diff --git a/assets/sw.js b/assets/sw.js
new file mode 100644
index 0000000..3ece6f3
--- /dev/null
+++ b/assets/sw.js
@@ -0,0 +1,242 @@
+const CACHE_VERSION = 1;
+
+const BASE_CACHE_FILES = ["/site.webmanifest"];
+
+const OFFLINE_CACHE_FILES = [
+ //"/images/logo.png",
+];
+
+const NOT_FOUND_CACHE_FILES = ["/404.html"];
+
+const OFFLINE_PAGE = "/offline/index.html";
+const NOT_FOUND_PAGE = "/404.html";
+
+const CACHE_VERSIONS = {
+ assets: "assets-v" + CACHE_VERSION,
+ content: "content-v" + CACHE_VERSION,
+ offline: "offline-v" + CACHE_VERSION,
+ notFound: "404-v" + CACHE_VERSION,
+};
+
+// Define MAX_TTL's in SECONDS for specific file extensions
+const MAX_TTL = {
+ "/": 3600,
+ html: 3600,
+ json: 86400,
+ js: 86400,
+ css: 86400,
+};
+
+const CACHE_BLACKLIST = [
+ (str) => {
+ return !str.startsWith("http://localhost");
+ },
+];
+
+const SUPPORTED_METHODS = ["GET"];
+
+/**
+ * isBlackListed
+ * @param {string} url
+ * @returns {boolean}
+ */
+function isBlacklisted(url) {
+ return CACHE_BLACKLIST.length > 0
+ ? !CACHE_BLACKLIST.filter((rule) => {
+ if (typeof rule === "function") {
+ return !rule(url);
+ } else {
+ return false;
+ }
+ }).length
+ : false;
+}
+
+/**
+ * getFileExtension
+ * @param {string} url
+ * @returns {string}
+ */
+function getFileExtension(url) {
+ let extension = url.split(".").reverse()[0].split("?")[0];
+ return extension.endsWith("/") ? "/" : extension;
+}
+
+/**
+ * getTTL
+ * @param {string} url
+ */
+function getTTL(url) {
+ if (typeof url === "string") {
+ let extension = getFileExtension(url);
+ if (typeof MAX_TTL[extension] === "number") {
+ return MAX_TTL[extension];
+ } else {
+ return null;
+ }
+ } else {
+ return null;
+ }
+}
+
+/**
+ * installServiceWorker
+ * @returns {Promise}
+ */
+function installServiceWorker() {
+ return Promise.all([
+ caches.open(CACHE_VERSIONS.assets).then((cache) => {
+ return cache.addAll(BASE_CACHE_FILES);
+ }),
+ caches.open(CACHE_VERSIONS.offline).then((cache) => {
+ return cache.addAll(OFFLINE_CACHE_FILES);
+ }),
+ caches.open(CACHE_VERSIONS.notFound).then((cache) => {
+ return cache.addAll(NOT_FOUND_CACHE_FILES);
+ }),
+ ]);
+}
+
+/**
+ * cleanupLegacyCache
+ * @returns {Promise}
+ */
+function cleanupLegacyCache() {
+ let currentCaches = Object.keys(CACHE_VERSIONS).map((key) => {
+ return CACHE_VERSIONS[key];
+ });
+
+ return new Promise((resolve, reject) => {
+ caches
+ .keys()
+ .then((keys) => {
+ return (legacyKeys = keys.filter((key) => {
+ return !~currentCaches.indexOf(key);
+ }));
+ })
+ .then((legacy) => {
+ if (legacy.length) {
+ Promise.all(
+ legacy.map((legacyKey) => {
+ return caches.delete(legacyKey);
+ })
+ )
+ .then(() => {
+ resolve();
+ })
+ .catch((err) => {
+ reject(err);
+ });
+ } else {
+ resolve();
+ }
+ })
+ .catch(() => {
+ reject();
+ });
+ });
+}
+
+self.addEventListener("install", (event) => {
+ event.waitUntil(installServiceWorker());
+});
+
+// The activate handler takes care of cleaning up old caches.
+self.addEventListener("activate", (event) => {
+ event.waitUntil(
+ Promise.all([cleanupLegacyCache()]).catch((err) => {
+ event.skipWaiting();
+ })
+ );
+});
+
+self.addEventListener("fetch", (event) => {
+ event.respondWith(
+ caches.open(CACHE_VERSIONS.content).then((cache) => {
+ return cache
+ .match(event.request)
+ .then((response) => {
+ if (response) {
+ let headers = response.headers.entries();
+ let date = null;
+
+ for (let pair of headers) {
+ if (pair[0] === "date") {
+ date = new Date(pair[1]);
+ }
+ }
+
+ if (date) {
+ let age = parseInt(
+ (new Date().getTime() - date.getTime()) / 1000
+ );
+ let ttl = getTTL(event.request.url);
+
+ if (ttl && age > ttl) {
+ return new Promise((resolve) => {
+ return fetch(event.request)
+ .then((updatedResponse) => {
+ if (updatedResponse) {
+ cache.put(event.request, updatedResponse.clone());
+ resolve(updatedResponse);
+ } else {
+ resolve(response);
+ }
+ })
+ .catch(() => {
+ resolve(response);
+ });
+ }).catch((err) => {
+ return response;
+ });
+ } else {
+ return response;
+ }
+ } else {
+ return response;
+ }
+ } else {
+ return null;
+ }
+ })
+ .then((response) => {
+ if (response) {
+ return response;
+ } else {
+ return fetch(event.request)
+ .then((response) => {
+ if (response.status < 400) {
+ if (
+ ~SUPPORTED_METHODS.indexOf(event.request.method) &&
+ !isBlacklisted(event.request.url)
+ ) {
+ cache.put(event.request, response.clone());
+ }
+ return response;
+ } else {
+ return caches.open(CACHE_VERSIONS.notFound).then((cache) => {
+ return cache.match(NOT_FOUND_PAGE);
+ });
+ }
+ })
+ .then((response) => {
+ if (response) {
+ return response;
+ }
+ })
+ .catch(() => {
+ return caches
+ .open(CACHE_VERSIONS.offline)
+ .then((offlineCache) => {
+ return offlineCache.match(OFFLINE_PAGE);
+ });
+ });
+ }
+ })
+ .catch((error) => {
+ console.error(" Error in fetch handler:", error);
+ throw error;
+ });
+ })
+ );
+});
diff --git a/layouts/partials/footer.html b/layouts/partials/footer.html
new file mode 100644
index 0000000..d143880
--- /dev/null
+++ b/layouts/partials/footer.html
@@ -0,0 +1,207 @@
+{{/* variables for enabling/disabling parts of the footer */}}
+{{ $footerEnabled := site.Params.footer.enable | default true }}
+{{ $navigationEnabled := site.Params.footer.navigation.enable | default true }}
+{{ $customMenusEnabled := site.Params.footer.navigation.customMenus | default true }}
+{{ $contactMeEnabled := site.Params.footer.contactMe.enable | default true }}
+{{ $newsletterEnabled := site.Params.footer.newsletter.enable | default true }}
+{{ $credentialsEnabled := site.Params.footer.credentials.enable | default true }}
+{{ $disclaimerEnabled := site.Params.footer.disclaimer.enable | default false }}
+
+{{/* Keep backward compatibility for the newsletter function */}}
+{{ if site.Params.newsletter }}
+ {{ if site.Params.newsletter.enable }}
+ {{ $newsletterEnabled = true }}
+ {{ else }}
+ {{ $newsletterEnabled = false }}
+ {{ end }}
+{{ end }}
+
+{{ if $footerEnabled }}
+ {{ $author:= site.Data.author }}
+ {{ if (index site.Data site.Language.Lang).author }}
+ {{ $author = (index site.Data site.Language.Lang).author }}
+ {{ end }}
+
+ {{ $sections:= site.Data.sections }}
+ {{ if (index site.Data site.Language.Lang).sections }}
+ {{ $sections = (index site.Data site.Language.Lang).sections }}
+ {{ end }}
+
+ {{ $customMenus := site.Params.customMenus }}
+ {{ if (index site.Data site.Language.Lang).site.customMenus }}
+ {{ $customMenus = (index site.Data site.Language.Lang).site.customMenus }}
+ {{ end }}
+
+ {{ $copyrightNotice := now.Format "2006" | printf "© %s Copyright."}}
+ {{ if (index site.Data site.Language.Lang).site }}
+ {{ $siteConfig := (index site.Data site.Language.Lang).site }}
+ {{ if $siteConfig.copyright }}
+ {{ $copyrightNotice = $siteConfig.copyright }}
+ {{ end }}
+ {{ end }}
+
+
+ {{ $disclaimer := "" }}
+ {{ $siteConfig := (index site.Data site.Language.Lang).site }}
+ {{ if $siteConfig.disclaimer }}
+ {{ $disclaimer = $siteConfig.disclaimer }}
+ {{ end }}
+
+ {{/* footer logos */}}
+ {{ $themeLogo := "/images/theme-logo.png" }}
+ {{ $hugoLogo := "/images/hugo-logo.svg" }}
+
+ {{/* resize the logos. don't resize svg because it is not supported */}}
+ {{ $themeLogo:= resources.Get $themeLogo}}
+ {{ if and $themeLogo (ne $themeLogo.MediaType.SubType "svg") }}
+ {{ $themeLogo = $themeLogo.Resize "32x" }}
+ {{ end }}
+ {{ $themeLogo = $themeLogo.RelPermalink}}
+
+ {{ $hugoLogo:= resources.Get $hugoLogo}}
+ {{ if and $hugoLogo (ne $hugoLogo.MediaType.SubType "svg")}}
+ {{ $hugoLogo = $hugoLogo.Resize "32x" }}
+ {{ end }}
+ {{ $hugoLogo = $hugoLogo.RelPermalink}}
+
+
+{{end}}
diff --git a/layouts/partials/header.html b/layouts/partials/header.html
new file mode 100644
index 0000000..583b57b
--- /dev/null
+++ b/layouts/partials/header.html
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+{{ if site.Params.darkMode.enable }} {{ end }}
+
+
+{{/* add favicon only if the site author has provided the the favicon */}} {{ if
+site.Params.logo.favicon }} {{ $favicon := site.Params.logo.favicon }} {{/*
+resize the favicon. don't resize svg because it is not supported */}} {{
+$favicon = resources.Get $favicon }} {{ if and $favicon (ne
+$favicon.MediaType.SubType "svg") }} {{ $favicon = $favicon.Resize "42x" }} {{
+end }} {{ $favicon = $favicon.RelPermalink}}
+
+
+{{end}}