refactor(nav): redesign mobile nav to match desktop

remove mobile header bar and replace with floating hamburger.
restyle mobile nav links with icons, active states, compact spacing.
integrate theme toggle into nav flow, remove redundant cta button.

- replace full-width header with floating top-right hamburger
- add icons to mobile nav links (matching desktop)
- reduce text size (text-3xl -> text-base) and spacing (gap-8 -> gap-3)
- implement active state indicators using isActive()
- add hover states with proper theme variables
- move theme toggle from decorative area into main nav with divider
- remove "let's talk" cta button (redundant with contact link)
This commit is contained in:
Nicholai Vogel 2026-01-20 08:03:18 -07:00
parent 4c8a6c2e0c
commit c688207865
2 changed files with 74 additions and 75 deletions

View File

@ -1,5 +1,5 @@
{ {
"generatedAt": "2026-01-20T13:58:29.727Z", "generatedAt": "2026-01-20T14:58:35.282Z",
"totalFiles": 143, "totalFiles": 143,
"totalSize": 3237922, "totalSize": 3237922,
"days": [ "days": [

View File

@ -88,28 +88,22 @@ function isActive(href: string): boolean {
</div> </div>
</nav> </nav>
<!-- Mobile Navigation: Top bar with hamburger (< lg) --> <!-- Mobile Navigation: Floating hamburger button (< lg) -->
<nav class="lg:hidden fixed top-0 left-0 w-full z-50 px-6 py-6 flex justify-between items-center backdrop-blur-md bg-[var(--theme-overlay)] border-b border-[var(--theme-border-secondary)]"> <button
<!-- Left side - branding --> id="mobile-menu-toggle"
<a href="/" class="text-[10px] font-mono text-[var(--theme-text-muted)] tracking-widest uppercase hover:text-brand-accent transition-colors duration-300">NV / 2026</a> class="lg:hidden fixed top-6 right-6 z-50 p-3 bg-[var(--theme-overlay)] backdrop-blur-md border border-[var(--theme-border-primary)] rounded-xl text-[var(--theme-text-muted)] hover:text-[var(--theme-text-primary)] transition-all duration-200 shadow-sm"
aria-label="Toggle menu"
<!-- Mobile menu button --> aria-expanded="false"
<button >
id="mobile-menu-toggle" <!-- Hamburger icon -->
class="p-2 text-[var(--theme-text-muted)] hover:text-[var(--theme-text-primary)] transition-colors z-[60]" <svg id="menu-icon-open" class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
aria-label="Toggle menu" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4 6h16M4 12h16M4 18h16"></path>
aria-expanded="false" </svg>
> <!-- Close icon (hidden by default) -->
<!-- Hamburger icon --> <svg id="menu-icon-close" class="w-6 h-6 hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg id="menu-icon-open" class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M6 18L18 6M6 6l12 12"></path>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4 6h16M4 12h16M4 18h16"></path> </svg>
</svg> </button>
<!-- Close icon (hidden by default) -->
<svg id="menu-icon-close" class="w-6 h-6 hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</nav>
<!-- Mobile Menu Overlay --> <!-- Mobile Menu Overlay -->
<div <div
@ -118,62 +112,67 @@ function isActive(href: string): boolean {
> >
<!-- Menu Content --> <!-- Menu Content -->
<div class="flex flex-col justify-center items-center h-full px-8"> <div class="flex flex-col justify-center items-center h-full px-8">
<!-- Navigation Links --> <nav class="flex flex-col items-center gap-3 w-full max-w-xs">
<nav class="flex flex-col items-center gap-8 mb-12"> {navItems.map((item) => (
<a <a
href="/" href={item.href}
class="mobile-nav-link text-3xl font-bold uppercase tracking-wider text-[var(--theme-text-primary)] hover:text-brand-accent transition-colors duration-300" class:list={[
> "mobile-nav-link flex flex-col items-center gap-2 px-6 py-3 rounded-xl transition-all duration-200 w-full",
Home isActive(item.href)
</a> ? "bg-brand-accent/10 text-brand-accent"
<a : "text-[var(--theme-text-muted)] hover:text-[var(--theme-text-primary)] hover:bg-[var(--theme-hover-bg-strong)]"
href="/dev" ]}
class="mobile-nav-link text-3xl font-bold uppercase tracking-wider text-[var(--theme-text-primary)] hover:text-brand-accent transition-colors duration-300" >
> {/* Home icon */}
Dev {item.icon === 'home' && (
</a> <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<a <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
href="/blog" <polyline points="9 22 9 12 15 12 15 22"></polyline>
class="mobile-nav-link text-3xl font-bold uppercase tracking-wider text-[var(--theme-text-primary)] hover:text-brand-accent transition-colors duration-300" </svg>
> )}
Blog {/* Code icon */}
</a> {item.icon === 'code' && (
<!-- temporarily disabled <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<a <polyline points="16 18 22 12 16 6"></polyline>
href="/hubert" <polyline points="8 6 2 12 8 18"></polyline>
class="mobile-nav-link text-3xl font-bold uppercase tracking-wider text-[var(--theme-text-primary)] hover:text-brand-accent transition-colors duration-300" </svg>
> )}
Hubert {/* File/Blog icon */}
</a> {item.icon === 'file' && (
--> <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<a <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
href="/contact" <polyline points="14 2 14 8 20 8"></polyline>
class="mobile-nav-link text-3xl font-bold uppercase tracking-wider text-[var(--theme-text-primary)] hover:text-brand-accent transition-colors duration-300" <line x1="16" y1="13" x2="8" y2="13"></line>
> <line x1="16" y1="17" x2="8" y2="17"></line>
Contact <polyline points="10 9 9 9 8 9"></polyline>
</a> </svg>
</nav> )}
{/* Chat icon */}
{item.icon === 'chat' && (
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
</svg>
)}
{/* Mail icon */}
{item.icon === 'mail' && (
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
<polyline points="22,6 12,13 2,6"></polyline>
</svg>
)}
<!-- CTA Button --> <span class="text-base font-medium uppercase tracking-wider">{item.label}</span>
<a </a>
href="/contact" ))}
class="border border-brand-accent px-8 py-4 text-sm font-bold uppercase tracking-[0.2em] text-brand-accent hover:bg-brand-accent hover:text-brand-dark transition-all duration-300 mb-8 rounded-full"
>
Let's Talk
</a>
<!-- Decorative Elements --> {/* Divider */}
<div class="absolute bottom-12 left-8 right-8 flex justify-between items-center"> <div class="w-16 h-px bg-[var(--theme-border-primary)] my-4"></div>
<div class="flex flex-col gap-2">
<div class="text-[10px] font-mono text-[var(--theme-text-muted)] uppercase tracking-widest"> {/* Theme Toggle */}
NV / 2026 <div class="nav-theme-toggle flex items-center justify-center">
</div>
<ThemeToggle /> <ThemeToggle />
</div> </div>
<div class="text-[10px] font-mono text-[var(--theme-text-muted)] uppercase tracking-widest self-end"> </nav>
Menu
</div>
</div>
</div> </div>
</div> </div>