managarten/apps-archived/bauntown/apps/landing/src/components/Navigation.astro
Till-JS 61d181fbc2 chore: archive inactive projects to apps-archived/
Move inactive projects out of active workspace:
- bauntown (community website)
- maerchenzauber (AI story generation)
- memoro (voice memo app)
- news (news aggregation)
- nutriphi (nutrition tracking)
- reader (reading app)
- uload (URL shortener)
- wisekeep (AI wisdom extraction)

Update CLAUDE.md documentation:
- Add presi to active projects
- Document archived projects section
- Update workspace configuration

Archived apps can be re-activated by moving back to apps/

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 07:03:59 +01:00

717 lines
17 KiB
Text

---
import { getLangFromUrl, useTranslations } from '../utils/i18n';
import ThemeToggle from './ThemeToggle.astro';
import LanguageSwitcher from './LanguageSwitcher.astro';
const lang = getLangFromUrl(Astro.url);
const t = useTranslations(lang);
const pathname = Astro.url.pathname;
// URLs with language prefix - use explicit /de/ for German too
const homeUrl = `/${lang}`;
const modelsUrl = `/${lang}/models`;
const projectsUrl = `/${lang}/projects`;
const tutorialsUrl = `/${lang}/tutorials`;
const toolsUrl = `/${lang}/tools`;
const visionUrl = `/${lang}/vision`;
const joinUrl = `/${lang}/join`;
const supportUrl = `/${lang}/support`;
const membersUrl = `/${lang}/members`;
// Function to check if the current page matches a given path
const isActive = (path: string) => {
if (path === homeUrl) {
// Special case for home: only match exactly
return pathname === homeUrl || pathname === `${homeUrl}/`;
}
// For other pages, check if the pathname starts with the path
return pathname.startsWith(path);
};
---
<!-- Desktop Navigation -->
<nav class="desktop-nav">
<div class="navbar">
<div class="logo">
<a href={homeUrl}>
<svg
width="24"
height="24"
viewBox="0 0 12 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="nav-logo-svg"
><rect
x="0.666667"
y="7.33334"
width="10.6667"
height="2.66667"
stroke="currentColor"
stroke-width="0.8"
fill="none"></rect><rect
x="2"
y="4.66666"
width="8"
height="2.66667"
stroke="currentColor"
stroke-width="0.8"
fill="none"></rect><rect
x="3.33333"
y="2"
width="5.33333"
height="2.66667"
stroke="currentColor"
stroke-width="0.8"
fill="none"></rect></svg
>
<span class="logo-text">BaunTown</span>
</a>
</div>
<div class="nav-container">
<div class="nav-links">
<a href={tutorialsUrl} class:list={[{ active: isActive(tutorialsUrl) }]}
>{t('nav.tutorials')}</a
>
<a href={toolsUrl} class:list={[{ active: isActive(toolsUrl) }]}>{t('nav.tools')}</a>
<a href={modelsUrl} class:list={[{ active: isActive(modelsUrl) }]}>{t('nav.models')}</a>
<a href={joinUrl} class:list={[{ active: isActive(joinUrl) }]}>{t('nav.join')}</a>
</div>
<div class="nav-controls">
<LanguageSwitcher dropdownStyle={true} />
<ThemeToggle />
</div>
</div>
</div>
</nav>
<!-- Mobile Bottom Navigation -->
<nav class="mobile-tab-bar">
<a href={homeUrl} class:list={[{ active: isActive(homeUrl) }]}>
<svg
width="24"
height="24"
viewBox="0 0 12 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="mobile-logo-svg"
><rect
x="0.666667"
y="7.33334"
width="10.6667"
height="2.66667"
stroke="currentColor"
stroke-width="0.8"
fill="none"></rect><rect
x="2"
y="4.66666"
width="8"
height="2.66667"
stroke="currentColor"
stroke-width="0.8"
fill="none"></rect><rect
x="3.33333"
y="2"
width="5.33333"
height="2.66667"
stroke="currentColor"
stroke-width="0.8"
fill="none"></rect></svg
>
<span>BaunTown</span>
</a>
<a href={tutorialsUrl} class:list={[{ active: isActive(tutorialsUrl) }]}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1 0-5H20"></path>
</svg>
<span>{t('nav.tutorials')}</span>
</a>
<a href={toolsUrl} class:list={[{ active: isActive(toolsUrl) }]}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path
d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"
></path>
</svg>
<span>{t('nav.tools')}</span>
</a>
<a href={modelsUrl} class:list={[{ active: isActive(modelsUrl) }]}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path
d="M4 22h16a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v16a2 2 0 0 1-2 2Zm0 0a2 2 0 0 1-2-2v-9c0-1.1.9-2 2-2h2"
></path>
<path d="M18 14h-8"></path>
<path d="M15 18h-5"></path>
<path d="M10 6h8v4h-8V6Z"></path>
</svg>
<span>{t('nav.models')}</span>
</a>
<button id="mobile-menu-toggle">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<line x1="4" x2="20" y1="12" y2="12"></line>
<line x1="4" x2="20" y1="6" y2="6"></line>
<line x1="4" x2="20" y1="18" y2="18"></line>
</svg>
<span>Menu</span>
</button>
</nav>
<!-- Mobile Side Menu -->
<div class="mobile-menu">
<div class="mobile-menu-header">
<svg
width="32"
height="32"
viewBox="0 0 12 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="mobile-logo-svg"
style="display:block;margin:0 auto 1rem auto;"
><rect
x="0.666667"
y="7.33334"
width="10.6667"
height="2.66667"
stroke="currentColor"
stroke-width="0.8"
fill="none"></rect><rect
x="2"
y="4.66666"
width="8"
height="2.66667"
stroke="currentColor"
stroke-width="0.8"
fill="none"></rect><rect
x="3.33333"
y="2"
width="5.33333"
height="2.66667"
stroke="currentColor"
stroke-width="0.8"
fill="none"></rect></svg
>
<button id="mobile-menu-close">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M18 6 6 18"></path>
<path d="m6 6 12 12"></path>
</svg>
</button>
<div class="mobile-logo">
<svg
width="40"
height="40"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M18.5714 13.4286V18.5714H5.42857L5.42857 13.4286H2V18.5714V22H5.42857H18.5714H22V18.5714V13.4286H18.5714ZM18.5714 10.5714L22 10.5714V5.42857V2H18.5714H5.42857H2V5.42857V10.5714H5.42857L5.42857 5.42857L18.5714 5.42857V10.5714Z"
></path>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M16.2857 13.4286L7.71428 13.4286V16.2857H11.1429H12.8571H16.2857V13.4286ZM16.2857 7.71429V10.5714L7.71428 10.5714L7.71428 7.71429H11.1429H12.8571H16.2857Z"
></path>
</svg>
</div>
</div>
<div class="mobile-menu-links">
<a href={homeUrl} class:list={[{ active: isActive(homeUrl) }]}>BaunTown</a>
<a href={tutorialsUrl} class:list={[{ active: isActive(tutorialsUrl) }]}>{t('nav.tutorials')}</a
>
<a href={toolsUrl} class:list={[{ active: isActive(toolsUrl) }]}>{t('nav.tools')}</a>
<a href={modelsUrl} class:list={[{ active: isActive(modelsUrl) }]}>{t('nav.models')}</a>
<a href={projectsUrl} class:list={[{ active: isActive(projectsUrl) }]}>{t('nav.projects')}</a>
<a href={visionUrl} class:list={[{ active: isActive(visionUrl) }]}>{t('nav.vision')}</a>
<a href={joinUrl} class:list={[{ active: isActive(joinUrl) }]}>{t('nav.join')}</a>
<a href={supportUrl} class:list={[{ active: isActive(supportUrl) }]}>{t('nav.support')}</a>
</div>
<div class="mobile-menu-controls">
<LanguageSwitcher dropdownStyle={true} />
<ThemeToggle />
</div>
</div>
<!-- Overlay for mobile menu -->
<div class="overlay" id="overlay"></div>
<style>
/* Common Navigation Styles */
.nav-logo-svg {
height: 24px;
width: auto;
fill: var(--text-color);
}
/* Desktop Navigation */
.desktop-nav {
position: fixed;
top: 0;
left: 0;
right: 0;
width: 100%;
height: 64px; /* Fixed height for calculations */
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
background-color: rgba(var(--background-color-rgb, 255, 255, 255), 0.85);
border-bottom: 1px solid rgba(var(--border-color-rgb, 229, 231, 235), 0.5);
transition:
background-color 0.3s ease,
border-color 0.3s ease;
overflow: visible; /* Allow dropdowns to extend outside nav */
z-index: 1000;
}
.navbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem 1.5rem;
max-width: 1200px;
margin: 0 auto;
}
.logo a {
display: flex;
align-items: center;
gap: 0.5rem;
text-decoration: none;
}
.logo-text {
font-size: 1.25rem;
font-weight: 600;
color: var(--text-color);
letter-spacing: 0.05em;
}
.nav-container {
display: flex;
align-items: center;
justify-content: center;
flex-grow: 1;
position: relative;
overflow: visible; /* Ensure dropdowns can appear outside the container */
}
.nav-links {
position: absolute;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 2rem;
justify-content: center;
}
.nav-links a {
color: var(--text-color);
text-decoration: none;
padding: 0.5rem;
font-weight: 500;
position: relative;
}
.nav-links a:hover,
.nav-links a.active {
text-decoration: none;
}
.nav-links a::after {
content: '';
position: absolute;
width: 0;
height: 2px;
bottom: 0;
left: 0.5rem;
right: 0.5rem;
background-color: var(--accent-color);
transition: width 0.3s ease;
}
.nav-links a:hover::after,
.nav-links a.active::after {
width: calc(100% - 1rem);
}
.nav-links a.active {
color: var(--accent-color);
}
.nav-controls {
display: flex;
align-items: center;
gap: 1rem;
position: absolute;
right: 0;
z-index: 100; /* Ensure controls appear above other elements */
}
/* Mobile Tab Bar */
.mobile-tab-bar {
display: none;
position: fixed;
left: 0;
right: 0;
background-color: var(--background-color);
padding: 0.5rem 0.25rem;
z-index: 1000; /* Ensure it's above other elements */
overflow: hidden;
}
.mobile-tab-bar {
display: none;
grid-template-columns: repeat(5, 1fr);
gap: 0.25rem;
}
.mobile-tab-bar a,
.mobile-tab-bar button {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 0.5rem 0.25rem;
color: var(--text-muted);
text-decoration: none;
font-size: 0.7rem;
font-weight: 500;
border-radius: 0.5rem;
transition: background-color 0.2s ease;
background: none;
border: none;
cursor: pointer;
}
.mobile-tab-bar a.active,
.mobile-tab-bar button.active {
color: var(--accent-color);
position: relative;
font-weight: 600;
}
.mobile-tab-bar a.active::after,
.mobile-tab-bar button.active::after {
content: '';
position: absolute;
width: 60%;
height: 2px;
bottom: 4px;
left: 20%;
background-color: var(--accent-color);
}
/* For non-touch desktop, align items in center */
@media (hover: hover) and (max-width: 768px) {
.mobile-tab-bar a,
.mobile-tab-bar button {
align-items: center;
flex-direction: row;
gap: 0.5rem;
height: 48px; /* Fixed height */
padding: 0.5rem;
}
.mobile-tab-bar svg {
margin-bottom: 0;
}
}
.mobile-tab-bar a.coffee-btn {
font-size: 0.65rem;
}
.mobile-tab-bar a.coffee-btn svg {
color: var(--accent-color);
}
.mobile-tab-bar a.active .nav-logo-svg {
fill: var(--accent-color);
}
.mobile-tab-bar svg {
width: 22px;
height: 22px;
margin-bottom: 0.25rem;
}
/* Mobile Side Menu */
.mobile-menu {
position: fixed;
top: 0;
right: 0;
bottom: 0;
width: 85%;
background-color: var(--background-color);
z-index: 1001; /* Above mobile tab bar */
box-shadow: -5px 0 15px rgba(0, 0, 0, 0.1);
display: none;
flex-direction: column;
overflow-y: auto;
overflow-x: visible; /* Allow language dropdown to be visible outside menu */
padding: 1.5rem;
}
.mobile-menu.active {
display: flex;
}
.mobile-menu-header {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 1.5rem;
margin-bottom: 1.5rem;
}
#mobile-menu-close {
background: none;
border: none;
cursor: pointer;
padding: 0.5rem;
color: var(--text-color);
}
.mobile-logo svg {
fill: var(--text-color);
}
.mobile-menu-links {
display: flex;
flex-direction: column;
margin-bottom: 2rem;
}
.mobile-menu-links a {
padding: 1rem 0;
border-bottom: 1px solid var(--border-color);
color: var(--text-color);
text-decoration: none;
font-weight: 500;
font-size: 1.25rem;
}
.mobile-menu-links a.active {
color: var(--accent-color);
}
.mobile-menu-controls {
margin-top: auto;
display: flex;
flex-direction: column;
gap: 1.5rem;
z-index: 1100; /* Higher z-index for language switcher in mobile menu */
position: relative;
overflow: visible; /* Ensure dropdowns are visible */
margin-bottom: 200px; /* Plenty of space at bottom for dropdown */
padding-bottom: 1rem;
}
.overlay {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000; /* Same level as navigation */
}
.overlay.active {
display: block;
}
/* Dark mode fixes */
.nav-logo-svg,
.mobile-logo-svg,
.hero-logo-svg {
color: #111;
transition: color 0.2s;
}
:root.dark .nav-logo-svg,
:root.dark .mobile-logo-svg,
:root.dark .hero-logo-svg {
color: #fff;
}
/* Set the logo to accent color when active in both light and dark mode */
.mobile-tab-bar a.active .nav-logo-svg {
fill: var(--accent-color) !important;
}
:global(:root.dark) .mobile-menu-links a {
color: #f9fafb;
}
:global(:root.dark) .mobile-menu-links a.active {
color: var(--accent-color);
}
/* Media Queries */
@media (max-width: 768px) {
.desktop-nav {
display: none;
}
/* Only show mobile tab bar on touch devices */
@media (hover: none) and (pointer: coarse) {
.mobile-tab-bar {
display: grid;
position: fixed;
bottom: 0; /* Position at the bottom of screen */
z-index: 1050; /* Make sure it's above most content but below page-header */
}
/* Add bottom padding to main content to account for tab bar and filter */
:global(body) {
padding-bottom: 130px; /* Ensure enough space for the tab bar and filter */
}
}
/* For non-touch devices with small viewport, position at top */
@media (hover: hover) {
.mobile-tab-bar {
display: grid;
position: fixed;
top: 0;
bottom: unset;
border-top: none;
border-bottom: 1px solid var(--border-color);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
height: 64px;
z-index: 1100; /* Higher than other content */
}
/* Add top padding to main content to account for tab bar */
:global(body) {
padding-top: 120px; /* Even more top padding for small desktop viewports */
padding-bottom: 0;
}
/* Fix placement of main content below nav bar */
:global(main) {
margin-top: 20px;
}
:global(h1) {
margin-top: 2rem;
}
}
}
@media (min-width: 769px) {
.mobile-tab-bar,
.mobile-menu {
display: none;
}
}
</style>
<script>
document.addEventListener('DOMContentLoaded', () => {
const mobileMenuToggle = document.getElementById('mobile-menu-toggle');
const mobileMenuClose = document.getElementById('mobile-menu-close');
const mobileMenu = document.querySelector('.mobile-menu');
const overlay = document.getElementById('overlay');
const body = document.body;
// Mobile menu toggle
if (mobileMenuToggle && mobileMenu && overlay) {
mobileMenuToggle.addEventListener('click', () => {
if (mobileMenu.classList.contains('active')) {
// If menu is open, close it
mobileMenu.classList.remove('active');
overlay.classList.remove('active');
body.style.overflow = '';
} else {
// If menu is closed, open it
mobileMenu.classList.add('active');
overlay.classList.add('active');
body.style.overflow = 'hidden';
}
});
}
// Mobile menu close
if (mobileMenuClose && mobileMenu && overlay) {
mobileMenuClose.addEventListener('click', () => {
mobileMenu.classList.remove('active');
overlay.classList.remove('active');
body.style.overflow = '';
});
}
// Overlay click
if (overlay && mobileMenu) {
overlay.addEventListener('click', () => {
mobileMenu.classList.remove('active');
overlay.classList.remove('active');
body.style.overflow = '';
});
}
// Close menu when clicking a link
const mobileMenuLinks = document.querySelectorAll('.mobile-menu-links a');
mobileMenuLinks.forEach((link) => {
link.addEventListener('click', () => {
if (mobileMenu && overlay) {
mobileMenu.classList.remove('active');
overlay.classList.remove('active');
body.style.overflow = '';
}
});
});
});
</script>