From e40cfb523a97c372b055069a3c4058b2b93766d5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=C3=A9=20Fischer?=
Date: Fri, 31 Oct 2025 00:15:45 +0100
Subject: [PATCH 001/140] added tinylitics
---
layouts/partials/footer.html | 1 +
1 file changed, 1 insertion(+)
diff --git a/layouts/partials/footer.html b/layouts/partials/footer.html
index e0992a5..26d8e37 100644
--- a/layouts/partials/footer.html
+++ b/layouts/partials/footer.html
@@ -8,6 +8,7 @@
+
\ No newline at end of file
From ad82888b7ca3394437a327b7575248fba42a161e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=C3=A9=20Fischer?=
Date: Fri, 31 Oct 2025 00:18:12 +0100
Subject: [PATCH 002/140] added tinylitcs kudos
---
layouts/post/single.html | 1 +
1 file changed, 1 insertion(+)
diff --git a/layouts/post/single.html b/layouts/post/single.html
index de1933a..6d10e03 100644
--- a/layouts/post/single.html
+++ b/layouts/post/single.html
@@ -23,6 +23,7 @@ {{ .Title }}
+
{{ .Content }}
From d0e0b5d427c43c6c3c7c7dd8cd59d0db3bf38eb7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=C3=A9=20Fischer?=
Date: Fri, 31 Oct 2025 00:23:56 +0100
Subject: [PATCH 003/140] tinylitics fixes
---
layouts/partials/footer.html | 2 +-
layouts/post/single.html | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/layouts/partials/footer.html b/layouts/partials/footer.html
index 26d8e37..5ab1bbb 100644
--- a/layouts/partials/footer.html
+++ b/layouts/partials/footer.html
@@ -8,7 +8,7 @@
-
+
\ No newline at end of file
diff --git a/layouts/post/single.html b/layouts/post/single.html
index 6d10e03..bc73208 100644
--- a/layouts/post/single.html
+++ b/layouts/post/single.html
@@ -23,7 +23,7 @@ {{ .Title }}
-
+
{{ .Content }}
From 1d0c037972d69d848c71c9a161783aee4682b13c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=C3=A9=20Fischer?=
Date: Fri, 31 Oct 2025 00:26:34 +0100
Subject: [PATCH 004/140] fixes
---
layouts/partials/footer.html | 2 +-
layouts/post/single.html | 3 +--
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/layouts/partials/footer.html b/layouts/partials/footer.html
index 5ab1bbb..9ba3479 100644
--- a/layouts/partials/footer.html
+++ b/layouts/partials/footer.html
@@ -8,7 +8,7 @@
-
+
\ No newline at end of file
diff --git a/layouts/post/single.html b/layouts/post/single.html
index bc73208..7916fa5 100644
--- a/layouts/post/single.html
+++ b/layouts/post/single.html
@@ -23,8 +23,7 @@ {{ .Title }}
-
- {{ .Content }}
+ {{ .Content }}
{{ if templates.Exists "partials/microhook-categories.html" }}
From 5af480e582ebc59d9affa7519a641e6c2df73e97 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=C3=A9=20Fischer?=
Date: Fri, 31 Oct 2025 00:29:11 +0100
Subject: [PATCH 005/140] kudos button
---
layouts/post/single.html | 2 --
1 file changed, 2 deletions(-)
diff --git a/layouts/post/single.html b/layouts/post/single.html
index 7916fa5..4ebc7f0 100644
--- a/layouts/post/single.html
+++ b/layouts/post/single.html
@@ -47,9 +47,7 @@ {{ .Title }}
{{ if or (templates.Exists "partials/reply-by-email.html") (templates.Exists "partials/conversation-link.html") (templates.Exists "partials/plugin_tinylytics.html") (templates.Exists "partials/reply-on-mastodon.html") (templates.Exists "partials/microhook-share-button.html")}}
-
+
+ async src="/js/count.js">
diff --git a/static/count.js b/static/count.js
new file mode 100644
index 0000000..b40052d
--- /dev/null
+++ b/static/count.js
@@ -0,0 +1,267 @@
+// GoatCounter: https://www.goatcounter.com
+// This file is released under the ISC license: https://opensource.org/licenses/ISC
+;(function() {
+ 'use strict';
+
+ window.goatcounter = window.goatcounter || {}
+
+ // Load settings from data-goatcounter-settings.
+ var s = document.querySelector('script[data-goatcounter]')
+ if (s && s.dataset.goatcounterSettings) {
+ try { var set = JSON.parse(s.dataset.goatcounterSettings) }
+ catch (err) { console.error('invalid JSON in data-goatcounter-settings: ' + err) }
+ for (var k in set)
+ if (['no_onload', 'no_events', 'allow_local', 'allow_frame', 'path', 'title', 'referrer', 'event'].indexOf(k) > -1)
+ window.goatcounter[k] = set[k]
+ }
+
+ var enc = encodeURIComponent
+
+ // Get all data we're going to send off to the counter endpoint.
+ window.goatcounter.get_data = function(vars) {
+ vars = vars || {}
+ var data = {
+ p: (vars.path === undefined ? goatcounter.path : vars.path),
+ r: (vars.referrer === undefined ? goatcounter.referrer : vars.referrer),
+ t: (vars.title === undefined ? goatcounter.title : vars.title),
+ e: !!(vars.event || goatcounter.event),
+ s: window.screen.width,
+ b: is_bot(),
+ q: location.search,
+ }
+
+ var rcb, pcb, tcb // Save callbacks to apply later.
+ if (typeof(data.r) === 'function') rcb = data.r
+ if (typeof(data.t) === 'function') tcb = data.t
+ if (typeof(data.p) === 'function') pcb = data.p
+
+ if (is_empty(data.r)) data.r = document.referrer
+ if (is_empty(data.t)) data.t = document.title
+ if (is_empty(data.p)) data.p = get_path()
+
+ if (rcb) data.r = rcb(data.r)
+ if (tcb) data.t = tcb(data.t)
+ if (pcb) data.p = pcb(data.p)
+ return data
+ }
+
+ // Check if a value is "empty" for the purpose of get_data().
+ var is_empty = function(v) { return v === null || v === undefined || typeof(v) === 'function' }
+
+ // See if this looks like a bot; there is some additional filtering on the
+ // backend, but these properties can't be fetched from there.
+ var is_bot = function() {
+ // Headless browsers are probably a bot.
+ var w = window, d = document
+ if (w.callPhantom || w._phantom || w.phantom)
+ return 150
+ if (w.__nightmare)
+ return 151
+ if (d.__selenium_unwrapped || d.__webdriver_evaluate || d.__driver_evaluate)
+ return 152
+ if (navigator.webdriver)
+ return 153
+ return 0
+ }
+
+ // Object to urlencoded string, starting with a ?.
+ var urlencode = function(obj) {
+ var p = []
+ for (var k in obj)
+ if (obj[k] !== '' && obj[k] !== null && obj[k] !== undefined && obj[k] !== false)
+ p.push(enc(k) + '=' + enc(obj[k]))
+ return '?' + p.join('&')
+ }
+
+ // Show a warning in the console.
+ var warn = function(msg) {
+ if (console && 'warn' in console)
+ console.warn('goatcounter: ' + msg)
+ }
+
+ // Get the endpoint to send requests to.
+ var get_endpoint = function() {
+ var s = document.querySelector('script[data-goatcounter]')
+ return (s && s.dataset.goatcounter) ? s.dataset.goatcounter : goatcounter.endpoint
+ }
+
+ // Get current path.
+ var get_path = function() {
+ var loc = location,
+ c = document.querySelector('link[rel="canonical"][href]')
+ if (c) { // May be relative or point to different domain.
+ var a = document.createElement('a')
+ a.href = c.href
+ if (a.hostname.replace(/^www\./, '') === location.hostname.replace(/^www\./, ''))
+ loc = a
+ }
+ return (loc.pathname + loc.search) || '/'
+ }
+
+ // Run function after DOM is loaded.
+ var on_load = function(f) {
+ if (document.body === null)
+ document.addEventListener('DOMContentLoaded', function() { f() }, false)
+ else
+ f()
+ }
+
+ // Filter some requests that we (probably) don't want to count.
+ window.goatcounter.filter = function() {
+ if ('visibilityState' in document && document.visibilityState === 'prerender')
+ return 'visibilityState'
+ if (!goatcounter.allow_frame && location !== parent.location)
+ return 'frame'
+ if (!goatcounter.allow_local && location.hostname.match(/(localhost$|^127\.|^10\.|^172\.(1[6-9]|2[0-9]|3[0-1])\.|^192\.168\.|^0\.0\.0\.0$)/))
+ return 'localhost'
+ if (!goatcounter.allow_local && location.protocol === 'file:')
+ return 'localfile'
+ if (localStorage && localStorage.getItem('skipgc') === 't')
+ return 'disabled with #toggle-goatcounter'
+ return false
+ }
+
+ // Get URL to send to GoatCounter.
+ window.goatcounter.url = function(vars) {
+ var data = window.goatcounter.get_data(vars || {})
+ if (data.p === null) // null from user callback.
+ return
+ data.rnd = Math.random().toString(36).substr(2, 5) // Browsers don't always listen to Cache-Control.
+
+ var endpoint = get_endpoint()
+ if (!endpoint)
+ return warn('no endpoint found')
+
+ return endpoint + urlencode(data)
+ }
+
+ // Count a hit.
+ window.goatcounter.count = function(vars) {
+ var f = goatcounter.filter()
+ if (f)
+ return warn('not counting because of: ' + f)
+ var url = goatcounter.url(vars)
+ if (!url)
+ return warn('not counting because path callback returned null')
+
+ if (!navigator.sendBeacon(url)) {
+ // This mostly fails due to being blocked by CSP; try again with an
+ // image-based fallback.
+ var img = document.createElement('img')
+ img.src = url
+ img.style.position = 'absolute' // Affect layout less.
+ img.style.bottom = '0px'
+ img.style.width = '1px'
+ img.style.height = '1px'
+ img.loading = 'eager'
+ img.setAttribute('alt', '')
+ img.setAttribute('aria-hidden', 'true')
+
+ var rm = function() { if (img && img.parentNode) img.parentNode.removeChild(img) }
+ img.addEventListener('load', rm, false)
+ document.body.appendChild(img)
+ }
+ }
+
+ // Get a query parameter.
+ window.goatcounter.get_query = function(name) {
+ var s = location.search.substr(1).split('&')
+ for (var i = 0; i < s.length; i++)
+ if (s[i].toLowerCase().indexOf(name.toLowerCase() + '=') === 0)
+ return s[i].substr(name.length + 1)
+ }
+
+ // Track click events.
+ window.goatcounter.bind_events = function() {
+ if (!document.querySelectorAll) // Just in case someone uses an ancient browser.
+ return
+
+ var send = function(elem) {
+ return function() {
+ goatcounter.count({
+ event: true,
+ path: (elem.dataset.goatcounterClick || elem.name || elem.id || ''),
+ title: (elem.dataset.goatcounterTitle || elem.title || (elem.innerHTML || '').substr(0, 200) || ''),
+ referrer: (elem.dataset.goatcounterReferrer || elem.dataset.goatcounterReferral || ''),
+ })
+ }
+ }
+
+ Array.prototype.slice.call(document.querySelectorAll("*[data-goatcounter-click]")).forEach(function(elem) {
+ if (elem.dataset.goatcounterBound)
+ return
+ var f = send(elem)
+ elem.addEventListener('click', f, false)
+ elem.addEventListener('auxclick', f, false) // Middle click.
+ elem.dataset.goatcounterBound = 'true'
+ })
+ }
+
+ // Add a "visitor counter" frame or image.
+ window.goatcounter.visit_count = function(opt) {
+ on_load(function() {
+ opt = opt || {}
+ opt.type = opt.type || 'html'
+ opt.append = opt.append || 'body'
+ opt.path = opt.path || get_path()
+ opt.attr = opt.attr || {width: '200', height: (opt.no_branding ? '60' : '80')}
+
+ opt.attr['src'] = get_endpoint() + 'er/' + enc(opt.path) + '.' + enc(opt.type) + '?'
+ if (opt.no_branding) opt.attr['src'] += '&no_branding=1'
+ if (opt.style) opt.attr['src'] += '&style=' + enc(opt.style)
+ if (opt.start) opt.attr['src'] += '&start=' + enc(opt.start)
+ if (opt.end) opt.attr['src'] += '&end=' + enc(opt.end)
+
+ var tag = {png: 'img', svg: 'img', html: 'iframe'}[opt.type]
+ if (!tag)
+ return warn('visit_count: unknown type: ' + opt.type)
+
+ if (opt.type === 'html') {
+ opt.attr['frameborder'] = '0'
+ opt.attr['scrolling'] = 'no'
+ }
+
+ var d = document.createElement(tag)
+ for (var k in opt.attr)
+ d.setAttribute(k, opt.attr[k])
+
+ var p = document.querySelector(opt.append)
+ if (!p)
+ return warn('visit_count: element to append to not found: ' + opt.append)
+ p.appendChild(d)
+ })
+ }
+
+ // Make it easy to skip your own views.
+ if (location.hash === '#toggle-goatcounter') {
+ if (localStorage.getItem('skipgc') === 't') {
+ localStorage.removeItem('skipgc', 't')
+ alert('GoatCounter tracking is now ENABLED in this browser.')
+ }
+ else {
+ localStorage.setItem('skipgc', 't')
+ alert('GoatCounter tracking is now DISABLED in this browser until ' + location + ' is loaded again.')
+ }
+ }
+
+ if (!goatcounter.no_onload)
+ on_load(function() {
+ // 1. Page is visible, count request.
+ // 2. Page is not yet visible; wait until it switches to 'visible' and count.
+ // See #487
+ if (!('visibilityState' in document) || document.visibilityState === 'visible')
+ goatcounter.count()
+ else {
+ var f = function(e) {
+ if (document.visibilityState !== 'visible')
+ return
+ document.removeEventListener('visibilitychange', f)
+ goatcounter.count()
+ }
+ document.addEventListener('visibilitychange', f)
+ }
+
+ if (!goatcounter.no_events)
+ goatcounter.bind_events()
+ })
+})();
From 8f996b1ad2bf43a6981c46fee08c5943a1167d13 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=C3=A9=20Fischer?=
Date: Fri, 31 Oct 2025 21:42:32 +0100
Subject: [PATCH 019/140] goatcounter fix
---
static/{ => js}/count.js | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename static/{ => js}/count.js (100%)
diff --git a/static/count.js b/static/js/count.js
similarity index 100%
rename from static/count.js
rename to static/js/count.js
From b00e149cfc8587fba756a362f14d1b9807f3e6c7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=C3=A9=20Fischer?=
Date: Fri, 31 Oct 2025 21:47:12 +0100
Subject: [PATCH 020/140] remove other analytics
---
layouts/partials/footer.html | 4 ----
1 file changed, 4 deletions(-)
diff --git a/layouts/partials/footer.html b/layouts/partials/footer.html
index 77614d1..8e40a12 100644
--- a/layouts/partials/footer.html
+++ b/layouts/partials/footer.html
@@ -13,8 +13,4 @@
-
-
-
-
\ No newline at end of file
From d21aab5f7eb2f477dfd6f163a86ad6a0cf6fe696 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=C3=A9=20Fischer?=
Date: Fri, 31 Oct 2025 21:47:38 +0100
Subject: [PATCH 021/140] remove Kudos
---
layouts/post/single.html | 2 --
1 file changed, 2 deletions(-)
diff --git a/layouts/post/single.html b/layouts/post/single.html
index 5d2fa5d..b86b06c 100644
--- a/layouts/post/single.html
+++ b/layouts/post/single.html
@@ -26,8 +26,6 @@ {{ .Title }}
{{ .Content }}
-
-
-
+
-
+
{{ $pages := sort (where .Site.RegularPages "Type" "in" (slice "post" "photo")) "Date" "desc" }}
@@ -176,66 +221,30 @@
{{ range $p := $pages }}
{{ $dateFormatted := $p.Date.Format "2. January 2006" }}
+ {{ $dateISO := $p.Date.Format "2006-01-02" }}
{{ $year := $p.Date.Format "2006" }}
{{ $overlayCaption := "" }}
{{ with $p.Title }}{{ $overlayCaption = . | plainify | truncate 80 }}{{ end }}
{{ with $p.Params.photos }}
{{ range $i, $url := . }}
- {{ $caption := $overlayCaption }}
{{ $eager := lt $imageIndex 12 }}
{{ $isMBPhotos := or (hasPrefix $url "https://micro.blog/photos/") (hasPrefix $url "http://micro.blog/photos/") }}
- {{ $src300 := "" }}{{ $src600 := "" }}{{ $srcFull := "" }}
+ {{ $src300 := "" }}{{ $src600 := "" }}{{ $srcFull := "" }}{{ $tiny := "" }}
{{ if $isMBPhotos }}
{{ $src300 = replaceRE `/photos/[^/]+/` "/photos/200x/" $url }}
{{ $src600 = replaceRE `/photos/[^/]+/` "/photos/400x/" $url }}
+ {{ $tiny = replaceRE `/photos/[^/]+/` "/photos/40x/" $url }}
{{ $srcFull = $url }}
{{ else }}
{{ $encoded := $url | urlquery }}
{{ $src300 = printf "https://micro.blog/photos/200x/%s" $encoded }}
{{ $src600 = printf "https://micro.blog/photos/400x/%s" $encoded }}
+ {{ $tiny = printf "https://micro.blog/photos/40x/%s" $encoded }}
{{ $srcFull = $url }}
{{ end }}
-
- {{ if $eager }}
-

