count,
})).sort((a, b) => a.displayName.localeCompare(b.displayName));
-const mainSections = [
- { name: 'blog', url: '/blog/', title: 'Blog' },
- { name: 'projects', url: '/projects/', title: 'Projects' },
- { name: 'about', url: '/about.html', title: 'About' },
-];
+// ── About section: static tree matching Hugo content/about/ structure ──────────
+// Sorted by weight (Hugo default), then by title.
+// isNode = true means the page is a section index (has children in Hugo).
+interface NavPage { url: string; title: string; isNode?: boolean; children?: NavPage[] }
+
+const aboutSection: NavPage = {
+ url: '/about.html', title: 'About', isNode: true,
+ children: [
+ { url: '/contact.html', title: 'Contact' }, // weight 10
+ { // weight 10 (alpha: C < I)
+ url: '/impressum.html', title: 'Impressum', isNode: true,
+ children: [
+ { url: '/urheberrechte.html', title: 'Urheberrecht' }, // weight 10
+ { url: '/datenschutz.html', title: 'Datenschutz' }, // weight 20
+ { url: '/haftung-inhalte.html', title: 'Haftung für Inhalte' }, // weight 30
+ { url: '/haftung-links.html', title: 'Haftung für Links' }, // weight 40
+ { url: '/google-analytics.html', title: 'Google Analytics' }, // no weight → last
+ ]
+ },
+ { url: '/agb.html', title: 'AGB' }, // weight 30
+ ]
+};
+
+// True if `url` is anywhere inside `node`'s subtree (inclusive)
+function inSubtree(node: NavPage, url: string): boolean {
+ if (node.url === url) return true;
+ return (node.children ?? []).some(c => inSubtree(c, url));
+}
+
+const onAboutSection = inSubtree(aboutSection, currentUrl);
+
+// Parent URL of each About-section page (needed for sibling/child off-logic)
+const aboutParentOf = new Map<string, string>([
+ ['/about.html', ''],
+ ['/contact.html', '/about.html'],
+ ['/impressum.html', '/about.html'],
+ ['/agb.html', '/about.html'],
+ ['/urheberrechte.html', '/impressum.html'],
+ ['/datenschutz.html', '/impressum.html'],
+ ['/haftung-inhalte.html', '/impressum.html'],
+ ['/haftung-links.html', '/impressum.html'],
+ ['/google-analytics.html','/impressum.html'],
+]);
+
+// Section-index pages within About (= have children in Hugo)
+const aboutSectionIndexUrls = new Set(['/about.html', '/impressum.html']);
+const currentAboutIsNode = aboutSectionIndexUrls.has(currentUrl);
+const currentAboutParent = aboutParentOf.get(currentUrl) ?? '';
-// URL classification
-const onBlog = currentUrl === '/blog/';
-const isOnBlogPost = allPosts.some(p => (p.data.url ?? `/${p.id}/`) === currentUrl);
-const onArchive = currentUrl.startsWith('/blog/archive/') || isOnBlogPost;
+// off-logic mirroring Hugo tree.html for a page inside the About subtree.
+// A page is visible (not off) if it is current, ancestor-of-current,
+// sibling-of-current (only when current is not a section-index), or child-of-current.
+function aboutPageOff(page: NavPage, parentUrl: string): boolean {
+ const isCurrent = page.url === currentUrl;
+ const isAncestor = !isCurrent && inSubtree(page, currentUrl);
+ const isSibling = parentUrl === currentAboutParent && !currentAboutIsNode;
+ const isChild = parentUrl === currentUrl;
+ return !(isCurrent || isAncestor || isSibling || isChild);
+}
-const onCategoriesIndex = currentUrl === '/blog/categories/';
-const onCategoryTerm = currentUrl.startsWith('/categories/');
-const onTagsIndex = currentUrl === '/blog/tags/';
-const onTagTerm = currentUrl.startsWith('/tags/');
+// ── Blog URL classification ────────────────────────────────────────────────────
+const mainSections = [
+ { name: 'blog', url: '/blog/', title: 'Blog' },
+ { name: 'projects', url: '/projects/', title: 'Projects' },
+ { name: 'about', url: '/about.html', title: 'About' },
+];
-const blogSelected = onBlog || onArchive || onCategoriesIndex || onCategoryTerm || onTagsIndex || onTagTerm;
-const onAbout = currentUrl === '/about.html';
+const onBlog = currentUrl === '/blog/';
+const isOnBlogPost = allPosts.some(p => (p.data.url ?? `/${p.id}/`) === currentUrl);
+const onArchive = currentUrl.startsWith('/blog/archive/') || isOnBlogPost;
+const onCategoriesIndex = currentUrl === '/blog/categories/';
+const onCategoryTerm = currentUrl.startsWith('/categories/');
+const onTagsIndex = currentUrl === '/blog/tags/';
+const onTagTerm = currentUrl.startsWith('/tags/');
+const blogSelected = onBlog || onArchive || onCategoriesIndex || onCategoryTerm || onTagsIndex || onTagTerm;
-// Active state flags
const categoriesSelected = onCategoriesIndex || onCategoryTerm;
-const tagsSelected = onTagsIndex || onTagTerm;
+const tagsSelected = onTagsIndex || onTagTerm;
-// "off" class logic derived from Hugo nav output:
-// Archive/Categories/Tags are all off when not in blog context (e.g. homepage, About)
-// Within blog context: Archive is off on taxonomy term pages only
-// Categories/Tags are off on archive pages and the sibling taxonomy term
-const archiveOff = !blogSelected || onCategoryTerm || onTagTerm;
-const categoriesOff = !blogSelected || onArchive || onTagTerm;
-const tagsOff = !blogSelected || onArchive || onCategoryTerm;
+// off-class logic for Blog subtree items (derived from Hugo blog.html + tree.html)
+const archiveOff = !blogSelected || onCategoryTerm || onTagTerm;
+const categoriesOff = !blogSelected || onArchive || onTagTerm;
+const tagsOff = !blogSelected || onArchive || onCategoryTerm;
-// Determine active section
+// Active section for #menu highlight
const activeSection = mainSections.find(s => {
- if (s.name === 'blog') return blogSelected;
+ if (s.name === 'blog') return blogSelected;
+ if (s.name === 'about') return onAboutSection;
return currentUrl.startsWith(s.url);
})?.name ?? '';
-// Hugo blog.html: Blog always visible on blog pages, all other sections always off.
-// Hugo default.html + tree.html: on Homepage/regular-root-pages all sections visible
-// (child/sibling logic); on About section-index only About visible, Blog off.
-// When Projects is implemented, extend both: blogSectionOff |= onProjects, aboutOff |= onProjects.
-const blogSectionOff = onAbout;
-const aboutOff = blogSelected;
+// Top-level section off-logic (Hugo blog.html vs default.html + tree.html):
+// Blog section hidden when viewing any About-section page (and later: Projects pages).
+// About section hidden only when on Blog pages.
+const blogSectionOff = onAboutSection;
+const aboutOff = blogSelected;
-// Determine active year from currentUrl
+// Active year / taxonomy term helpers
function isActiveYear(year: string) {
return currentUrl === `/blog/archive/${year}/` ||
postsByYear[year]?.some(p => postUrl(p) === currentUrl) === true;
}
-
-// Active taxonomy term from URL
-const activeCatSlug = onCategoryTerm ? currentUrl.replace(/^\/categories\//, '').replace(/\/$/, '') : '';
-const activeTagSlug = onTagTerm ? currentUrl.replace(/^\/tags\//, '').replace(/\/$/, '') : '';
+const activeCatSlug = onCategoryTerm ? currentUrl.replace(/^\/categories\//, '').replace(/\/$/, '') : '';
+const activeTagSlug = onTagTerm ? currentUrl.replace(/^\/tags\//, '').replace(/\/$/, '') : '';
---
<nav id="nav">
<a class="s selected" href="/">Home</a>
</h2>
<ul id="submenu" class="s active">
- {/* Blog subtree */}
+
+ {/* ── Blog subtree ─────────────────────────────────────────────────────── */}
<li class={`s sub${blogSectionOff ? ' off' : ''}`}>
<a href="/blog/" class={blogSelected ? 's selected' : 's'}>
{blogSelected ? <strong>Blog</strong> : 'Blog'}
</a>
<ul class={blogSelected ? 's active' : 's'}>
- {/* Archive — only render subtree on blog/archive pages */}
+ {/* Archive — only render year/post subtree on blog/archive pages */}
<li class={`s sub${archiveOff ? ' off' : ''}`}>
<a href="/blog/archive/" class={onArchive ? 's selected' : 's'}>
{onArchive ? <strong>Archive</strong> : 'Archive'}
)}
</ul>
</li>
- {/* About entry */}
- <li class={`s${aboutOff ? ' off' : ''}`}>
- <a href="/about.html" class={onAbout ? 's selected' : 's'}>
- {onAbout ? <strong>About</strong> : 'About'}
+
+ {/* ── About subtree ────────────────────────────────────────────────────── */}
+ <li class={`s sub${aboutOff ? ' off' : ''}`}>
+ <a href="/about.html" class={onAboutSection ? 's selected' : 's'}>
+ {onAboutSection ? <strong>About</strong> : 'About'}
</a>
+ <ul class={`s${onAboutSection ? ' active' : ''}`}>
+ {aboutSection.children!.map(child => {
+ const childOff = aboutPageOff(child, '/about.html');
+ const childInPath = inSubtree(child, currentUrl);
+ const childHasSubs = (child.children ?? []).length > 0;
+ return (
+ <li class={`s${childHasSubs ? ' sub' : ''}${childOff ? ' off' : ''}`}>
+ <a href={child.url} class={childInPath ? 's selected' : 's'}>
+ {childInPath ? <strong>{child.title}</strong> : child.title}
+ </a>
+ {childHasSubs && (
+ <ul class={`s${childInPath ? ' active' : ''}`}>
+ {child.children!.map(grand => {
+ const grandOff = aboutPageOff(grand, child.url);
+ const grandActive = grand.url === currentUrl;
+ return (
+ <li class={`s${grandOff ? ' off' : ''}`}>
+ <a href={grand.url} class={grandActive ? 's selected' : 's'}>
+ {grandActive ? <strong>{grand.title}</strong> : grand.title}
+ </a>
+ </li>
+ );
+ })}
+ </ul>
+ )}
+ </li>
+ );
+ })}
+ </ul>
</li>
+
</ul>
<hr class="n"/>
</nav>