Files
pivoine.art/layouts/partials/header.html
Sebastian Krüger de5dacfa3b feat(ui): add responsive mobile menu with animated hamburger icon
- Add hamburger icon that morphs to X when menu is open
- Full-screen mobile menu overlay with backdrop blur
- Staggered fade-in animation for menu items
- Desktop navigation unchanged (hidden on mobile)
- Close menu via X button, backdrop click, or ESC key

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 00:51:08 +01:00

122 lines
3.9 KiB
HTML
Executable File

<div x-data="{ menuOpen: false }" @keydown.escape.window="menuOpen = false">
<header class="fixed top-0 left-0 right-0 z-sticky bg-surface-0/80 backdrop-blur-md border-b border-border">
<nav class="container-wide flex items-center justify-between h-16">
{{/* Logo */}}
<div class="flex items-center gap-3 group">
<canvas
id="logo-canvas"
hx-preserve="true"
class="w-8 h-8 cursor-pointer"
width="32"
height="32"
aria-hidden="true"
@click="window.__pivoine?.visualizer?.nextVisualizer()"
title="Click to change visualizer"
></canvas>
<a
href="{{ "/" | relURL }}"
class="text-lg font-medium tracking-tight group-hover:text-accent transition-colors"
aria-label="{{ .Site.Title }} - Home"
>
VALKNAR'S
</a>
</div>
{{/* Desktop Navigation */}}
<ul
class="hidden md:flex items-center gap-8"
x-data="{ path: window.location.pathname }"
@htmx:after-settle.window="path = window.location.pathname"
>
{{- range .Site.Menus.main }}
<li>
<a
href="{{ .URL }}"
class="text-sm transition-colors"
:class="path.startsWith('{{ .URL }}') ? 'text-text-primary border-b border-text-primary' : 'text-text-secondary hover:text-text-primary link-hover'"
>
{{ .Name }}
</a>
</li>
{{- end }}
</ul>
{{/* Mobile Hamburger Button */}}
<button
class="md:hidden relative w-8 h-8 flex items-center justify-center"
@click="menuOpen = !menuOpen"
:aria-expanded="menuOpen"
aria-label="Toggle menu"
>
<div class="hamburger" :class="{ 'is-active': menuOpen }">
<span></span>
<span></span>
<span></span>
</div>
</button>
</nav>
</header>
{{/* Mobile Menu Overlay - Full Screen */}}
<div
class="md:hidden fixed inset-0 z-[300]"
x-show="menuOpen"
x-cloak
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="transition ease-in duration-200"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
@click="menuOpen = false"
>
{{/* Backdrop */}}
<div class="absolute inset-0 bg-surface-0/98 backdrop-blur-xl"></div>
{{/* Close Button - Top right corner */}}
<div class="absolute top-0 right-0 h-16 container-wide flex items-center justify-end z-10">
<button
class="w-8 h-8 flex items-center justify-center"
@click.stop="menuOpen = false"
aria-label="Close menu"
>
<div class="hamburger is-active">
<span></span>
<span></span>
<span></span>
</div>
</button>
</div>
{{/* Menu Content */}}
<nav
class="relative h-full flex flex-col items-center justify-center"
x-data="{ path: window.location.pathname }"
@htmx:after-settle.window="path = window.location.pathname"
@click.stop
>
<ul class="flex flex-col items-center gap-8">
{{- range $i, $item := .Site.Menus.main }}
<li
class="transform transition-all duration-300 ease-out"
:class="menuOpen ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-4'"
style="transition-delay: {{ mul $i 50 }}ms"
>
<a
href="{{ .URL }}"
class="text-2xl font-medium tracking-wide transition-colors"
:class="path.startsWith('{{ .URL }}') ? 'text-text-primary' : 'text-text-secondary hover:text-text-primary'"
@click="menuOpen = false"
>
{{ .Name }}
</a>
</li>
{{- end }}
</ul>
</nav>
</div>
</div>
{{/* Spacer for fixed header */}}
<div class="h-16" aria-hidden="true"></div>