- {{ else }}
- {{ $tiny := "" }}
- {{ if $isMBPhotos }}
- {{ $tiny = replaceRE `/photos/[^/]+/` "/photos/40x/" $url }}
- {{ else }}
- {{ $encoded2 := $url | urlquery }}
- {{ $tiny = printf "https://micro.blog/photos/40x/%s" $encoded2 }}
- {{ end }}
-

- {{ end }}
-
-
-
+ {{ partial "photo-tile.html" (dict "srcFull" $srcFull "src300" $src300 "src600" $src600 "tiny" $tiny "caption" $overlayCaption "dateFormatted" $dateFormatted "dateISO" $dateISO "year" $year "permalink" $p.RelPermalink "index" $imageIndex "eager" $eager) }}
{{ $imageIndex = add $imageIndex 1 }}
{{ end }}
{{ end }}
@@ -246,42 +255,10 @@
{{ if not (or (hasPrefix $name "hero") (in $name "og-") (in $name "open-graph") (in $name "social") (in $name "thumb")) }}
{{ $w300 := $img.Fit "200x200 q85" }}
{{ $w600 := $img.Fit "400x400 q85" }}
- {{ $caption := $overlayCaption }}
+ {{ $w40 := $img.Fit "40x40 q50" }}
{{ $eager := lt $imageIndex 12 }}
-
- {{ if $eager }}
-

