diff --git a/assets/js/toc (Scroll-Driven Implementation).js b/assets/js/toc (Scroll-Driven Implementation).js
new file mode 100644
index 0000000..caf33b3
--- /dev/null
+++ b/assets/js/toc (Scroll-Driven Implementation).js
@@ -0,0 +1,322 @@
+/**
+ * Floating TOC (Scroll-Driven Implementation)
+ *
+ * This implementation uses a scroll-based approach combined with
+ * requestAnimationFrame to determine the currently active heading.
+ * The active section is calculated based on a fixed viewport trigger
+ * (offset from the top), ensuring predictable and stable behavior.
+ *
+ * Design Goals:
+ * - Deterministic highlighting (no competing states)
+ * - Visual stability (no flickering or race conditions)
+ * - Theme compatibility (no reliance on experimental APIs)
+ * - Maintainable and debuggable logic
+ *
+ * Characteristics:
+ * - Uses a cached list of headings for efficient iteration
+ * - Throttles scroll handling via requestAnimationFrame
+ * - Minimizes DOM writes and layout recalculations
+ * - Separates layout (positioning) from state (active link)
+ *
+ * Trade-offs:
+ * - Runs continuously during scroll (minor CPU overhead)
+ * - Relies on getBoundingClientRect for visibility detection
+ * - Less "event-driven" than IntersectionObserver-based approaches
+ *
+ * Notes:
+ * This approach was chosen over IntersectionObserver to guarantee
+ * consistent ordering, eliminate flickering edge cases, and provide
+ * fully deterministic behavior across all layouts and browsers.
+ */
+
+document.addEventListener('DOMContentLoaded', function () {
+ var toc = document.getElementById('zeitfresser-floating-toc');
+ var title = document.querySelector(
+ '.zeitfresser-article-heading .page-title, ' +
+ '.zeitfresser-article-heading .entry-title, ' +
+ '.entry-header .entry-title'
+ );
+ var progressBar = document.getElementById('zeitfresser-floating-toc-progress');
+ var nav = toc ? toc.querySelector('.zeitfresser-floating-toc__nav') : null;
+
+ if (!toc || !title) {
+ return;
+ }
+
+ var links = Array.prototype.slice.call(toc.querySelectorAll('a[data-target]'));
+ var desktopQuery = window.matchMedia('(min-width: 1500px)');
+ var stickyTop = 100;
+ var headingOffset = 88;
+ var ticking = false;
+ var tocBottomOffset = null;
+ var cachedSidebar = null;
+ var headings = getHeadings();
+
+ function isDesktop() {
+ return desktopQuery.matches;
+ }
+
+ function getTarget(link) {
+ var id = link.getAttribute('data-target');
+ return id ? document.getElementById(id) : null;
+ }
+
+ function getHeadings() {
+ return links
+ .map(function (link) {
+ return { link: link, target: getTarget(link) };
+ })
+ .filter(function (item) {
+ return !!item.target;
+ });
+ }
+
+ function getArticleElement() {
+ return document.querySelector(
+ '.single-post .post-content article, ' +
+ '.single-post .post-content, ' +
+ 'article.post, article, ' +
+ '.entry-content'
+ );
+ }
+
+ function getTocBottomOffset() {
+ if (tocBottomOffset !== null) return tocBottomOffset;
+
+ var value = getComputedStyle(document.documentElement)
+ .getPropertyValue('--toc-bottom-offset')
+ .trim();
+
+ tocBottomOffset = parseInt(value, 10) || 12;
+ return tocBottomOffset;
+ }
+
+ function getRealSidebar() {
+ if (cachedSidebar) return cachedSidebar;
+
+ var candidates = Array.prototype.slice.call(
+ document.querySelectorAll('aside, .sidebar, #secondary')
+ );
+
+ cachedSidebar = candidates
+ .filter(function (el) {
+ var rect = el.getBoundingClientRect();
+ return rect.width > 200 && rect.height > 200;
+ })
+ .sort(function (a, b) {
+ var rectA = a.getBoundingClientRect();
+ var rectB = b.getBoundingClientRect();
+ return rectB.left - rectA.left;
+ })[0] || null;
+
+ return cachedSidebar;
+ }
+
+ function syncPosition() {
+ if (!isDesktop()) {
+ document.documentElement.style.setProperty('--zeitfresser-toc-top', stickyTop + 'px');
+ document.documentElement.style.setProperty('--zeitfresser-toc-left', '24px');
+ document.documentElement.style.setProperty('--zeitfresser-toc-width', '220px');
+ return;
+ }
+
+ var scrollTop = window.scrollY || window.pageYOffset || 0;
+ var titleRect = title.getBoundingClientRect();
+
+ // 🔥 bessere Content-Erkennung
+ var contentColumn =
+ document.querySelector('.inside-page .main-wrapper > section') ||
+ document.querySelector('#primary') ||
+ document.querySelector('.content-area') ||
+ title;
+
+ if (!contentColumn) return;
+
+ var sidebar = getRealSidebar();
+ var contentRect = contentColumn.getBoundingClientRect();
+ var sidebarRect = sidebar ? sidebar.getBoundingClientRect() : null;
+
+ var gap = 48;
+
+ if (sidebarRect) {
+ gap = Math.abs(sidebarRect.left - contentRect.right);
+ gap = Math.max(32, Math.min(gap, 120));
+ }
+
+ var maxWidth = Math.max(Math.round(contentRect.left - gap - 24), 180);
+ var tocWidth = Math.max(220, Math.min(260, maxWidth));
+
+ var tocLeft = Math.max(
+ 24,
+ Math.round(contentRect.left - gap - tocWidth)
+ );
+
+ var tocTop = Math.max(
+ stickyTop,
+ Math.round(titleRect.top + scrollTop + 14)
+ );
+
+ document.documentElement.style.setProperty('--zeitfresser-toc-top', tocTop + 'px');
+ document.documentElement.style.setProperty('--zeitfresser-toc-left', tocLeft + 'px');
+ document.documentElement.style.setProperty('--zeitfresser-toc-width', tocWidth + 'px');
+ }
+
+ function handleFooterCollision() {
+ if (!isDesktop()) {
+ toc.style.transform = '';
+ return;
+ }
+
+ var article = getArticleElement();
+ if (!article) {
+ toc.style.transform = '';
+ return;
+ }
+
+ toc.style.transform = '';
+
+ var scrollTop = window.scrollY || window.pageYOffset;
+ var articleRect = article.getBoundingClientRect();
+ var articleBottom = articleRect.top + scrollTop + articleRect.height;
+
+ var tocRect = toc.getBoundingClientRect();
+ var tocTop = tocRect.top + scrollTop;
+ var tocHeight = tocRect.height;
+ var tocBottom = tocTop + tocHeight;
+
+ var offset = getTocBottomOffset();
+ var maxBottom = articleBottom - offset;
+ var overflow = Math.ceil(tocBottom - maxBottom);
+
+ if (overflow > 0) {
+ toc.style.transform = 'translateY(-' + overflow + 'px)';
+ }
+ }
+
+ function setActiveLink(id) {
+ links.forEach(function (link) {
+ var active = link.getAttribute('data-target') === id;
+ link.classList.toggle('is-active', active);
+
+ if (active) {
+ link.setAttribute('aria-current', 'true');
+ } else {
+ link.removeAttribute('aria-current');
+ }
+ });
+ }
+
+ function updateProgress() {
+ if (!progressBar) return;
+
+ var article = getArticleElement();
+ if (!article) {
+ progressBar.style.width = '0%';
+ return;
+ }
+
+ var rect = article.getBoundingClientRect();
+ var total = Math.max(article.offsetHeight - window.innerHeight, 1);
+
+ var progress = Math.min(
+ Math.max((-rect.top / total) * 100, 0),
+ 100
+ );
+
+ progressBar.style.width = progress + '%';
+ }
+
+ function updateActiveHeading() {
+ if (!headings.length) return;
+
+ var currentId = headings[0].target.id;
+ var triggerY = headingOffset + 24;
+
+ for (var i = 0; i < headings.length; i++) {
+ var rectTop = headings[i].target.getBoundingClientRect().top;
+
+ if (rectTop <= triggerY) {
+ currentId = headings[i].target.id;
+ } else {
+ break;
+ }
+ }
+
+ setActiveLink(currentId);
+ }
+
+ function onViewportChange() {
+ if (ticking) return;
+
+ ticking = true;
+
+ window.requestAnimationFrame(function () {
+ syncPosition();
+ handleFooterCollision();
+ updateProgress();
+ updateActiveHeading();
+ ticking = false;
+ });
+ }
+
+ links.forEach(function (link) {
+ link.addEventListener('click', function (event) {
+ var target = getTarget(link);
+ if (!target) return;
+
+ event.preventDefault();
+
+ var top =
+ target.getBoundingClientRect().top +
+ window.scrollY -
+ headingOffset;
+
+ window.scrollTo({
+ top: top,
+ behavior: 'smooth'
+ });
+
+ setActiveLink(target.id);
+ });
+ });
+
+ if (nav) {
+ nav.addEventListener(
+ 'wheel',
+ function (event) {
+ var canScroll = nav.scrollHeight > nav.clientHeight;
+ if (!canScroll) return;
+
+ var atTop = nav.scrollTop <= 0;
+ var atBottom =
+ Math.ceil(nav.scrollTop + nav.clientHeight) >= nav.scrollHeight;
+
+ if (
+ (event.deltaY < 0 && !atTop) ||
+ (event.deltaY > 0 && !atBottom)
+ ) {
+ event.preventDefault();
+ nav.scrollTop += event.deltaY;
+ }
+ },
+ { passive: false }
+ );
+ }
+
+ // Initial run
+ syncPosition();
+ handleFooterCollision();
+ updateProgress();
+ updateActiveHeading();
+
+ requestAnimationFrame(function () {
+ toc.classList.add('is-visible');
+ });
+
+ window.addEventListener('scroll', onViewportChange, { passive: true });
+ window.addEventListener('resize', onViewportChange, { passive: true });
+ window.addEventListener('load', function () {
+ syncPosition();
+ });
+
+});
diff --git a/assets/js/toc (observer).js b/assets/js/toc (observer).js
new file mode 100644
index 0000000..8968825
--- /dev/null
+++ b/assets/js/toc (observer).js
@@ -0,0 +1,299 @@
+/**
+ * Floating TOC (IntersectionObserver Version)
+ *
+ * This implementation relies on the IntersectionObserver API to detect which
+ * headings are currently visible in the viewport. It is event-driven and
+ * more efficient, as updates occur only when visibility changes.
+ *
+ * Pros:
+ * - Lower CPU usage (no continuous polling)
+ * - Native browser optimization
+ *
+ * Cons:
+ * - Can produce multiple competing active states
+ * - May require tuning to avoid flickering or reordering
+ */
+
+document.addEventListener('DOMContentLoaded', function () {
+ var toc = document.getElementById('zeitfresser-floating-toc');
+ var title = document.querySelector('.zeitfresser-article-heading .page-title, .zeitfresser-article-heading .entry-title, .entry-header .entry-title');
+ var progressBar = document.getElementById('zeitfresser-floating-toc-progress');
+ var nav = toc ? toc.querySelector('.zeitfresser-floating-toc__nav') : null;
+
+ if (!toc || !title) {
+ return;
+ }
+
+ var links = Array.prototype.slice.call(toc.querySelectorAll('a[data-target]'));
+ var desktopQuery = window.matchMedia('(min-width: 1500px)');
+ var stickyTop = 100;
+ var headingOffset = 88;
+ var ticking = false;
+ var cachedSidebar = null;
+ var tocBottomOffset = null;
+
+ function isDesktop() {
+ return desktopQuery.matches;
+ }
+
+ function getTarget(link) {
+ var id = link.getAttribute('data-target');
+ return id ? document.getElementById(id) : null;
+ }
+
+ function getHeadings() {
+ return links.map(function (link) {
+ return {
+ link: link,
+ target: getTarget(link)
+ };
+ }).filter(function (item) {
+ return !!item.target;
+ });
+ }
+
+ function getArticleElement() {
+ return document.querySelector(
+ '.single-post .post-content article, ' +
+ '.single-post .post-content, ' +
+ 'article.post, article, ' +
+ '.entry-content'
+ );
+ }
+
+ function getTocBottomOffset() {
+ if (tocBottomOffset !== null) return tocBottomOffset;
+
+ var value = getComputedStyle(document.documentElement)
+ .getPropertyValue('--toc-bottom-offset')
+ .trim();
+
+ tocBottomOffset = parseInt(value, 10) || 12;
+ return tocBottomOffset;
+ }
+
+ function getRealSidebar() {
+ if (cachedSidebar) return cachedSidebar;
+
+ var candidates = Array.prototype.slice.call(
+ document.querySelectorAll('aside, .sidebar, #secondary')
+ );
+
+ cachedSidebar = candidates
+ .filter(function (el) {
+ var rect = el.getBoundingClientRect();
+ return rect.width > 200 && rect.height > 200;
+ })
+ .sort(function (a, b) {
+ return b.getBoundingClientRect().left - a.getBoundingClientRect().left;
+ })[0] || null;
+
+ return cachedSidebar;
+ }
+
+ function syncPosition() {
+ if (!isDesktop()) {
+ document.documentElement.style.setProperty('--zeitfresser-toc-top', stickyTop + 'px');
+ document.documentElement.style.setProperty('--zeitfresser-toc-left', '24px');
+ document.documentElement.style.setProperty('--zeitfresser-toc-width', '220px');
+ return;
+ }
+
+ var scrollTop = window.scrollY || window.pageYOffset || 0;
+ var titleRect = title.getBoundingClientRect();
+
+ // 🔥 bessere Content-Erkennung
+ var contentColumn =
+ document.querySelector('.inside-page .main-wrapper > section') ||
+ document.querySelector('#primary') ||
+ document.querySelector('.content-area') ||
+ title;
+
+ if (!contentColumn) return;
+
+ var sidebar = getRealSidebar();
+
+ var contentRect = contentColumn.getBoundingClientRect();
+ var sidebarRect = sidebar ? sidebar.getBoundingClientRect() : null;
+
+ var gap = 48;
+
+ if (sidebarRect) {
+ gap = Math.abs(sidebarRect.left - contentRect.right);
+ gap = Math.max(32, Math.min(gap, 120));
+ }
+
+ // Toc Content Breite
+ var maxWidth = Math.max(Math.round(contentRect.left - gap - 24), 180);
+ var tocWidth = Math.max(220, Math.min(260, maxWidth));
+
+ var tocLeft = Math.max(
+ 24,
+ Math.round(contentRect.left - gap - tocWidth)
+ );
+
+ var tocTop = Math.max(
+ stickyTop,
+ Math.round(titleRect.top + scrollTop + 14)
+ );
+
+ document.documentElement.style.setProperty('--zeitfresser-toc-top', tocTop + 'px');
+ document.documentElement.style.setProperty('--zeitfresser-toc-left', tocLeft + 'px');
+ document.documentElement.style.setProperty('--zeitfresser-toc-width', tocWidth + 'px');
+ }
+
+ function handleFooterCollision() {
+ if (!isDesktop()) {
+ toc.style.transform = '';
+ return;
+ }
+
+ var article = getArticleElement();
+
+ if (!article) {
+ toc.style.transform = '';
+ return;
+ }
+
+ toc.style.transform = '';
+
+ var scrollTop = window.scrollY || window.pageYOffset;
+
+ var articleRect = article.getBoundingClientRect();
+ var articleBottom = articleRect.top + scrollTop + articleRect.height;
+
+ var tocRect = toc.getBoundingClientRect();
+ var tocTop = tocRect.top + scrollTop;
+ var tocHeight = tocRect.height;
+ var tocBottom = tocTop + tocHeight;
+
+ var offset = getTocBottomOffset();
+
+ var maxBottom = articleBottom - offset;
+ var overflow = Math.ceil(tocBottom - maxBottom);
+
+ if (overflow > 0) {
+ toc.style.transform = 'translateY(-' + overflow + 'px)';
+ }
+ }
+
+ function setActiveLink(id) {
+ links.forEach(function (link) {
+ var active = link.getAttribute('data-target') === id;
+ link.classList.toggle('is-active', active);
+
+ if (active) {
+ link.setAttribute('aria-current', 'true');
+ } else {
+ link.removeAttribute('aria-current');
+ }
+ });
+ }
+
+ function updateProgress() {
+ if (!progressBar) return;
+
+ var article = getArticleElement();
+
+ if (!article) {
+ progressBar.style.width = '0%';
+ return;
+ }
+
+ var rect = article.getBoundingClientRect();
+ var total = Math.max(article.offsetHeight - window.innerHeight, 1);
+ var progress = Math.min(Math.max((-rect.top / total) * 100, 0), 100);
+
+ progressBar.style.width = progress + '%';
+ }
+
+ function onViewportChange() {
+ if (ticking) return;
+
+ ticking = true;
+
+ window.requestAnimationFrame(function () {
+ syncPosition();
+ handleFooterCollision();
+ updateProgress();
+ ticking = false;
+ });
+ }
+
+ // 🔥 NEW: IntersectionObserver for active headings
+ let currentActiveId = null;
+
+ function initIntersectionObserver() {
+ const headings = getHeadings();
+ if (!headings.length) return;
+
+ const observer = new IntersectionObserver(function (entries) {
+
+ entries.forEach(function (entry) {
+ if (entry.isIntersecting) {
+ const id = entry.target.id;
+
+ if (id !== currentActiveId) {
+ currentActiveId = id;
+ setActiveLink(id);
+ }
+ }
+ });
+
+ }, {
+ root: null,
+ rootMargin: `-${headingOffset}px 0px -70% 0px`,
+ threshold: 0
+ });
+
+ headings.forEach(function (item) {
+ if (item.target) {
+ observer.observe(item.target);
+ }
+ });
+ }
+
+ links.forEach(function (link) {
+ link.addEventListener('click', function (event) {
+ var target = getTarget(link);
+ if (!target) return;
+
+ event.preventDefault();
+
+ var top = target.getBoundingClientRect().top + window.scrollY - headingOffset;
+
+ window.scrollTo({
+ top: top,
+ behavior: 'smooth'
+ });
+ });
+ });
+
+ if (nav) {
+ nav.addEventListener('wheel', function (event) {
+ var canScroll = nav.scrollHeight > nav.clientHeight;
+ if (!canScroll) return;
+
+ var atTop = nav.scrollTop <= 0;
+ var atBottom = Math.ceil(nav.scrollTop + nav.clientHeight) >= nav.scrollHeight;
+
+ if ((event.deltaY < 0 && !atTop) || (event.deltaY > 0 && !atBottom)) {
+ event.preventDefault();
+ nav.scrollTop += event.deltaY;
+ }
+ }, { passive: false });
+ }
+
+ // Initial run
+ syncPosition();
+ handleFooterCollision();
+ updateProgress();
+ initIntersectionObserver();
+
+ requestAnimationFrame(function () {
+ toc.classList.add('is-visible');
+ });
+
+ window.addEventListener('scroll', onViewportChange, { passive: true });
+ window.addEventListener('resize', onViewportChange, { passive: true });
+});
diff --git a/assets/js/toc.js b/assets/js/toc.js
index ae04466..caf33b3 100644
--- a/assets/js/toc.js
+++ b/assets/js/toc.js
@@ -1,6 +1,41 @@
+/**
+ * Floating TOC (Scroll-Driven Implementation)
+ *
+ * This implementation uses a scroll-based approach combined with
+ * requestAnimationFrame to determine the currently active heading.
+ * The active section is calculated based on a fixed viewport trigger
+ * (offset from the top), ensuring predictable and stable behavior.
+ *
+ * Design Goals:
+ * - Deterministic highlighting (no competing states)
+ * - Visual stability (no flickering or race conditions)
+ * - Theme compatibility (no reliance on experimental APIs)
+ * - Maintainable and debuggable logic
+ *
+ * Characteristics:
+ * - Uses a cached list of headings for efficient iteration
+ * - Throttles scroll handling via requestAnimationFrame
+ * - Minimizes DOM writes and layout recalculations
+ * - Separates layout (positioning) from state (active link)
+ *
+ * Trade-offs:
+ * - Runs continuously during scroll (minor CPU overhead)
+ * - Relies on getBoundingClientRect for visibility detection
+ * - Less "event-driven" than IntersectionObserver-based approaches
+ *
+ * Notes:
+ * This approach was chosen over IntersectionObserver to guarantee
+ * consistent ordering, eliminate flickering edge cases, and provide
+ * fully deterministic behavior across all layouts and browsers.
+ */
+
document.addEventListener('DOMContentLoaded', function () {
var toc = document.getElementById('zeitfresser-floating-toc');
- var title = document.querySelector('.zeitfresser-article-heading .page-title, .zeitfresser-article-heading .entry-title, .entry-header .entry-title');
+ var title = document.querySelector(
+ '.zeitfresser-article-heading .page-title, ' +
+ '.zeitfresser-article-heading .entry-title, ' +
+ '.entry-header .entry-title'
+ );
var progressBar = document.getElementById('zeitfresser-floating-toc-progress');
var nav = toc ? toc.querySelector('.zeitfresser-floating-toc__nav') : null;
@@ -13,8 +48,9 @@ document.addEventListener('DOMContentLoaded', function () {
var stickyTop = 100;
var headingOffset = 88;
var ticking = false;
- var cachedSidebar = null;
var tocBottomOffset = null;
+ var cachedSidebar = null;
+ var headings = getHeadings();
function isDesktop() {
return desktopQuery.matches;
@@ -26,14 +62,13 @@ document.addEventListener('DOMContentLoaded', function () {
}
function getHeadings() {
- return links.map(function (link) {
- return {
- link: link,
- target: getTarget(link)
- };
- }).filter(function (item) {
- return !!item.target;
- });
+ return links
+ .map(function (link) {
+ return { link: link, target: getTarget(link) };
+ })
+ .filter(function (item) {
+ return !!item.target;
+ });
}
function getArticleElement() {
@@ -55,7 +90,7 @@ document.addEventListener('DOMContentLoaded', function () {
tocBottomOffset = parseInt(value, 10) || 12;
return tocBottomOffset;
}
-
+
function getRealSidebar() {
if (cachedSidebar) return cachedSidebar;
@@ -69,7 +104,9 @@ document.addEventListener('DOMContentLoaded', function () {
return rect.width > 200 && rect.height > 200;
})
.sort(function (a, b) {
- return b.getBoundingClientRect().left - a.getBoundingClientRect().left;
+ var rectA = a.getBoundingClientRect();
+ var rectB = b.getBoundingClientRect();
+ return rectB.left - rectA.left;
})[0] || null;
return cachedSidebar;
@@ -96,7 +133,6 @@ document.addEventListener('DOMContentLoaded', function () {
if (!contentColumn) return;
var sidebar = getRealSidebar();
-
var contentRect = contentColumn.getBoundingClientRect();
var sidebarRect = sidebar ? sidebar.getBoundingClientRect() : null;
@@ -107,10 +143,9 @@ document.addEventListener('DOMContentLoaded', function () {
gap = Math.max(32, Math.min(gap, 120));
}
- // Toc Content Breite
var maxWidth = Math.max(Math.round(contentRect.left - gap - 24), 180);
var tocWidth = Math.max(220, Math.min(260, maxWidth));
-
+
var tocLeft = Math.max(
24,
Math.round(contentRect.left - gap - tocWidth)
@@ -133,7 +168,6 @@ document.addEventListener('DOMContentLoaded', function () {
}
var article = getArticleElement();
-
if (!article) {
toc.style.transform = '';
return;
@@ -142,7 +176,6 @@ document.addEventListener('DOMContentLoaded', function () {
toc.style.transform = '';
var scrollTop = window.scrollY || window.pageYOffset;
-
var articleRect = article.getBoundingClientRect();
var articleBottom = articleRect.top + scrollTop + articleRect.height;
@@ -152,7 +185,6 @@ document.addEventListener('DOMContentLoaded', function () {
var tocBottom = tocTop + tocHeight;
var offset = getTocBottomOffset();
-
var maxBottom = articleBottom - offset;
var overflow = Math.ceil(tocBottom - maxBottom);
@@ -178,7 +210,6 @@ document.addEventListener('DOMContentLoaded', function () {
if (!progressBar) return;
var article = getArticleElement();
-
if (!article) {
progressBar.style.width = '0%';
return;
@@ -186,23 +217,30 @@ document.addEventListener('DOMContentLoaded', function () {
var rect = article.getBoundingClientRect();
var total = Math.max(article.offsetHeight - window.innerHeight, 1);
- var progress = Math.min(Math.max((-rect.top / total) * 100, 0), 100);
+
+ var progress = Math.min(
+ Math.max((-rect.top / total) * 100, 0),
+ 100
+ );
progressBar.style.width = progress + '%';
}
function updateActiveHeading() {
- var headings = getHeadings();
if (!headings.length) return;
var currentId = headings[0].target.id;
var triggerY = headingOffset + 24;
- headings.forEach(function (item) {
- if (item.target.getBoundingClientRect().top <= triggerY) {
- currentId = item.target.id;
+ for (var i = 0; i < headings.length; i++) {
+ var rectTop = headings[i].target.getBoundingClientRect().top;
+
+ if (rectTop <= triggerY) {
+ currentId = headings[i].target.id;
+ } else {
+ break;
}
- });
+ }
setActiveLink(currentId);
}
@@ -228,7 +266,10 @@ document.addEventListener('DOMContentLoaded', function () {
event.preventDefault();
- var top = target.getBoundingClientRect().top + window.scrollY - headingOffset;
+ var top =
+ target.getBoundingClientRect().top +
+ window.scrollY -
+ headingOffset;
window.scrollTo({
top: top,
@@ -240,18 +281,26 @@ document.addEventListener('DOMContentLoaded', function () {
});
if (nav) {
- nav.addEventListener('wheel', function (event) {
- var canScroll = nav.scrollHeight > nav.clientHeight;
- if (!canScroll) return;
+ nav.addEventListener(
+ 'wheel',
+ function (event) {
+ var canScroll = nav.scrollHeight > nav.clientHeight;
+ if (!canScroll) return;
- var atTop = nav.scrollTop <= 0;
- var atBottom = Math.ceil(nav.scrollTop + nav.clientHeight) >= nav.scrollHeight;
+ var atTop = nav.scrollTop <= 0;
+ var atBottom =
+ Math.ceil(nav.scrollTop + nav.clientHeight) >= nav.scrollHeight;
- if ((event.deltaY < 0 && !atTop) || (event.deltaY > 0 && !atBottom)) {
- event.preventDefault();
- nav.scrollTop += event.deltaY;
- }
- }, { passive: false });
+ if (
+ (event.deltaY < 0 && !atTop) ||
+ (event.deltaY > 0 && !atBottom)
+ ) {
+ event.preventDefault();
+ nav.scrollTop += event.deltaY;
+ }
+ },
+ { passive: false }
+ );
}
// Initial run
@@ -266,4 +315,8 @@ document.addEventListener('DOMContentLoaded', function () {
window.addEventListener('scroll', onViewportChange, { passive: true });
window.addEventListener('resize', onViewportChange, { passive: true });
+ window.addEventListener('load', function () {
+ syncPosition();
+ });
+
});
diff --git a/inc/customizer/general.php b/inc/customizer/general.php
index 310ff5b..71ac11b 100644
--- a/inc/customizer/general.php
+++ b/inc/customizer/general.php
@@ -10,7 +10,24 @@ add_action( 'customize_register', 'zeitfresser_general_options' );
function zeitfresser_general_options( $wp_customize ) {
/**
- * General Section (falls nicht schon vorhanden)
+ * 🔥 Custom Heading Control
+ */
+ if ( class_exists( 'WP_Customize_Control' ) ) {
+ class ZTFR_Customize_Heading_Control extends WP_Customize_Control {
+ public $type = 'ztfr-heading';
+
+ public function render_content() {
+ ?>
+
+ label ); ?>
+
+ get_section( 'ztfr_general' ) ) {
$wp_customize->add_section(
@@ -23,28 +40,23 @@ function zeitfresser_general_options( $wp_customize ) {
}
/**
- * Excerpt Length
+ * ------------------------
+ * HEADER
+ * ------------------------
*/
- $wp_customize->add_setting(
- 'post_snippet_excerpt_size',
- array(
- 'default' => 20,
- 'sanitize_callback' => 'absint',
- )
- );
+ $wp_customize->add_setting( 'ztfr_header_heading', array(
+ 'sanitize_callback' => 'sanitize_text_field',
+ ));
$wp_customize->add_control(
- 'post_snippet_excerpt_size',
- array(
- 'type' => 'number',
- 'section' => 'ztfr_general',
- 'label' => 'Excerpt Length (Post Cards)',
- 'description' => 'Number of words shown in post previews.',
- 'input_attrs' => array(
- 'min' => 5,
- 'max' => 100,
- 'step' => 1,
- ),
+ new ZTFR_Customize_Heading_Control(
+ $wp_customize,
+ 'ztfr_header_heading',
+ array(
+ 'label' => 'Header',
+ 'section' => 'ztfr_general',
+ 'priority' => 1,
+ )
)
);
@@ -62,9 +74,10 @@ function zeitfresser_general_options( $wp_customize ) {
$wp_customize->add_control(
'show_hide_site_title',
array(
- 'type' => 'checkbox',
- 'section' => 'ztfr_general',
- 'label' => 'Show Site Title',
+ 'type' => 'checkbox',
+ 'section' => 'ztfr_general',
+ 'label' => 'Show Site Title',
+ 'priority' => 2,
)
);
@@ -82,9 +95,58 @@ function zeitfresser_general_options( $wp_customize ) {
$wp_customize->add_control(
'show_hide_site_tagline',
array(
- 'type' => 'checkbox',
- 'section' => 'ztfr_general',
- 'label' => 'Show Tagline',
+ 'type' => 'checkbox',
+ 'section' => 'ztfr_general',
+ 'label' => 'Show Tagline',
+ 'priority' => 3,
+ )
+ );
+
+ /**
+ * ------------------------
+ * GRID
+ * ------------------------
+ */
+ $wp_customize->add_setting( 'ztfr_grid_heading', array(
+ 'sanitize_callback' => 'sanitize_text_field',
+ ));
+
+ $wp_customize->add_control(
+ new ZTFR_Customize_Heading_Control(
+ $wp_customize,
+ 'ztfr_grid_heading',
+ array(
+ 'label' => 'Grid',
+ 'section' => 'ztfr_general',
+ 'priority' => 20,
+ )
+ )
+ );
+
+ /**
+ * Excerpt Length
+ */
+ $wp_customize->add_setting(
+ 'post_snippet_excerpt_size',
+ array(
+ 'default' => 25,
+ 'sanitize_callback' => 'absint',
+ )
+ );
+
+ $wp_customize->add_control(
+ 'post_snippet_excerpt_size',
+ array(
+ 'type' => 'number',
+ 'section' => 'ztfr_general',
+ 'label' => 'Excerpt Length (Post Cards)',
+ 'description' => 'Number of words shown in post previews.',
+ 'priority' => 21,
+ 'input_attrs' => array(
+ 'min' => 5,
+ 'max' => 100,
+ 'step' => 1,
+ ),
)
);
}
diff --git a/inc/customizer/layout.php b/inc/customizer/layout.php
index 93e2c65..a44934e 100644
--- a/inc/customizer/layout.php
+++ b/inc/customizer/layout.php
@@ -27,7 +27,7 @@ function zeitfresser_layout_options( $wp_customize ) {
'section' => 'ztfr_general',
'label' => esc_html__( 'Container Width', 'zeitfresser' ),
'description' => esc_html__( 'Maximum width of the content container in pixels.', 'zeitfresser' ),
- 'priority' => 10,
+ 'priority' => 22,
'input_attrs' => array(
'min' => 800,
'max' => 2000,
@@ -47,7 +47,7 @@ function zeitfresser_container_width_dynamic_css() {
$container_width = (int) get_theme_mod( 'container_width' );
if ( $container_width <= 0 ) {
- $container_width = 1140;
+ $container_width = 1400;
}
echo '';
diff --git a/inc/customizer/social.php b/inc/customizer/social.php
index 887d745..f5b383b 100644
--- a/inc/customizer/social.php
+++ b/inc/customizer/social.php
@@ -6,11 +6,6 @@
*/
if ( ! function_exists( 'zeitfresser_get_social_links' ) ) {
- /**
- * Return supported social networks.
- *
- * @return array
- */
function zeitfresser_get_social_links() {
return array(
'facebook' => esc_html__( 'Facebook', 'zeitfresser' ),
@@ -31,31 +26,52 @@ add_action( 'customize_register', 'zeitfresser_social_links' );
function zeitfresser_social_links( $wp_customize ) {
- $social_links = zeitfresser_get_social_links();
+ /**
+ * 🔥 Heading Control (falls noch nicht vorhanden)
+ */
+ if ( class_exists( 'WP_Customize_Control' ) && ! class_exists( 'ZTFR_Customize_Heading_Control' ) ) {
+ class ZTFR_Customize_Heading_Control extends WP_Customize_Control {
+ public $type = 'ztfr-heading';
+
+ public function render_content() {
+ ?>
+
+ label ); ?>
+
+ add_setting(
'ztfr_social_heading',
array(
- 'sanitize_callback' => 'wp_kses_post',
+ 'sanitize_callback' => 'sanitize_text_field',
)
);
$wp_customize->add_control(
- 'ztfr_social_heading',
- array(
- 'section' => 'ztfr_general',
- 'type' => 'hidden',
- 'description' => '
' . esc_html__( 'Social Links', 'zeitfresser' ) . '',
- 'priority' => 30,
+ new ZTFR_Customize_Heading_Control(
+ $wp_customize,
+ 'ztfr_social_heading',
+ array(
+ 'label' => esc_html__( 'Social Links', 'zeitfresser' ),
+ 'section' => 'ztfr_general',
+ 'priority' => 30,
+ )
)
);
/**
* Social URLs
*/
+ $social_links = zeitfresser_get_social_links();
+
$priority = 31;
foreach ( $social_links as $key => $label ) {
diff --git a/inc/customizer/toc.php b/inc/customizer/toc.php
index 38f6ec2..984eb4b 100644
--- a/inc/customizer/toc.php
+++ b/inc/customizer/toc.php
@@ -10,27 +10,48 @@ add_action( 'customize_register', 'zeitfresser_toc_options' );
function zeitfresser_toc_options( $wp_customize ) {
/**
- * Section Divider (UI only)
+ * 🔥 Heading Control (falls noch nicht vorhanden)
+ */
+ if ( class_exists( 'WP_Customize_Control' ) && ! class_exists( 'ZTFR_Customize_Heading_Control' ) ) {
+ class ZTFR_Customize_Heading_Control extends WP_Customize_Control {
+ public $type = 'ztfr-heading';
+
+ public function render_content() {
+ ?>
+
+ label ); ?>
+
+ add_setting(
'ztfr_toc_heading',
array(
- 'sanitize_callback' => 'wp_kses_post',
+ 'sanitize_callback' => 'sanitize_text_field',
)
);
$wp_customize->add_control(
- 'ztfr_toc_heading',
- array(
- 'section' => 'ztfr_general',
- 'type' => 'hidden',
- 'description' => '
' . esc_html__( 'Article TOC', 'zeitfresser' ) . '',
- 'priority' => 20,
+ new ZTFR_Customize_Heading_Control(
+ $wp_customize,
+ 'ztfr_toc_heading',
+ array(
+ 'label' => esc_html__( 'TOC', 'zeitfresser' ),
+ 'section' => 'ztfr_general',
+ 'priority' => 10,
+ )
)
);
/**
- * Toggle TOC
+ * Show TOC
*/
$wp_customize->add_setting(
'show_article_toc',
@@ -47,7 +68,7 @@ function zeitfresser_toc_options( $wp_customize ) {
'section' => 'ztfr_general',
'label' => esc_html__( 'Show Article TOC', 'zeitfresser' ),
'description' => esc_html__( 'Enable floating TOC on single posts.', 'zeitfresser' ),
- 'priority' => 21,
+ 'priority' => 11,
)
);
@@ -67,9 +88,9 @@ function zeitfresser_toc_options( $wp_customize ) {
array(
'type' => 'number',
'section' => 'ztfr_general',
- 'label' => esc_html__( 'Minimum Headlines for TOC', 'zeitfresser' ),
+ 'label' => esc_html__( 'Minimum Headlines', 'zeitfresser' ),
'description' => esc_html__( 'TOC appears only if this number of headings is reached.', 'zeitfresser' ),
- 'priority' => 22,
+ 'priority' => 12,
'input_attrs' => array(
'min' => 1,
'max' => 50,
diff --git a/style.css b/style.css
index 5457eab..bf1d150 100644
--- a/style.css
+++ b/style.css
@@ -5,7 +5,7 @@ Author: Zeitfresser
Author URI: https://ztfr.eu/
Theme URI: https://ztfr.eu/
Description: Zeitfresser Wordpress Theme
-Version: 2.2
+Version: 2.4
Tested up to: 6.2
Requires PHP: 7.0
License: GNU General Public License v2 or later