feat(header): icon nav redesign + migrate icons mdi→lucide
Deploy / verify (push) Successful in 1m26s
Deploy / deploy (push) Successful in 2m26s

- Header desktop nav: social icons grouped in a wald-tinted pill with
  labels, active-state (aria-current + bold), opacity hover (no per-item bg)
- Mobile overlay: social links as icon+label rows with active-state
- Search button: lucide:search, icon-only
- Migrate all icons mdi→lucide app-wide (234 refs); brand/language logos
  (google, whatsapp, mastodon, telegram, language-*) stay on mdi
- generate-icons: emit mdi + lucide subsets; add @iconify-json/lucide
- iconify-offline: register lucide collection
- scripts/icon-map|validate|apply: reusable migration tooling

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Peter Meier
2026-06-24 10:44:56 +02:00
parent e434ee3620
commit 0ab2c58eb4
46 changed files with 823 additions and 598 deletions
+63 -48
View File
@@ -149,7 +149,7 @@
aria-expanded="false"
>
{link.label}
<Icon icon="mdi:chevron-down" class="size-3" aria-hidden="true" />
<Icon icon="lucide:chevron-down" class="size-3" aria-hidden="true" />
</a>
{:else}
<button
@@ -159,7 +159,7 @@
aria-expanded="false"
>
{link.label}
<Icon icon="mdi:chevron-down" class="size-3" aria-hidden="true" />
<Icon icon="lucide:chevron-down" class="size-3" aria-hidden="true" />
</button>
{/if}
<div
@@ -193,43 +193,51 @@
</a>
{/if}
{/each}
<div class="w-1 h-4"></div>
{#each socialLinks as social}
{@const isInternal = social.href.startsWith("/")}
<a
href={social.href}
target={isInternal ? undefined : "_blank"}
rel={isInternal ? undefined : "noopener noreferrer"}
class="p-1.5 -m-1.5 rounded-xs transition-colors flex items-center justify-center"
aria-label={social.label || t(T.header_social_aria)}
title={social.label || t(T.header_social_aria)}
>
<Icon icon={social.icon} aria-hidden="true" />
</a>
{/each}
<button
type="button"
class="p-2 -mr-2 rounded-xs transition-colors flex items-center justify-center"
aria-label={searchOpen
? t(T.header_search_close)
: t(T.header_search_open)}
title={searchOpen
? t(T.header_search_close)
: t(T.header_search_open)}
aria-expanded={searchOpen}
aria-controls="search-panel"
onclick={() => (searchOpen = !searchOpen)}
<div
class="ml-2 flex items-center gap-0.5 rounded-full border border-wald-700/15 bg-wald-700/8 px-2 py-1"
>
{#if searchOpen}
<Icon
icon="mdi:close"
class="text-error!"
aria-hidden="true"
/>
{:else}
<Icon icon="mdi:magnify" aria-hidden="true" />
{/if}
</button>
{#each socialLinks as social}
{@const isInternal = social.href.startsWith("/")}
{@const sActive = isInternal && isActiveLink(social.href, $page.url.pathname)}
<a
href={social.href}
target={isInternal ? undefined : "_blank"}
rel={isInternal ? undefined : "noopener noreferrer"}
class="px-1.5 py-0.5 rounded-full transition-opacity hover:opacity-70 inline-flex items-center gap-1.5 {sActive ? 'font-bold' : ''}"
aria-current={sActive ? "page" : undefined}
aria-label={social.label || t(T.header_social_aria)}
title={social.label || t(T.header_social_aria)}
>
<Icon icon={social.icon} class="text-sm" aria-hidden="true" />
{#if social.label}
<span class="text-[.7rem] leading-none">{social.label}</span>
{/if}
</a>
{/each}
<button
type="button"
class="px-1.5 py-0.5 rounded-full transition-opacity hover:opacity-70 inline-flex items-center justify-center"
aria-label={searchOpen
? t(T.header_search_close)
: t(T.header_search_open)}
title={searchOpen
? t(T.header_search_close)
: t(T.header_search_open)}
aria-expanded={searchOpen}
aria-controls="search-panel"
onclick={() => (searchOpen = !searchOpen)}
>
{#if searchOpen}
<Icon
icon="lucide:x"
class="text-sm text-error!"
aria-hidden="true"
/>
{:else}
<Icon icon="lucide:search" class="text-sm" aria-hidden="true" />
{/if}
</button>
</div>
</nav>
<!-- Mobile: Such- und Burger-Buttons (unter lg) -->
@@ -246,13 +254,13 @@
>
{#if searchOpen}
<Icon
icon="mdi:close"
icon="lucide:x"
class="text-2xl text-error!"
aria-hidden="true"
/>
{:else}
<Icon
icon="mdi:magnify"
icon="lucide:search"
class="text-2xl"
aria-hidden="true"
/>
@@ -275,12 +283,12 @@
>
{#if menuOpen}
<Icon
icon="mdi:close"
icon="lucide:x"
class="text-2xl"
aria-hidden="true"
/>
{:else}
<Icon icon="mdi:menu" class="text-2xl" aria-hidden="true" />
<Icon icon="lucide:menu" class="text-2xl" aria-hidden="true" />
{/if}
</button>
</div>
@@ -314,7 +322,7 @@
onclick={() => toggleSubmenu(link.label)}
>
<span>{link.label}</span>
<Icon icon={expanded ? "mdi:chevron-up" : "mdi:chevron-down"} class="size-4" aria-hidden="true" />
<Icon icon={expanded ? "lucide:chevron-up" : "lucide:chevron-down"} class="size-4" aria-hidden="true" />
</button>
{#if expanded}
<div class="ml-6 border-l border-stein-0/10" in:slide={{ duration: 150 }} out:slide={{ duration: 100 }}>
@@ -357,27 +365,34 @@
{/if}
{/each}
{#if socialLinks.length > 0}
<div
class="flex flex-wrap gap-2 pt-2 mt-2 border-t border-stein-0/10"
>
<div class="flex flex-col gap-1 pt-2 mt-2 border-t border-stein-0/10">
{#each socialLinks as social}
{@const isInternal = social.href.startsWith("/")}
{@const sActive =
isInternal &&
isActiveLink(social.href, $page.url.pathname)}
<a
href={social.href}
target={isInternal ? undefined : "_blank"}
rel={isInternal
? undefined
: "noopener noreferrer"}
class="inline-flex p-2 text-stein-100 hover:text-stein-0 hover:bg-stein-0/10 rounded-xs transition-colors"
class="flex items-center gap-3 py-4 px-3 rounded-xs transition-colors text-base border-l-4 {sActive
? 'font-bold text-wald-300 bg-wald-700/20 border-wald-400'
: 'text-stein-100 hover:text-stein-0 hover:bg-stein-0/10 border-transparent'}"
aria-current={sActive ? "page" : undefined}
aria-label={social.label ||
t(T.header_social_aria)}
onclick={closeMenu}
>
<Icon
icon={social.icon}
class="text-[1.5rem]"
class="text-base"
aria-hidden="true"
/>
{#if social.label}
<span>{social.label}</span>
{/if}
</a>
{/each}
</div>