- {{ else }}
- {{ $w40 := $img.Fit "40x40 q50" }}
-

- {{ end }}
-
-
-
+ {{ partial "photo-tile.html" (dict "srcFull" $img.RelPermalink "src300" $w300.RelPermalink "src600" $w600.RelPermalink "tiny" $w40.RelPermalink "caption" $overlayCaption "dateFormatted" $dateFormatted "dateISO" $dateISO "year" $year "permalink" $p.RelPermalink "index" $imageIndex "eager" $eager) }}
{{ $imageIndex = add $imageIndex 1 }}
{{ end }}
{{ end }}
@@ -294,6 +271,22 @@
diff --git a/layouts/partials/photo-tile.html b/layouts/partials/photo-tile.html
index c1658a2..e937e12 100644
--- a/layouts/partials/photo-tile.html
+++ b/layouts/partials/photo-tile.html
@@ -37,7 +37,6 @@
-
+
\ No newline at end of file
From b353d630db572600def65ec2595fb6ff1c4d4030 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=C3=A9=20Fischer?=
Date: Sun, 2 Nov 2025 01:12:26 +0100
Subject: [PATCH 030/140] gallery tweaks
---
layouts/_default/list.photoshtml.html | 34 ++++-----------------------
layouts/partials/photo-tile.html | 4 +++-
2 files changed, 7 insertions(+), 31 deletions(-)
diff --git a/layouts/_default/list.photoshtml.html b/layouts/_default/list.photoshtml.html
index dd509fd..3657c9c 100644
--- a/layouts/_default/list.photoshtml.html
+++ b/layouts/_default/list.photoshtml.html
@@ -276,10 +276,9 @@
// ====== Performance-Konstanten ======
const INITIAL_LOAD_COUNT = 36;
const BATCH_SIZE = 36;
- const PREFETCH_AHEAD = 24;
- const INTERSECTION_ROOT_MARGIN = '1000px 0px';
- const SENTINEL_ROOT_MARGIN = '1200px 0px';
- const NEAR_BOTTOM_THRESHOLD = 900;
+ const INTERSECTION_ROOT_MARGIN = '300px 0px';
+ const SENTINEL_ROOT_MARGIN = '400px 0px';
+ const NEAR_BOTTOM_THRESHOLD = 400;
const SCROLL_THROTTLE_MS = 180;
const MOBILE_SCROLL_DEBOUNCE_MS = 160;
const SCROLL_IDLE_UNLOCK_MS = 250;
@@ -315,7 +314,6 @@
end: Math.min(allTiles.length, INITIAL_LOAD_COUNT)
};
const batchSize = BATCH_SIZE;
- const prefetchAhead= PREFETCH_AHEAD;
// requestIdleCallback Polyfill/Wrapper
const scheduleIdleTask = window.requestIdleCallback || ((cb) => setTimeout(cb, 1));
@@ -373,29 +371,11 @@
.forEach(img => { img.closest('.photo-tile')?.classList.add('ready'); });
}
- let prefetchIdleTask = null;
- function prefetchNext(){
- if (prefetchIdleTask) cancelIdleTask(prefetchIdleTask);
- prefetchIdleTask = scheduleIdleTask(() => {
- const start = visibleRange.end;
- const end = Math.min(allTiles.length, visibleRange.end + prefetchAhead);
- for (let i = start; i < end; i++) {
- const img = allTiles[i]?.querySelector('img');
- if (img && img.dataset && !img.dataset.activated) {
- if (img.dataset.sizes && !img.sizes) img.sizes = img.dataset.sizes;
- if (img.dataset.srcset && !img.srcset) img.srcset = img.dataset.srcset;
- }
- }
- prefetchIdleTask = null;
- });
- }
-
function loadMore() {
const before = visibleRange.end;
visibleRange.end = Math.min(allTiles.length, visibleRange.end + batchSize);
if (visibleRange.end === before) return;
applyVisibility();
- prefetchNext();
syncURLToCurrentYear();
}
@@ -546,7 +526,6 @@
}
await new Promise(r => requestAnimationFrame(r));
- prefetchNext();
const anchor = allTiles[index];
if (anchor && !anchor.classList.contains('hidden')) {
@@ -576,10 +555,6 @@
function activateImg(img){
if (!img || img.dataset.activated) return;
- // *** WICHTIG: Lazy-Blocker entfernen, sofort laden ***
- img.removeAttribute('loading');
- img.loading = 'eager';
-
if (img.dataset.sizes && !img.sizes) img.sizes = img.dataset.sizes;
if (img.dataset.srcset && !img.srcset) { img.srcset = img.dataset.srcset; delete img.dataset.srcset; }
if (img.dataset.src) { img.src = img.dataset.src; delete img.dataset.src; }
@@ -898,7 +873,6 @@
scheduleIdleTask(() => {
hydrateDateBadges();
- prefetchNext();
syncURLToCurrentYear();
});
@@ -918,4 +892,4 @@
})();
-{{ end }}
\ No newline at end of file
+{{ end }}
diff --git a/layouts/partials/photo-tile.html b/layouts/partials/photo-tile.html
index e937e12..004d726 100644
--- a/layouts/partials/photo-tile.html
+++ b/layouts/partials/photo-tile.html
@@ -28,6 +28,7 @@
{{ if .eager }}
-
\ No newline at end of file
+
From ea0d8ef5a6a652eb7638e71a626b8c119ec4e4bb Mon Sep 17 00:00:00 2001
From: Claude
Date: Sun, 2 Nov 2025 00:16:57 +0000
Subject: [PATCH 031/140] Rebuild photo gallery in Instagram style with modern
lazy loading
- Simplified from ~900 to ~80 lines of JavaScript
- Removed complex virtual scroll logic
- Instagram-style grid with small gaps (2-4px)
- Modern Intersection Observer for lazy loading
- LQIP (Low Quality Image Placeholder) with blur effect
- Smooth transitions when images load
- First 12 images eager loaded for performance
- Images preload 300px before entering viewport
- Maintained accessibility and responsive design
- Cleaner, more maintainable codebase
---
layouts/_default/list.photoshtml.html | 1066 ++++++-------------------
1 file changed, 252 insertions(+), 814 deletions(-)
diff --git a/layouts/_default/list.photoshtml.html b/layouts/_default/list.photoshtml.html
index 3657c9c..c99f92a 100644
--- a/layouts/_default/list.photoshtml.html
+++ b/layouts/_default/list.photoshtml.html
@@ -1,894 +1,332 @@
{{ define "main" }}
-
-
-
-
-
-
-
-
- {{ $pages := sort (where .Site.RegularPages "Type" "in" (slice "post" "photo")) "Date" "desc" }}
- {{ $imageIndex := 0 }}
-
- {{ range $p := $pages }}
- {{ $dateFormatted := $p.Date.Format "2. January 2006" }}
- {{ $dateISO := $p.Date.Format "2006-01-02" }}
- {{ $year := $p.Date.Format "2006" }}
- {{ $overlayCaption := "" }}
- {{ with $p.Title }}{{ $overlayCaption = . | plainify | truncate 80 }}{{ end }}
-
- {{ with $p.Params.photos }}
- {{ range $i, $url := . }}
- {{ $eager := lt $imageIndex 12 }}
- {{ $isMBPhotos := or (hasPrefix $url "https://micro.blog/photos/") (hasPrefix $url "http://micro.blog/photos/") }}
- {{ $src300 := "" }}{{ $src600 := "" }}{{ $srcFull := "" }}{{ $tiny := "" }}
- {{ if $isMBPhotos }}
- {{ $src300 = replaceRE `/photos/[^/]+/` "/photos/200x/" $url }}
- {{ $src600 = replaceRE `/photos/[^/]+/` "/photos/400x/" $url }}
- {{ $tiny = replaceRE `/photos/[^/]+/` "/photos/40x/" $url }}
- {{ $srcFull = $url }}
+
+ {{ $pages := sort (where .Site.RegularPages "Type" "in" (slice "post" "photo")) "Date" "desc" }}
+ {{ $imageIndex := 0 }}
+
+ {{ range $p := $pages }}
+ {{ $dateFormatted := $p.Date.Format "2. January 2006" }}
+ {{ $dateISO := $p.Date.Format "2006-01-02" }}
+ {{ $year := $p.Date.Format "2006" }}
+
+ {{ with $p.Params.photos }}
+ {{ range $i, $url := . }}
+ {{ $eager := lt $imageIndex 12 }}
+ {{ $isMBPhotos := or (hasPrefix $url "https://micro.blog/photos/") (hasPrefix $url "http://micro.blog/photos/") }}
+ {{ $src300 := "" }}{{ $src600 := "" }}{{ $tiny := "" }}
+
+ {{ if $isMBPhotos }}
+ {{ $src300 = replaceRE `/photos/[^/]+/` "/photos/200x/" $url }}
+ {{ $src600 = replaceRE `/photos/[^/]+/` "/photos/400x/" $url }}
+ {{ $tiny = replaceRE `/photos/[^/]+/` "/photos/40x/" $url }}
+ {{ else }}
+ {{ $encoded := $url | urlquery }}
+ {{ $src300 = printf "https://micro.blog/photos/200x/%s" $encoded }}
+ {{ $src600 = printf "https://micro.blog/photos/400x/%s" $encoded }}
+ {{ $tiny = printf "https://micro.blog/photos/40x/%s" $encoded }}
+ {{ end }}
+
+
+ {{ if $eager }}
+
{{ else }}
- {{ $encoded := $url | urlquery }}
- {{ $src300 = printf "https://micro.blog/photos/200x/%s" $encoded }}
- {{ $src600 = printf "https://micro.blog/photos/400x/%s" $encoded }}
- {{ $tiny = printf "https://micro.blog/photos/40x/%s" $encoded }}
- {{ $srcFull = $url }}
+
{{ end }}
+ {{ $dateFormatted }}
+
- {{ partial "photo-tile.html" (dict "srcFull" $srcFull "src300" $src300 "src600" $src600 "tiny" $tiny "caption" $overlayCaption "dateFormatted" $dateFormatted "dateISO" $dateISO "year" $year "permalink" $p.RelPermalink "index" $imageIndex "eager" $eager) }}
- {{ $imageIndex = add $imageIndex 1 }}
- {{ end }}
+ {{ $imageIndex = add $imageIndex 1 }}
{{ end }}
+ {{ end }}
- {{ $imgs := $p.Resources.ByType "image" }}
- {{ range $img := $imgs }}
- {{ $name := $img.Name | lower }}
- {{ if not (or (hasPrefix $name "hero") (in $name "og-") (in $name "open-graph") (in $name "social") (in $name "thumb")) }}
- {{ $w300 := $img.Fit "200x200 q85" }}
- {{ $w600 := $img.Fit "400x400 q85" }}
- {{ $w40 := $img.Fit "40x40 q50" }}
- {{ $eager := lt $imageIndex 12 }}
-
- {{ partial "photo-tile.html" (dict "srcFull" $img.RelPermalink "src300" $w300.RelPermalink "src600" $w600.RelPermalink "tiny" $w40.RelPermalink "caption" $overlayCaption "dateFormatted" $dateFormatted "dateISO" $dateISO "year" $year "permalink" $p.RelPermalink "index" $imageIndex "eager" $eager) }}
- {{ $imageIndex = add $imageIndex 1 }}
- {{ end }}
+ {{ $imgs := $p.Resources.ByType "image" }}
+ {{ range $img := $imgs }}
+ {{ $name := $img.Name | lower }}
+ {{ if not (or (hasPrefix $name "hero") (in $name "og-") (in $name "open-graph") (in $name "social") (in $name "thumb")) }}
+ {{ $w300 := $img.Fit "200x200 q85" }}
+ {{ $w600 := $img.Fit "400x400 q85" }}
+ {{ $w40 := $img.Fit "40x40 q50" }}
+ {{ $eager := lt $imageIndex 12 }}
+
+
+ {{ if $eager }}
+
+ {{ else }}
+
+ {{ end }}
+ {{ $dateFormatted }}
+
+
+ {{ $imageIndex = add $imageIndex 1 }}
{{ end }}
{{ end }}
-
+ {{ end }}
-
-
From 91a642a07f28765c0be2d55a488fb2f26a48983c Mon Sep 17 00:00:00 2001
From: Claude
Date: Sun, 2 Nov 2025 00:22:55 +0000
Subject: [PATCH 032/140] Fix viewport-based image loading priority
- Remove native loading="lazy" that conflicted with IntersectionObserver
- Implement priority queue based on viewport distance
- Load up to 6 images in parallel instead of sequentially
- Prioritize images in viewport (distance = 0) over others
- Add initial load for images within 600px on page load
- Re-prioritize queue on scroll for visible images
- Images now load based on visibility, not DOM order
---
layouts/_default/list.photoshtml.html | 152 ++++++++++++++++++--------
1 file changed, 104 insertions(+), 48 deletions(-)
diff --git a/layouts/_default/list.photoshtml.html b/layouts/_default/list.photoshtml.html
index c99f92a..281bf37 100644
--- a/layouts/_default/list.photoshtml.html
+++ b/layouts/_default/list.photoshtml.html
@@ -191,7 +191,6 @@
data-srcset="{{ $src300 }} 200w, {{ $src600 }} 400w"
sizes="(max-width: 768px) 50vw, 33vw"
alt="{{ $dateFormatted }}"
- loading="lazy"
decoding="async"
class="lqip">
{{ end }}
@@ -232,7 +231,6 @@
data-srcset="{{ $w300.RelPermalink }} 200w, {{ $w600.RelPermalink }} 400w"
sizes="(max-width: 768px) 50vw, 33vw"
alt="{{ $dateFormatted }}"
- loading="lazy"
decoding="async"
class="lqip">
{{ end }}
@@ -246,84 +244,142 @@
+
+
@@ -256,103 +269,131 @@
(function() {
'use strict';
- const grid = document.querySelector('.photo-grid');
+ const grid = document.getElementById('photoGrid');
if (!grid) return;
- // Masonry initialisieren
- const msnry = new Masonry(grid, {
+ // Masonry Konfiguration
+ const masonryOptions = {
itemSelector: '.photo-tile',
columnWidth: '.photo-tile',
- gutter: parseInt(getComputedStyle(document.documentElement).getPropertyValue('--gap')) || 8,
+ gutter: parseInt(getComputedStyle(document.documentElement).getPropertyValue('--gap')) || 16,
percentPosition: false,
transitionDuration: '0.3s',
- initLayout: true
- });
+ horizontalOrder: false,
+ initLayout: false // Wir triggern das Layout manuell nach Bildladen
+ };
- // Lazy Loading fĂŒr Bilder auĂerhalb des Viewports
- const lazyImages = document.querySelectorAll('img.lqip[data-src]');
+ // Masonry initialisieren
+ const masonry = new Masonry(grid, masonryOptions);
+
+ // Lazy Loading Setup
let loadingCount = 0;
- const maxConcurrent = 6;
+ const maxConcurrent = 8; // Max gleichzeitig ladende Bilder
+ /**
+ * LĂ€dt ein einzelnes Bild mit LQIP-Effekt
+ */
function loadImage(img) {
- if (img.dataset.loading || !img.dataset.src) return;
+ if (img.dataset.loading || img.classList.contains('loaded')) return;
img.dataset.loading = 'true';
loadingCount++;
const src = img.dataset.src;
const srcset = img.dataset.srcset;
- const tile = img.closest('.photo-tile');
- const fullImg = new Image();
- fullImg.onload = () => {
+ // Neues Image Objekt zum Vorladen
+ const tempImg = new Image();
+
+ tempImg.onload = () => {
+ // Bild ist geladen - jetzt austauschen
img.src = src;
if (srcset) img.srcset = srcset;
img.classList.remove('lqip');
img.classList.add('loaded');
+ // Cleanup
delete img.dataset.src;
delete img.dataset.srcset;
delete img.dataset.loading;
-
loadingCount--;
- // Masonry Layout nach Bildladen aktualisieren
+ // Masonry Layout aktualisieren
+ const tile = img.closest('.photo-tile');
if (tile) {
- imagesLoaded(tile, function() {
- msnry.layout();
- });
+ imagesLoaded(tile, () => masonry.layout());
}
};
- fullImg.onerror = () => {
+ tempImg.onerror = () => {
+ console.warn('Fehler beim Laden des Bildes:', src);
delete img.dataset.loading;
loadingCount--;
};
- if (srcset) fullImg.srcset = srcset;
- fullImg.src = src;
+ // Start loading
+ if (srcset) tempImg.srcset = srcset;
+ tempImg.src = src;
}
- // IntersectionObserver fĂŒr Lazy Loading
- const imageObserver = new IntersectionObserver((entries) => {
+ /**
+ * IntersectionObserver fĂŒr performantes Lazy Loading
+ */
+ const lazyImageObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting && loadingCount < maxConcurrent) {
const img = entry.target;
- if (img.dataset.src && !img.dataset.loading) {
+ if (img.dataset.src) {
loadImage(img);
+ lazyImageObserver.unobserve(img); // Nicht mehr beobachten
}
}
});
}, {
- rootMargin: '200px 0px',
+ rootMargin: '300px 0px', // 300px vor dem Sichtbereich laden
threshold: 0.01
});
- // Beobachte alle lazy Images
- lazyImages.forEach(img => imageObserver.observe(img));
+ // Alle lazy Images registrieren
+ const lazyImages = grid.querySelectorAll('img[data-src]');
+ lazyImages.forEach(img => lazyImageObserver.observe(img));
- // Initial Layout nach dem Laden aller eager Bilder
- imagesLoaded(grid, function() {
- msnry.layout();
+ /**
+ * Initial Layout nach eager Images
+ */
+ imagesLoaded(grid, { background: true }, () => {
+ masonry.layout();
});
- // Layout bei FenstergröĂenĂ€nderung aktualisieren
+ /**
+ * Responsive Handling - Layout bei Resize neu berechnen
+ */
let resizeTimeout;
- window.addEventListener('resize', function() {
+ window.addEventListener('resize', () => {
clearTimeout(resizeTimeout);
- resizeTimeout = setTimeout(function() {
- // Gutter neu berechnen
- const newGutter = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--gap')) || 8;
- msnry.options.gutter = newGutter;
- msnry.layout();
+ resizeTimeout = setTimeout(() => {
+ // Gutter neu berechnen fĂŒr responsive Breakpoints
+ const newGutter = parseInt(
+ getComputedStyle(document.documentElement).getPropertyValue('--gap')
+ ) || 16;
+ masonry.options.gutter = newGutter;
+ masonry.layout();
}, 250);
});
- // Cleanup
+ /**
+ * Cleanup bei Page Unload
+ */
window.addEventListener('beforeunload', () => {
- imageObserver.disconnect();
+ lazyImageObserver.disconnect();
+ masonry.destroy();
});
+
+ // Debug Info (optional - kann entfernt werden)
+ console.info('đž Photo Gallery initialized with Masonry.js');
+ console.info(' Items:', grid.querySelectorAll('.photo-tile').length);
+ console.info(' Eager loaded:', grid.querySelectorAll('img.loaded').length);
+ console.info(' Lazy loading:', lazyImages.length);
})();
From d6b63a014e6d0117494e81a3ef87d0c98f832cf2 Mon Sep 17 00:00:00 2001
From: Claude
Date: Sun, 2 Nov 2025 00:55:15 +0000
Subject: [PATCH 036/140] Simplify photo gallery with Instagram-style grid and
native lazy loading
Replace complex Masonry.js layout with clean CSS Grid implementation:
- Use CSS Grid for Instagram-style square tiles (3 columns, 2 on mobile)
- Replace IntersectionObserver with native loading="lazy" attribute
- Remove Masonry.js and imagesloaded.js dependencies (~100KB saved)
- Add month grouping headers for better organization
- Keep hover effects and date badges
- Reduce code from ~400 to ~270 lines
Benefits:
- Faster page loads (no external JS libraries)
- Better performance (browser-native lazy loading)
- Simpler maintenance and easier to customize
- More consistent Instagram-like appearance
---
layouts/_default/list.photoshtml.html | 394 +++++++++-----------------
1 file changed, 131 insertions(+), 263 deletions(-)
diff --git a/layouts/_default/list.photoshtml.html b/layouts/_default/list.photoshtml.html
index 6bc20b6..79badd1 100644
--- a/layouts/_default/list.photoshtml.html
+++ b/layouts/_default/list.photoshtml.html
@@ -3,8 +3,7 @@
-
- {{ $pages := sort (where .Site.RegularPages "Type" "in" (slice "post" "photo")) "Date" "desc" }}
- {{ $imageIndex := 0 }}
-
- {{ range $p := $pages }}
- {{ $dateFormatted := $p.Date.Format "2. January 2006" }}
- {{ $dateISO := $p.Date.Format "2006-01-02" }}
- {{ $year := $p.Date.Format "2006" }}
-
- {{/* Micro.blog Photos */}}
- {{ with $p.Params.photos }}
- {{ range $i, $url := . }}
- {{ $eager := lt $imageIndex 8 }}
- {{ $isMBPhotos := or (hasPrefix $url "https://micro.blog/photos/") (hasPrefix $url "http://micro.blog/photos/") }}
- {{ $src300 := "" }}{{ $src600 := "" }}{{ $tiny := "" }}
-
- {{ if $isMBPhotos }}
- {{ $src300 = replaceRE `/photos/[^/]+/` "/photos/300w/" $url }}
- {{ $src600 = replaceRE `/photos/[^/]+/` "/photos/600w/" $url }}
- {{ $tiny = replaceRE `/photos/[^/]+/` "/photos/40w/" $url }}
- {{ else }}
- {{ $encoded := $url | urlquery }}
- {{ $src300 = printf "https://micro.blog/photos/300w/%s" $encoded }}
- {{ $src600 = printf "https://micro.blog/photos/600w/%s" $encoded }}
- {{ $tiny = printf "https://micro.blog/photos/40w/%s" $encoded }}
- {{ end }}
+ {{ $pages := sort (where .Site.RegularPages "Type" "in" (slice "post" "photo")) "Date" "desc" }}
+ {{ $.Scratch.Set "currentMonth" "" }}
+ {{ $imageIndex := 0 }}
+
+ {{ range $p := $pages }}
+ {{ $dateFormatted := $p.Date.Format "2. January 2006" }}
+ {{ $dateISO := $p.Date.Format "2006-01-02" }}
+ {{ $year := $p.Date.Format "2006" }}
+ {{ $monthYear := $p.Date.Format "January 2006" }}
+
+ {{/* Micro.blog Photos */}}
+ {{ with $p.Params.photos }}
+ {{ range $i, $url := . }}
+ {{ $eager := lt $imageIndex 8 }}
+ {{ $isMBPhotos := or (hasPrefix $url "https://micro.blog/photos/") (hasPrefix $url "http://micro.blog/photos/") }}
+ {{ $src300 := "" }}{{ $src600 := "" }}
+
+ {{ if $isMBPhotos }}
+ {{ $src300 = replaceRE `/photos/[^/]+/` "/photos/300w/" $url }}
+ {{ $src600 = replaceRE `/photos/[^/]+/` "/photos/600w/" $url }}
+ {{ else }}
+ {{ $encoded := $url | urlquery }}
+ {{ $src300 = printf "https://micro.blog/photos/300w/%s" $encoded }}
+ {{ $src600 = printf "https://micro.blog/photos/600w/%s" $encoded }}
+ {{ end }}
-
- {{ if $eager }}
-
- {{ else }}
-
- {{ end }}
- {{ $dateFormatted }}
-
-
- {{ $imageIndex = add $imageIndex 1 }}
+ {{/* Check if we need to start a new month section */}}
+ {{ if ne $monthYear ($.Scratch.Get "currentMonth") }}
+ {{ if ne ($.Scratch.Get "currentMonth") "" }}
+
{{/* Close previous grid */}}
+ {{ end }}
+
+
+ {{ $.Scratch.Set "currentMonth" $monthYear }}
{{ end }}
+
+
+
+ {{ $dateFormatted }}
+
+
+ {{ $imageIndex = add $imageIndex 1 }}
{{ end }}
+ {{ end }}
- {{/* Page Bundle Images */}}
- {{ $imgs := $p.Resources.ByType "image" }}
- {{ range $img := $imgs }}
- {{ $name := $img.Name | lower }}
- {{ if not (or (hasPrefix $name "hero") (in $name "og-") (in $name "open-graph") (in $name "social") (in $name "thumb")) }}
- {{ $w300 := $img.Resize "300x q85" }}
- {{ $w600 := $img.Resize "600x q85" }}
- {{ $w40 := $img.Resize "40x q50" }}
- {{ $eager := lt $imageIndex 8 }}
-
-
- {{ if $eager }}
-
- {{ else }}
-
- {{ end }}
- {{ $dateFormatted }}
-
-
- {{ $imageIndex = add $imageIndex 1 }}
+ {{/* Page Bundle Images */}}
+ {{ $imgs := $p.Resources.ByType "image" }}
+ {{ range $img := $imgs }}
+ {{ $name := $img.Name | lower }}
+ {{ if not (or (hasPrefix $name "hero") (in $name "og-") (in $name "open-graph") (in $name "social") (in $name "thumb")) }}
+ {{ $w300 := $img.Resize "300x q85" }}
+ {{ $w600 := $img.Resize "600x q85" }}
+ {{ $eager := lt $imageIndex 8 }}
+
+ {{/* Check if we need to start a new month section */}}
+ {{ if ne $monthYear ($.Scratch.Get "currentMonth") }}
+ {{ if ne ($.Scratch.Get "currentMonth") "" }}
+
{{/* Close previous grid */}}
+ {{ end }}
+
+
+ {{ $.Scratch.Set "currentMonth" $monthYear }}
{{ end }}
+
+
+
+ {{ $dateFormatted }}
+
+
+ {{ $imageIndex = add $imageIndex 1 }}
{{ end }}
{{ end }}
-
-
-
-
-
-
-
{{ end }}
From 56f4975f80150a4629e6fa3d1a2532a0496f7aa2 Mon Sep 17 00:00:00 2001
From: Claude
Date: Sun, 2 Nov 2025 00:59:25 +0000
Subject: [PATCH 037/140] Fix image preview URLs in photo gallery
Fixed the URL generation for Micro.blog photos in the gallery:
- Corrected regex syntax from backticks to quotes
- Updated pattern to specifically match numeric size values
- Fixed target URL format from /photos/300w/ to /photos/300/
- Removed problematic URL encoding for external images
This resolves the issue where images were not loading and only
alt text placeholders with dates were visible.
---
layouts/_default/list.photoshtml.html | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/layouts/_default/list.photoshtml.html b/layouts/_default/list.photoshtml.html
index 79badd1..6f5b823 100644
--- a/layouts/_default/list.photoshtml.html
+++ b/layouts/_default/list.photoshtml.html
@@ -187,12 +187,11 @@
{{ $src300 := "" }}{{ $src600 := "" }}
{{ if $isMBPhotos }}
- {{ $src300 = replaceRE `/photos/[^/]+/` "/photos/300w/" $url }}
- {{ $src600 = replaceRE `/photos/[^/]+/` "/photos/600w/" $url }}
+ {{ $src300 = replaceRE "/photos/[0-9]+/" "/photos/300/" $url }}
+ {{ $src600 = replaceRE "/photos/[0-9]+/" "/photos/600/" $url }}
{{ else }}
- {{ $encoded := $url | urlquery }}
- {{ $src300 = printf "https://micro.blog/photos/300w/%s" $encoded }}
- {{ $src600 = printf "https://micro.blog/photos/600w/%s" $encoded }}
+ {{ $src300 = $url }}
+ {{ $src600 = $url }}
{{ end }}
{{/* Check if we need to start a new month section */}}
From 2c799a2a70fe333ec89d9144c084391189043b9e Mon Sep 17 00:00:00 2001
From: Claude
Date: Sun, 2 Nov 2025 01:03:48 +0000
Subject: [PATCH 038/140] Optimize photo gallery: smaller tiles, 4 columns,
left-aligned
- Change grid from 3 to 4 columns on desktop
- Reduce image sizes from 300px/600px to 200px/400px for better performance
- Update sizes attribute from 33vw to 25vw for 4-column layout
- Left-align gallery (remove centered margin)
---
layouts/_default/list.photoshtml.html | 30 +++++++++++++--------------
1 file changed, 15 insertions(+), 15 deletions(-)
diff --git a/layouts/_default/list.photoshtml.html b/layouts/_default/list.photoshtml.html
index 6f5b823..284149c 100644
--- a/layouts/_default/list.photoshtml.html
+++ b/layouts/_default/list.photoshtml.html
@@ -25,7 +25,7 @@
/* Main Container */
.photos.content {
max-width: var(--max-width);
- margin: 0 auto;
+ margin: 0;
padding: 20px;
overscroll-behavior-y: contain;
-webkit-tap-highlight-color: transparent;
@@ -47,7 +47,7 @@
/* Instagram-Style Grid */
.photos-grid {
display: grid;
- grid-template-columns: repeat(3, 1fr);
+ grid-template-columns: repeat(4, 1fr);
grid-gap: var(--gap);
margin-bottom: 2rem;
}
@@ -184,14 +184,14 @@
{{ range $i, $url := . }}
{{ $eager := lt $imageIndex 8 }}
{{ $isMBPhotos := or (hasPrefix $url "https://micro.blog/photos/") (hasPrefix $url "http://micro.blog/photos/") }}
- {{ $src300 := "" }}{{ $src600 := "" }}
+ {{ $src200 := "" }}{{ $src400 := "" }}
{{ if $isMBPhotos }}
- {{ $src300 = replaceRE "/photos/[0-9]+/" "/photos/300/" $url }}
- {{ $src600 = replaceRE "/photos/[0-9]+/" "/photos/600/" $url }}
+ {{ $src200 = replaceRE "/photos/[0-9]+/" "/photos/200/" $url }}
+ {{ $src400 = replaceRE "/photos/[0-9]+/" "/photos/400/" $url }}
{{ else }}
- {{ $src300 = $url }}
- {{ $src600 = $url }}
+ {{ $src200 = $url }}
+ {{ $src400 = $url }}
{{ end }}
{{/* Check if we need to start a new month section */}}
@@ -208,9 +208,9 @@
href="{{ $p.RelPermalink }}"
aria-label="Foto vom {{ $dateFormatted }}">
@@ -226,8 +226,8 @@
{{ range $img := $imgs }}
{{ $name := $img.Name | lower }}
{{ if not (or (hasPrefix $name "hero") (in $name "og-") (in $name "open-graph") (in $name "social") (in $name "thumb")) }}
- {{ $w300 := $img.Resize "300x q85" }}
- {{ $w600 := $img.Resize "600x q85" }}
+ {{ $w200 := $img.Resize "200x q85" }}
+ {{ $w400 := $img.Resize "400x q85" }}
{{ $eager := lt $imageIndex 8 }}
{{/* Check if we need to start a new month section */}}
@@ -244,9 +244,9 @@
href="{{ $p.RelPermalink }}"
aria-label="Foto vom {{ $dateFormatted }}">
From 2030e1454b1416785fa4d15b64c248b31637ac3c Mon Sep 17 00:00:00 2001
From: Claude
Date: Sun, 2 Nov 2025 01:06:39 +0000
Subject: [PATCH 039/140] Add German dates and centered month headers with
separators
- Convert all date formats to German (Januar, Februar, etc.)
- Center month headers with decorative separator lines
- Add thin gradient lines extending from month headers
- Include dark mode support for separator lines
---
layouts/_default/list.photoshtml.html | 35 +++++++++++++++++++++++++--
1 file changed, 33 insertions(+), 2 deletions(-)
diff --git a/layouts/_default/list.photoshtml.html b/layouts/_default/list.photoshtml.html
index 284149c..70e319c 100644
--- a/layouts/_default/list.photoshtml.html
+++ b/layouts/_default/list.photoshtml.html
@@ -38,12 +38,32 @@
margin: 2rem 0 1rem;
padding-bottom: 0.5rem;
color: #333;
+ text-align: center;
+ display: flex;
+ align-items: center;
+ gap: 1.5rem;
}
.month-header:first-child {
margin-top: 0;
}
+ .month-header::before,
+ .month-header::after {
+ content: '';
+ flex: 1;
+ height: 1px;
+ background: linear-gradient(to right, transparent, #ddd 20%, #ddd 80%, transparent);
+ }
+
+ .month-header::before {
+ background: linear-gradient(to left, #ddd, transparent);
+ }
+
+ .month-header::after {
+ background: linear-gradient(to right, #ddd, transparent);
+ }
+
/* Instagram-Style Grid */
.photos-grid {
display: grid;
@@ -154,6 +174,12 @@
.month-header {
color: #e5e7eb;
}
+ .month-header::before {
+ background: linear-gradient(to left, #4b5563, transparent);
+ }
+ .month-header::after {
+ background: linear-gradient(to right, #4b5563, transparent);
+ }
.photo-tile {
background: #1f2937;
}
@@ -173,11 +199,16 @@
{{ $.Scratch.Set "currentMonth" "" }}
{{ $imageIndex := 0 }}
+ {{/* Deutsche Monatsnamen */}}
+ {{ $monthsDE := dict "January" "Januar" "February" "Februar" "March" "MĂ€rz" "April" "April" "May" "Mai" "June" "Juni" "July" "Juli" "August" "August" "September" "September" "October" "Oktober" "November" "November" "December" "Dezember" }}
+
{{ range $p := $pages }}
- {{ $dateFormatted := $p.Date.Format "2. January 2006" }}
+ {{ $monthEN := $p.Date.Format "January" }}
+ {{ $monthDE := index $monthsDE $monthEN }}
+ {{ $dateFormatted := printf "%d. %s %s" $p.Date.Day $monthDE ($p.Date.Format "2006") }}
{{ $dateISO := $p.Date.Format "2006-01-02" }}
{{ $year := $p.Date.Format "2006" }}
- {{ $monthYear := $p.Date.Format "January 2006" }}
+ {{ $monthYear := printf "%s %s" $monthDE ($p.Date.Format "2006") }}
{{/* Micro.blog Photos */}}
{{ with $p.Params.photos }}
From e033d9167c9c874e038c75fb82fecbc938bb52b2 Mon Sep 17 00:00:00 2001
From: Claude
Date: Sun, 2 Nov 2025 01:07:57 +0000
Subject: [PATCH 040/140] Remove first month header to save space
- First images now start directly at the top without month header
- Month headers only appear from second month onwards
- Saves vertical space and provides cleaner initial view
---
layouts/_default/list.photoshtml.html | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/layouts/_default/list.photoshtml.html b/layouts/_default/list.photoshtml.html
index 70e319c..93426dc 100644
--- a/layouts/_default/list.photoshtml.html
+++ b/layouts/_default/list.photoshtml.html
@@ -229,8 +229,8 @@
{{ if ne $monthYear ($.Scratch.Get "currentMonth") }}
{{ if ne ($.Scratch.Get "currentMonth") "" }}
{{/* Close previous grid */}}
+
{{ end }}
-
{{ $.Scratch.Set "currentMonth" $monthYear }}
{{ end }}
@@ -265,8 +265,8 @@
{{ if ne $monthYear ($.Scratch.Get "currentMonth") }}
{{ if ne ($.Scratch.Get "currentMonth") "" }}
{{/* Close previous grid */}}
+
{{ end }}
-
{{ $.Scratch.Set "currentMonth" $monthYear }}
{{ end }}
From a048e91de641bd9f836ea4d43c582679d26899ce Mon Sep 17 00:00:00 2001
From: Claude
Date: Sun, 2 Nov 2025 01:11:49 +0000
Subject: [PATCH 041/140] Change month separator lines from gradient to solid
in photo gallery
The decorative lines around month headers in the photo gallery now use solid colors instead of gradients for a cleaner, more defined look.
---
layouts/_default/list.photoshtml.html | 16 +++-------------
1 file changed, 3 insertions(+), 13 deletions(-)
diff --git a/layouts/_default/list.photoshtml.html b/layouts/_default/list.photoshtml.html
index 93426dc..ec21a06 100644
--- a/layouts/_default/list.photoshtml.html
+++ b/layouts/_default/list.photoshtml.html
@@ -53,15 +53,7 @@
content: '';
flex: 1;
height: 1px;
- background: linear-gradient(to right, transparent, #ddd 20%, #ddd 80%, transparent);
- }
-
- .month-header::before {
- background: linear-gradient(to left, #ddd, transparent);
- }
-
- .month-header::after {
- background: linear-gradient(to right, #ddd, transparent);
+ background: #ddd;
}
/* Instagram-Style Grid */
@@ -174,11 +166,9 @@
.month-header {
color: #e5e7eb;
}
- .month-header::before {
- background: linear-gradient(to left, #4b5563, transparent);
- }
+ .month-header::before,
.month-header::after {
- background: linear-gradient(to right, #4b5563, transparent);
+ background: #4b5563;
}
.photo-tile {
background: #1f2937;
From 329153cb0103da822d46f5d6c70fc666faa1092a Mon Sep 17 00:00:00 2001
From: Claude
Date: Sun, 2 Nov 2025 01:18:39 +0000
Subject: [PATCH 042/140] Improve photo page layout and hover behavior
- Left-align photo page to match other pages
- Move date badge to top right corner
- Add post title display on hover (bottom)
- Show only date if no title exists
- Improve accessibility with better aria-labels
---
layouts/_default/list.photoshtml.html | 50 ++++++++++++++++++++++-----
1 file changed, 41 insertions(+), 9 deletions(-)
diff --git a/layouts/_default/list.photoshtml.html b/layouts/_default/list.photoshtml.html
index ec21a06..02b15b5 100644
--- a/layouts/_default/list.photoshtml.html
+++ b/layouts/_default/list.photoshtml.html
@@ -25,7 +25,7 @@
/* Main Container */
.photos.content {
max-width: var(--max-width);
- margin: 0;
+ margin: 0 auto 0 0;
padding: 20px;
overscroll-behavior-y: contain;
-webkit-tap-highlight-color: transparent;
@@ -128,11 +128,11 @@
opacity: 1;
}
- /* Date Badge */
+ /* Date Badge - Top Right */
.date-badge {
position: absolute;
- bottom: 12px;
- left: 12px;
+ top: 12px;
+ right: 12px;
padding: 6px 10px;
font-size: 0.75rem;
font-weight: 500;
@@ -141,7 +141,7 @@
background: rgba(0, 0, 0, 0.75);
border-radius: 6px;
opacity: 0;
- transform: translateY(8px);
+ transform: translateY(-8px);
transition: opacity var(--transition-speed) ease,
transform var(--transition-speed) ease;
pointer-events: none;
@@ -155,6 +155,36 @@
transform: translateY(0);
}
+ /* Title Badge - Bottom */
+ .title-badge {
+ position: absolute;
+ bottom: 12px;
+ left: 12px;
+ right: 12px;
+ padding: 8px 12px;
+ font-size: 0.85rem;
+ font-weight: 600;
+ line-height: 1.3;
+ color: #ffffff;
+ background: rgba(0, 0, 0, 0.85);
+ border-radius: 6px;
+ opacity: 0;
+ transform: translateY(8px);
+ transition: opacity var(--transition-speed) ease,
+ transform var(--transition-speed) ease;
+ pointer-events: none;
+ backdrop-filter: blur(8px);
+ z-index: 2;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ .photo-tile:hover .title-badge {
+ opacity: 1;
+ transform: translateY(0);
+ }
+
/* Focus States fĂŒr Accessibility */
.photo-tile:focus-visible {
outline: 3px solid #3b82f6;
@@ -227,15 +257,16 @@
+ aria-label="{{ if $p.Title }}{{ $p.Title }} â {{ end }}Foto vom {{ $dateFormatted }}">
{{ $dateFormatted }}
+ {{ if $p.Title }}{{ $p.Title }}{{ end }}
{{ $imageIndex = add $imageIndex 1 }}
@@ -263,15 +294,16 @@
+ aria-label="{{ if $p.Title }}{{ $p.Title }} â {{ end }}Foto vom {{ $dateFormatted }}">
{{ $dateFormatted }}
+ {{ if $p.Title }}{{ $p.Title }}{{ end }}
{{ $imageIndex = add $imageIndex 1 }}
From a1de7f0c29a954ada1f91429ee2bcc35b41c2172 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=C3=A9=20Fischer?=
Date: Sun, 2 Nov 2025 02:29:38 +0100
Subject: [PATCH 043/140] gallery
---
assets/css/main.css | 10 +++-
layouts/_default/list.photoshtml.html | 68 ++++++++++++++-------------
layouts/partials/photo-tile.html | 50 --------------------
3 files changed, 45 insertions(+), 83 deletions(-)
delete mode 100644 layouts/partials/photo-tile.html
diff --git a/assets/css/main.css b/assets/css/main.css
index 7f83c01..5f9ea4d 100644
--- a/assets/css/main.css
+++ b/assets/css/main.css
@@ -550,6 +550,14 @@ article img,
border-radius: var(--radius-default);
}
+.photos-grid .photo-tile {
+ border-radius: var(--radius-default);
+}
+
+.photos-grid .photo-tile img {
+ border-radius: inherit;
+}
+
/* Video */
video {
width: 100%;
@@ -654,4 +662,4 @@ iframe {
.tiny-text {
font-size: 0.6em;
-}
\ No newline at end of file
+}
diff --git a/layouts/_default/list.photoshtml.html b/layouts/_default/list.photoshtml.html
index 02b15b5..ea431c6 100644
--- a/layouts/_default/list.photoshtml.html
+++ b/layouts/_default/list.photoshtml.html
@@ -5,7 +5,6 @@
:root {
--gap: 15px;
--max-width: 1400px;
- --radius: 8px;
--transition-speed: 0.2s;
}
@@ -24,9 +23,10 @@
/* Main Container */
.photos.content {
- max-width: var(--max-width);
- margin: 0 auto 0 0;
- padding: 20px;
+ max-width: none;
+ width: 100%;
+ margin: 0;
+ padding: 24px clamp(16px, 4vw, 48px);
overscroll-behavior-y: contain;
-webkit-tap-highlight-color: transparent;
}
@@ -81,9 +81,7 @@
position: relative;
aspect-ratio: 1 / 1;
overflow: hidden;
- background: #f8f9fa;
cursor: pointer;
- border-radius: var(--radius);
transition: transform var(--transition-speed) ease,
box-shadow var(--transition-speed) ease;
display: block;
@@ -131,26 +129,28 @@
/* Date Badge - Top Right */
.date-badge {
position: absolute;
- top: 12px;
- right: 12px;
- padding: 6px 10px;
- font-size: 0.75rem;
+ top: 10px;
+ right: 10px;
+ padding: 4px 8px;
+ font-size: 0.68rem;
font-weight: 500;
line-height: 1.2;
color: #ffffff;
- background: rgba(0, 0, 0, 0.75);
- border-radius: 6px;
+ background: rgba(0, 0, 0, 0.65);
+ border-radius: 999px;
opacity: 0;
- transform: translateY(-8px);
+ transform: translateY(-6px);
transition: opacity var(--transition-speed) ease,
transform var(--transition-speed) ease;
pointer-events: none;
- backdrop-filter: blur(8px);
+ backdrop-filter: blur(6px);
z-index: 2;
white-space: nowrap;
+ letter-spacing: 0.01em;
}
- .photo-tile:hover .date-badge {
+ .photo-tile:hover .date-badge,
+ .photo-tile:focus-visible .date-badge {
opacity: 1;
transform: translateY(0);
}
@@ -158,29 +158,32 @@
/* Title Badge - Bottom */
.title-badge {
position: absolute;
- bottom: 12px;
- left: 12px;
- right: 12px;
- padding: 8px 12px;
- font-size: 0.85rem;
- font-weight: 600;
- line-height: 1.3;
+ inset: 0;
+ display: flex;
+ align-items: flex-end;
+ padding: 1.4rem 1rem 1rem;
+ font-size: 0.72rem;
+ font-weight: 500;
+ line-height: 1.35;
color: #ffffff;
- background: rgba(0, 0, 0, 0.85);
- border-radius: 6px;
+ background: linear-gradient(0deg, rgba(0, 0, 0, 0.82) 0%, rgba(0, 0, 0, 0.65) 45%, rgba(0, 0, 0, 0) 100%);
+ border-radius: inherit;
opacity: 0;
- transform: translateY(8px);
+ transform: translateY(6px);
transition: opacity var(--transition-speed) ease,
transform var(--transition-speed) ease;
pointer-events: none;
- backdrop-filter: blur(8px);
+ backdrop-filter: blur(10px);
z-index: 2;
- white-space: nowrap;
+ white-space: normal;
overflow: hidden;
- text-overflow: ellipsis;
+ overflow-wrap: anywhere;
+ text-align: left;
+ text-shadow: 0 2px 6px rgba(0, 0, 0, 0.45);
}
- .photo-tile:hover .title-badge {
+ .photo-tile:hover .title-badge,
+ .photo-tile:focus-visible .title-badge {
opacity: 1;
transform: translateY(0);
}
@@ -209,7 +212,8 @@
@media (prefers-reduced-motion: reduce) {
.photo-tile,
.photo-tile::before,
- .date-badge {
+ .date-badge,
+ .title-badge {
transition: none;
}
}
@@ -251,7 +255,7 @@
{{/* Close previous grid */}}
{{ end }}
-