From 828c7dc3b668fbf4bda373b722fb537b004be60f Mon Sep 17 00:00:00 2001 From: haemka Date: Thu, 13 Aug 2026 14:59:41 +0200 Subject: [PATCH] Added color switching, moved scripts into dedicated files --- static/css/site.css | 147 +++++++++++++++++++++++++++++++++------- static/js/lightbox.js | 63 +++++++++++++++++ static/js/svg-inject.js | 94 +++++++++++++++++++++++++ static/js/theme-init.js | 14 ++++ static/js/toggles.js | 51 ++++++++++++++ templates/base.html | 96 +++++++------------------- 6 files changed, 371 insertions(+), 94 deletions(-) create mode 100644 static/js/lightbox.js create mode 100644 static/js/svg-inject.js create mode 100644 static/js/theme-init.js create mode 100644 static/js/toggles.js diff --git a/static/css/site.css b/static/css/site.css index dabf72e..75e0c0e 100644 --- a/static/css/site.css +++ b/static/css/site.css @@ -1,4 +1,4 @@ - @import url("pygments.css"); +@import url("pygments.css"); @import url("pygments-dark.css") (prefers-color-theme: dark); @font-face { @@ -44,9 +44,16 @@ :root { --main-bg-color: hsl(210, 20%, 98%); --main-text-color: hsl(0, 5%, 10%); - --main-link-color: hsl(0, 100%, 35%); + --main-link-color: hsl(220, 90%, 45%); + --accent-default: hsl(220, 90%, 45%); + --accent-alt: hsl(140, 45%, 28%); --table-border-color: hsl(0, 0%, 85%); --table-head-border-color: hsl(0, 0%, 70%); + --main-font: 'Latin Modern', Georgia, Cambria, 'Times New Roman', Times, serif; +} + +html[data-font="sans"] { + --main-font: 'Segoe UI', system-ui, -apple-system, Roboto, Helvetica, Arial, sans-serif; } @media (prefers-color-scheme: dark) { @@ -54,6 +61,8 @@ --main-bg-color: hsl(0, 5%, 10%); --main-text-color: hsl(210, 20%, 98%); --main-link-color: hsl(0, 100%, 65%); + --accent-default: hsl(0, 100%, 65%); + --accent-alt: hsl(55, 100%, 60%); --table-border-color: hsl(0, 0%, 15%); --table-head-border-color: hsl(0, 0%, 30%); } @@ -63,6 +72,8 @@ --main-bg-color: hsl(0, 5%, 10%); --main-text-color: hsl(210, 20%, 98%); --main-link-color: hsl(0, 100%, 65%); + --accent-default: hsl(0, 100%, 65%); + --accent-alt: hsl(55, 100%, 60%); --table-border-color: hsl(0, 0%, 15%); --table-head-border-color: hsl(0, 0%, 30%); } @@ -70,11 +81,29 @@ :root[data-theme="light"] { --main-bg-color: hsl(210, 20%, 98%); --main-text-color: hsl(0, 5%, 10%); - --main-link-color: hsl(0, 100%, 35%); + --main-link-color: hsl(220, 90%, 45%); + --accent-default: hsl(220, 90%, 45%); + --accent-alt: hsl(140, 45%, 28%); --table-border-color: hsl(0, 0%, 85%); --table-head-border-color: hsl(0, 0%, 70%); } +/* Accent-Alt-Override */ +:root[data-accent="alt"]:not([data-theme="dark"]) { + --main-link-color: hsl(140, 45%, 28%); + --main-bg-color: hsl(35, 30%, 92%); +} +@media (prefers-color-scheme: dark) { + :root[data-accent="alt"]:not([data-theme]) { + --main-link-color: hsl(55, 100%, 60%); + --main-bg-color: hsl(260, 15%, 8%); + } +} +:root[data-theme="dark"][data-accent="alt"] { + --main-link-color: hsl(55, 100%, 60%); + --main-bg-color: hsl(260, 15%, 8%); +} + *, *::before, *::after { box-sizing: border-box; } @@ -106,6 +135,7 @@ body { color: var(--main-text-color); background-color: var(--main-bg-color); text-rendering: optimizeLegibility; + font-family: var(--main-font); } header { @@ -151,6 +181,7 @@ a:not([class]) { a, a:visited { color: var(--main-link-color); + text-decoration: none; } a:focus { @@ -158,6 +189,10 @@ a:focus { outline: 2px solid hsl(220, 90%, 52%); } +a:hover, +a:focus-visible { + text-decoration: underline; +} h1 { font-size: 2.5rem; @@ -218,7 +253,8 @@ ul.menuitems { font-size: 0.9rem; } -#theme-toggle { +#theme-toggle, +#font-toggle { background: none; border: none; padding: 0; @@ -235,15 +271,42 @@ ul.menuitems { } #theme-toggle:hover, -#theme-toggle:focus-visible { +#theme-toggle:focus-visible, +#font-toggle:hover, +#font-toggle:focus-visible { opacity: 1; } -#theme-toggle:focus-visible { +#accent-toggle { + width: 0.9rem; + height: 0.9rem; + padding: 0; + border-radius: 50%; + background: linear-gradient(90deg, var(--accent-default) 50%, var(--accent-alt) 50%); + border: none; + cursor: pointer; + opacity: 0.85; + transition: opacity 0.15s ease, transform 0.15s ease; +} + +#accent-toggle:hover, +#accent-toggle:focus-visible { + opacity: 1; + transform: scale(1.1); +} + +#theme-toggle:focus-visible, +#accent-toggle:focus-visible, +#font-toggle:focus-visible { outline: 2px solid hsl(220, 90%, 52%); outline-offset: 2px; } +#font-toggle { + font-family: sans-serif; + font-size: 0.95rem; +} + #banner, #contentinfo { width: 100%; text-align: center; @@ -283,6 +346,38 @@ ul.menuitems { content: "Categories:"; } +#tag-cloud { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + list-style: none; + max-width: 300px; +} + +#tag-cloud:before { + content: "Tags:"; + display: block; + width: 100%; + font-size: 1.4rem; + line-height: 1.8rem; + margin-bottom: 0.75rem; +} + +#tag-cloud li { + margin: 0; +} + +#tag-cloud a { + display: inline; + -webkit-line-clamp: unset; + overflow: visible; +} + +#tag-cloud li.tag-weight-1 a { font-size: 0.7rem; opacity: 0.7; } +#tag-cloud li.tag-weight-2 a { font-size: 0.8rem; opacity: 0.85; } +#tag-cloud li.tag-weight-3 a { font-size: 0.9rem; opacity: 1; } +#tag-cloud li.tag-weight-4 a { font-size: 1rem; opacity: 1; font-weight: bold; } + #translations { text-align: right; } @@ -333,15 +428,9 @@ html[lang="de"] { overflow: hidden; text-overflow: ellipsis; line-height: 1.3; - text-decoration: none; padding-bottom: 1px; } -.menuitems a:hover, -.menuitems a:focus-visible { - text-decoration: underline; -} - #content { flex: 1 1 auto; } @@ -413,14 +502,6 @@ footer.post-info { color: var(--main-link-color); } -.toc a { - text-decoration: none; -} - -.toc a:hover { - text-decoration: underline; -} - /* * Headings */ @@ -514,10 +595,23 @@ footer.post-info { * Images */ -.entry-content img { +img, svg { + max-width: 100%; + height: auto; +} + +svg.injected-svg text, +svg.injected-svg tspan { + font-family: var(--main-font); +} + +.entry-content img, +.entry-content svg.injected-svg { counter-increment: figures; display: block; max-width: 80%; + width: auto; + height: auto; margin: 0 auto 0 auto; padding: 1rem; cursor: zoom-in; @@ -565,15 +659,20 @@ footer.post-info { max-height: 95vh; } -.lightbox-overlay img { +.lightbox-overlay #lightbox-media { + max-width: 95vw; + max-height: 85vh; +} + +.lightbox-overlay #lightbox-media img, +.lightbox-overlay #lightbox-media svg { max-width: 95vw; max-height: 85vh; object-fit: contain; - background: #fff; } .lightbox-overlay #lightbox-caption { - color: #111; + color: var(--main-text-color); text-align: center; margin-top: 0.75rem; font-size: 0.95rem; diff --git a/static/js/lightbox.js b/static/js/lightbox.js new file mode 100644 index 0000000..a1ad40d --- /dev/null +++ b/static/js/lightbox.js @@ -0,0 +1,63 @@ +document.addEventListener('DOMContentLoaded', function () { + const overlay = document.getElementById('lightbox-overlay'); + const mediaContainer = document.getElementById('lightbox-media'); + const overlayCaption = document.getElementById('lightbox-caption'); + + function openLightboxWithImg(src, alt, figureNumber, captionText) { + mediaContainer.innerHTML = ''; + const img = document.createElement('img'); + img.src = src; + img.alt = alt; + mediaContainer.appendChild(img); + setCaption(figureNumber, captionText); + overlay.classList.add('active'); + } + + function openLightboxWithSvg(svgEl, figureNumber, captionText) { + mediaContainer.innerHTML = ''; + const svgClone = svgEl.cloneNode(true); + mediaContainer.appendChild(svgClone); + setCaption(figureNumber, captionText); + overlay.classList.add('active'); + } + + function setCaption(figureNumber, captionText) { + overlayCaption.innerHTML = captionText + ? 'Figure ' + figureNumber + ': ' + captionText + : ''; + } + + document.querySelectorAll('.entry-content img').forEach(function (img, index) { + const figureNumber = index + 1; + const originalSrc = img.src; + const originalAlt = img.alt; + const captionEl = img.nextElementSibling; + const captionText = (captionEl && captionEl.tagName === 'EM') ? captionEl.textContent : ''; + + img.addEventListener('click', function () { + openLightboxWithImg(originalSrc, originalAlt, figureNumber, captionText); + }); + }); + + document.addEventListener('svg-injected', function (e) { + const svgEl = e.detail.svgEl; + svgEl.style.cursor = 'zoom-in'; + svgEl.addEventListener('click', function () { + openLightboxWithSvg(svgEl, e.detail.figureNumber, e.detail.captionText); + }); + }); + + overlay.addEventListener('click', function () { + overlay.classList.remove('active'); + mediaContainer.innerHTML = ''; + overlayCaption.innerHTML = ''; + }); + + document.addEventListener('keydown', function (e) { + if (e.key === 'Escape') { + overlay.classList.remove('active'); + mediaContainer.innerHTML = ''; + overlayCaption.innerHTML = ''; + } + }); +}); diff --git a/static/js/svg-inject.js b/static/js/svg-inject.js new file mode 100644 index 0000000..1842c1d --- /dev/null +++ b/static/js/svg-inject.js @@ -0,0 +1,94 @@ +document.addEventListener('DOMContentLoaded', function () { + const injectedSvgs = []; + + function parseColorMap(cssText, prop) { + const map = {}; + const regex = new RegExp('\\[' + prop + '="([^"]+)"\\]\\s*{\\s*' + prop + ':\\s*([^;]+);', 'g'); + let m; + while ((m = regex.exec(cssText)) !== null) { + map[m[1]] = m[2].trim(); + } + return map; + } + + function applyThemeToSvg(entry, isDark) { + const { svgEl, fillMap, strokeMap } = entry; + + Object.keys(fillMap).forEach(function (lightColor) { + svgEl.querySelectorAll('[fill="' + lightColor + '"]').forEach(function (el) { + el.setAttribute('fill', isDark ? fillMap[lightColor] : lightColor); + }); + }); + + Object.keys(strokeMap).forEach(function (lightColor) { + svgEl.querySelectorAll('[stroke="' + lightColor + '"]').forEach(function (el) { + el.setAttribute('stroke', isDark ? strokeMap[lightColor] : lightColor); + }); + }); + } + + function getIsDark() { + const attr = document.documentElement.getAttribute('data-theme'); + if (attr) return attr === 'dark'; + return window.matchMedia('(prefers-color-scheme: dark)').matches; + } + + function applyThemeToAll() { + const isDark = getIsDark(); + injectedSvgs.forEach(function (entry) { + applyThemeToSvg(entry, isDark); + }); + } + + const svgImages = document.querySelectorAll('.entry-content img[src$=".svg"]'); + + svgImages.forEach(function (img, index) { + const figureNumber = index + 1; + const originalSrc = img.src; + const originalAlt = img.alt; + const captionEl = img.nextElementSibling; + const captionText = (captionEl && captionEl.tagName === 'EM') ? captionEl.textContent : ''; + + fetch(img.src) + .then(function (response) { return response.text(); }) + .then(function (svgText) { + const parser = new DOMParser(); + const svgDoc = parser.parseFromString(svgText, 'image/svg+xml'); + const svgEl = svgDoc.querySelector('svg'); + + if (!svgEl) return; + + if (img.alt) { + svgEl.setAttribute('role', 'img'); + svgEl.setAttribute('aria-label', img.alt); + } + + svgEl.classList.add('injected-svg'); + + // Dark-Farbzuordnung aus dem internen