Files
jleibl.net/src/components/layout/Header.astro

222 lines
6.3 KiB
Plaintext

---
import MobileMenu from "./MobileMenu.astro";
---
<header
class="fixed top-0 left-0 right-0 z-40 nav-glass backdrop-blur-lg theme-transition"
role="banner"
>
<div class="max-w-7xl mx-auto">
<nav
class="py-4 px-6 sm:px-8 flex justify-between items-center font-['Instrument_Sans']"
role="navigation"
aria-label="Main navigation"
>
<div class="flex items-center">
<a href="#hero" class="flex items-center gap-2.5 group" aria-label="Home">
<div
class="relative w-9 h-9 rounded-xl theme-accent flex items-center justify-center border theme-border overflow-hidden group-hover:border-opacity-80 transition-all duration-300 shadow-sm"
>
<span class="text-base font-bold tracking-tight theme-primary theme-transition">JL</span
>
<div
class="absolute inset-0 theme-bg-05 opacity-0 group-hover:opacity-100 transition-opacity duration-500"
>
</div>
</div>
<span
class="text-base font-medium tracking-tight theme-primary theme-transition hidden sm:block"
>
Jan-Marlon Leibl
</span>
</a>
</div>
<div class="hidden md:flex items-center">
<div
class="flex bg-black/5 backdrop-blur-md overflow-hidden theme-border border divide-x divide-white/5 shadow-sm rounded-xl"
>
{
["About", "Work", "Contact"].map((item, index) => (
<a
href={`#${item.toLowerCase()}`}
class={`nav-item px-5 py-2 theme-secondary hover:theme-primary theme-transition ${index === 0 ? "rounded-l-xl" : ""} ${index === 2 ? "rounded-r-xl" : ""}`}
aria-label={`View my ${item.toLowerCase()}`}
>
{item}
</a>
))
}
</div>
<div class="flex items-center gap-4 ml-6">
<a
href="mailto:jleibl@proton.me"
class="flex items-center gap-2 px-5 py-2 theme-bg-05 theme-primary border theme-border rounded-xl hover:bg-opacity-100 transition-all duration-300 group shadow-sm"
aria-label="Contact me via email"
>
<svg
class="w-4 h-4 theme-primary"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path
d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
></path>
</svg>
<span class="text-sm font-medium">Contact</span>
<div class="w-0 overflow-hidden group-hover:w-4 transition-all duration-300">
<svg
class="w-4 h-4 theme-primary"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M5 12h14M12 5l7 7-7 7"></path>
</svg>
</div>
</a>
</div>
</div>
<div class="flex md:hidden">
<MobileMenu />
</div>
</nav>
</div>
</header>
<script>
document.addEventListener("DOMContentLoaded", () => {
const header = document.querySelector("header");
if (!header) return;
header.style.transform = "translateY(0)";
header.style.opacity = "1";
let lastScrollY = window.scrollY;
const scrollThreshold = 100;
let isHeaderVisible = true;
const sections = document.querySelectorAll("section[id]");
const navItems = document.querySelectorAll(".nav-item");
function setActiveNavItem() {
const scrollPosition = window.scrollY + 200;
navItems.forEach((item) => {
item.classList.remove("nav-item-active");
item.removeAttribute("aria-current");
});
let currentSection: string | null = null;
sections.forEach((section) => {
const sectionTop = section.getBoundingClientRect().top + window.scrollY;
const sectionHeight = section.getBoundingClientRect().height;
if (scrollPosition >= sectionTop && scrollPosition < sectionTop + sectionHeight) {
currentSection = section.getAttribute("id");
}
});
if (currentSection) {
navItems.forEach((item) => {
const href = item.getAttribute("href")?.substring(1);
if (href === currentSection) {
item.classList.add("nav-item-active");
item.setAttribute("aria-current", "true");
}
});
}
}
window.addEventListener("scroll", () => {
const currentScrollY = window.scrollY;
if (currentScrollY > 20) {
header.classList.add("scrolled");
} else {
header.classList.remove("scrolled");
}
lastScrollY = currentScrollY;
setActiveNavItem();
});
setActiveNavItem();
navItems.forEach((item) => {
item.addEventListener("keydown", (e) => {
if (e instanceof KeyboardEvent && (e.key === "Enter" || e.key === " ")) {
e.preventDefault();
item.dispatchEvent(new MouseEvent("click"));
}
});
});
});
</script>
<style>
header {
transform: translateY(-100px);
opacity: 0;
transition:
transform 0.4s ease-out,
opacity 0.5s ease-out,
border 0.3s ease;
}
header.scrolled {
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}
.nav-item {
position: relative;
overflow: hidden;
font-weight: 500;
transition: all 0.2s ease;
}
.nav-item::after {
content: "";
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 2px;
transform: translateX(-100%);
transition: transform 0.3s cubic-bezier(0.22, 1, 0.36, 1);
}
.black .nav-item::after {
background-color: rgba(255, 255, 255, 0.8);
}
.red .nav-item::after {
background-color: rgba(248, 113, 113, 0.8);
}
.gold .nav-item::after {
background-color: rgba(251, 191, 36, 0.8);
}
.nav-item:hover::after {
transform: translateX(0);
}
.nav-item-active {
background-color: rgba(255, 255, 255, 0.08);
}
.nav-item-active::after {
transform: translateX(0);
}
</style>