226 lines
8.3 KiB
Plaintext
226 lines
8.3 KiB
Plaintext
---
|
|
import { metaData, API_URL } from "../config";
|
|
import { Icon } from "astro-icon/components";
|
|
|
|
const isLoggedIn = Astro.cookies.get('access_token') || Astro.cookies.get('refresh_token');
|
|
|
|
const padlockIconSvg = `
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2.2" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M8.5 11V9a3.5 3.5 0 1 1 7 0v2" />
|
|
<rect x="7" y="11" width="10" height="8.5" rx="1.8" />
|
|
<circle cx="12" cy="15" r="1.2" fill="currentColor" />
|
|
</svg>
|
|
`;
|
|
|
|
const externalLinkIconSvg = `
|
|
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"></path>
|
|
</svg>
|
|
`;
|
|
|
|
const navItems = [
|
|
{ label: "Home", href: "/" },
|
|
{ label: "Radio", href: "/radio" },
|
|
{ label: "Memes", href: "/memes" },
|
|
{ label: "Lighting", href: "/lighting", auth: true, icon: "padlock" },
|
|
{ label: "TRip", href: "/TRip", auth: true, icon: "padlock" },
|
|
{ label: "Status", href: "https://status.boatson.boats", icon: "external" },
|
|
{ label: "Git", href: "https://kode.boatson.boats", icon: "external" },
|
|
...(isLoggedIn ? [{ label: "Logout", href: "#logout", onclick: "handleLogout()" }] : []),
|
|
];
|
|
|
|
const currentPath = Astro.url.pathname;
|
|
|
|
---
|
|
|
|
<script src="/scripts/nav-controls.js" defer data-api-url={API_URL}></script>
|
|
|
|
<nav class="w-full px-4 sm:px-6 py-4 bg-transparent sticky top-0 z-50 backdrop-blur-sm bg-white/80 dark:bg-[#121212]/80 border-b border-neutral-200/50 dark:border-neutral-800/50">
|
|
<div class="max-w-7xl mx-auto">
|
|
<div class="flex items-center justify-between">
|
|
<!-- Logo/Brand -->
|
|
<a
|
|
href="/"
|
|
class="text-xl sm:text-2xl font-semibold header-text whitespace-nowrap hover:opacity-80 transition-opacity"
|
|
>
|
|
{metaData.title}
|
|
</a>
|
|
|
|
<!-- Desktop Navigation -->
|
|
<div class="desktop-nav flex items-center gap-0.5">
|
|
<ul class="flex items-center gap-0.5">
|
|
{navItems.map((item) => {
|
|
const isExternal = item.href?.startsWith("http");
|
|
const isAuthedPath = item.auth ?? false;
|
|
const normalize = (url) => (url || '/').replace(/\/+$/, '') || '/';
|
|
const normalizedCurrent = normalize(currentPath);
|
|
const normalizedHref = normalize(item.href);
|
|
const isActive = !isExternal && (
|
|
normalizedHref === '/'
|
|
? normalizedCurrent === '/'
|
|
: normalizedCurrent === normalizedHref || normalizedCurrent.startsWith(normalizedHref + '/')
|
|
);
|
|
|
|
return (
|
|
<li>
|
|
<a
|
|
href={item.href}
|
|
class={isActive
|
|
? "flex items-center gap-0 px-2.5 py-1.5 rounded-md text-xs font-medium transition-all duration-200 bg-neutral-900 dark:bg-neutral-100 text-white dark:text-neutral-900"
|
|
: "flex items-center gap-0 px-2.5 py-1.5 rounded-md text-xs font-medium transition-all duration-200 text-neutral-700 dark:text-neutral-300 hover:bg-neutral-100 dark:hover:bg-neutral-800"
|
|
}
|
|
target={isExternal ? "_blank" : undefined}
|
|
rel={(isExternal || isAuthedPath) ? "external" : undefined}
|
|
onclick={item.onclick}
|
|
>
|
|
{item.label}
|
|
{item.icon === "external" && (
|
|
<span class="inline-flex ml-0.5" aria-hidden="true" set:html={externalLinkIconSvg}></span>
|
|
)}
|
|
{item.icon === "padlock" && (
|
|
<span class="inline-flex" aria-hidden="true" set:html={padlockIconSvg}></span>
|
|
)}
|
|
</a>
|
|
</li>
|
|
);
|
|
})}
|
|
</ul>
|
|
|
|
<!-- Theme Toggle Desktop -->
|
|
<button
|
|
aria-label="Toggle theme"
|
|
type="button"
|
|
class="flex items-center justify-center w-8 h-8 ml-1 rounded-md hover:bg-neutral-100 dark:hover:bg-neutral-800 transition-colors"
|
|
onclick="toggleTheme()"
|
|
>
|
|
<Icon
|
|
name="fa6-solid:circle-half-stroke"
|
|
class="h-4 w-4 text-[#1c1c1c] dark:text-[#D4D4D4]"
|
|
/>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Mobile Menu Button -->
|
|
<div class="mobile-nav flex items-center gap-2">
|
|
<!-- Theme Toggle Mobile (visible) -->
|
|
<button
|
|
aria-label="Toggle theme"
|
|
type="button"
|
|
class="flex items-center justify-center w-9 h-9 rounded-lg hover:bg-neutral-100 dark:hover:bg-neutral-800 transition-colors"
|
|
onclick="toggleTheme()"
|
|
>
|
|
<Icon
|
|
name="fa6-solid:circle-half-stroke"
|
|
class="h-5 w-5 text-[#1c1c1c] dark:text-[#D4D4D4]"
|
|
/>
|
|
</button>
|
|
|
|
<button
|
|
id="mobile-menu-btn"
|
|
aria-label="Toggle menu"
|
|
type="button"
|
|
class="flex items-center justify-center w-9 h-9 rounded-lg hover:bg-neutral-100 dark:hover:bg-neutral-800 transition-colors"
|
|
>
|
|
<svg id="menu-icon" class="w-6 h-6 text-neutral-700 dark:text-neutral-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
|
|
</svg>
|
|
<svg id="close-icon" class="w-6 h-6 text-neutral-700 dark:text-neutral-300" style="display: none;" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Mobile Navigation Menu -->
|
|
<div
|
|
id="mobile-menu"
|
|
class="mobile-menu-dropdown md:hidden"
|
|
>
|
|
<ul class="flex flex-col gap-1 py-4">
|
|
{navItems.map((item) => {
|
|
const isExternal = item.href?.startsWith("http");
|
|
const isAuthedPath = item.auth ?? false;
|
|
const normalize = (url) => (url || '/').replace(/\/+$/, '') || '/';
|
|
const normalizedCurrent = normalize(currentPath);
|
|
const normalizedHref = normalize(item.href);
|
|
const isActive = !isExternal && (
|
|
normalizedHref === '/'
|
|
? normalizedCurrent === '/'
|
|
: normalizedCurrent === normalizedHref || normalizedCurrent.startsWith(normalizedHref + '/')
|
|
);
|
|
|
|
return (
|
|
<li>
|
|
<a
|
|
href={item.href}
|
|
class={isActive
|
|
? "flex items-center gap-0 px-4 py-3 rounded-lg text-base font-medium transition-all duration-200 bg-neutral-900 dark:bg-neutral-100 text-white dark:text-neutral-900"
|
|
: "flex items-center gap-0 px-4 py-3 rounded-lg text-base font-medium transition-all duration-200 text-neutral-700 dark:text-neutral-300 hover:bg-neutral-100 dark:hover:bg-neutral-800"
|
|
}
|
|
target={isExternal ? "_blank" : undefined}
|
|
rel={(isExternal || isAuthedPath) ? "external" : undefined}
|
|
onclick={item.onclick}
|
|
>
|
|
<span>{item.label}</span>
|
|
{item.icon === "external" && (
|
|
<span class="inline-flex ml-0.5" aria-hidden="true" set:html={externalLinkIconSvg}></span>
|
|
)}
|
|
{item.icon === "padlock" && (
|
|
<span class="inline-flex" aria-hidden="true" set:html={padlockIconSvg}></span>
|
|
)}
|
|
</a>
|
|
</li>
|
|
);
|
|
})}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
|
|
<style>
|
|
/* Desktop navigation - visible on medium screens and up */
|
|
.desktop-nav {
|
|
display: none;
|
|
}
|
|
|
|
@media (min-width: 768px) {
|
|
.desktop-nav {
|
|
display: flex;
|
|
}
|
|
}
|
|
|
|
/* Mobile navigation - visible below medium screens */
|
|
.mobile-nav {
|
|
display: flex;
|
|
}
|
|
|
|
@media (min-width: 768px) {
|
|
.mobile-nav {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
/* Mobile menu dropdown */
|
|
.mobile-menu-dropdown {
|
|
max-height: 0;
|
|
overflow: hidden;
|
|
transition: max-height 0.3s ease-in-out, opacity 0.3s ease-in-out;
|
|
opacity: 0;
|
|
}
|
|
|
|
.mobile-menu-dropdown.open {
|
|
max-height: 500px;
|
|
opacity: 1;
|
|
}
|
|
|
|
@media (min-width: 768px) {
|
|
.mobile-menu-dropdown {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
nav {
|
|
transition: background-color 0.2s ease, border-color 0.2s ease;
|
|
}
|
|
</style>
|