Compare commits

5 Commits

Author SHA1 Message Date
4356af4e0a fix mobile hamburger menu positioning and layout
All checks were successful
CI / update (push) Successful in 1m11s
Improve profile picture and navigation alignment on mobile:
- Position UserHeader fixed 2rem from viewport bottom (avoids browser UI issues)
- Center UserHeader horizontally within hamburger menu
- Add 2rem margin to links wrapper for better spacing
- Align navigation items to flex-start for left alignment
2025-12-27 10:24:44 +01:00
0ef6ca4698 shorten English header labels to match German compact style
Update English navigation labels for consistency:
- "In Season" to "Season" (matches "Saison")
- "Keywords" to "Tags" (same in both languages)
2025-12-27 10:16:37 +01:00
2b76b47083 improve header navigation styling and active link highlighting
Optimize header link spacing and add visual feedback for active pages:
- Reduce link padding and gap for more compact navigation
- Shorten German labels: "In Saison" to "Saison", "Stichwörter" to "Tags"
- Remove "Tipps" section from navigation menu

Add active page highlighting across all layouts:
- Highlight current page links in red (matching hover color)
- Desktop: animated red underline that smoothly slides between links
- Mobile: static red underline for active links in hamburger menu
- Underline aligns precisely with text width (excludes padding)

Improve transitions:
- Fix color transition to only animate color, not layout properties
- Disable underline transition during window resize to prevent lag
- Underline updates immediately on resize for perfect alignment
2025-12-27 10:15:16 +01:00
51b0928489 fix mobile header shadow appearing over hamburger menu
Separate the drop shadow from the button wrapper into its own fixed
element with a lower z-index. This prevents the shadow from appearing
over the hamburger menu when it's pulled out on mobile.

- Create separate button_wrapper_shadow element
- Move box-shadow styling to shadow element
- Set shadow z-index to 9 to stay below menu but above page content
- Use fixed positioning with pointer-events: none
2025-12-27 09:54:17 +01:00
8d5d64a9bd refactor language selector into separate component
Extract language switching functionality from UserHeader into a new
LanguageSelector component. In mobile view, the selector appears in
the top bar next to the hamburger menu. In desktop view, it appears
in the navigation bar to the left of the UserHeader.

- Create LanguageSelector component with local element bindings
- Update Header component with language_selector_mobile and
  language_selector_desktop slots
- Remove language selector code from UserHeader
- Update recipe and main layouts to use new component
- Hide desktop language selector inside mobile hamburger menu
2025-12-27 09:46:04 +01:00
7 changed files with 368 additions and 201 deletions

View File

@@ -1,8 +1,13 @@
<script>
import "$lib/css/nordtheme.css"
import { onMount } from "svelte";
import { page } from '$app/stores';
import Symbol from "./Symbol.svelte"
let underlineLeft = $state(0);
let underlineWidth = $state(0);
let disableTransition = $state(false);
function toggle_sidebar(state){
// state: force hidden state (optional)
const nav_el = document.querySelector("nav")
@@ -10,11 +15,61 @@ function toggle_sidebar(state){
else nav_el.hidden = state
}
function updateUnderline() {
const activeLink = document.querySelector('.site_header a.active');
const linksWrapper = document.querySelector('.links-wrapper');
if (activeLink && linksWrapper) {
const wrapperRect = linksWrapper.getBoundingClientRect();
const linkRect = activeLink.getBoundingClientRect();
// Get computed padding to exclude from width and adjust position
const computedStyle = window.getComputedStyle(activeLink);
const paddingLeft = parseFloat(computedStyle.paddingLeft);
const paddingRight = parseFloat(computedStyle.paddingRight);
underlineLeft = linkRect.left - wrapperRect.left + paddingLeft;
underlineWidth = linkRect.width - paddingLeft - paddingRight;
} else {
underlineWidth = 0;
}
}
// Update underline when page changes
$effect(() => {
$page.url.pathname; // Subscribe to pathname changes
// Use setTimeout to ensure DOM has updated
setTimeout(updateUnderline, 0);
});
onMount( () => {
const link_els = document.querySelectorAll("nav a")
link_els.forEach((el) => {
el.addEventListener("click", () => {toggle_sidebar(true)});
})
const link_els = document.querySelectorAll("nav a")
link_els.forEach((el) => {
el.addEventListener("click", () => {toggle_sidebar(true)});
})
// Initialize underline position
updateUnderline();
// Update underline on resize, with transition disabled
let resizeTimer;
function handleResize() {
disableTransition = true;
updateUnderline(); // Update immediately to prevent lag
clearTimeout(resizeTimer);
resizeTimer = setTimeout(() => {
// Re-enable transition after resize has settled
disableTransition = false;
}, 150);
}
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
clearTimeout(resizeTimer);
};
})
</script>
@@ -39,7 +94,7 @@ nav[hidden]{
:global(a.entry)
{
list-style-type:none;
transition: 100ms;
transition: color 100ms;
color: white;
user-select: none;
}
@@ -51,11 +106,12 @@ nav[hidden]{
font-size: 1.2rem;
color: inherit;
border-radius: 1000px;
padding: 0.5rem 1rem;
padding: 0.5rem 0.75rem;
}
:global(.site_header li:hover),
:global(.site_header li:focus-within),
:global(.site_header li:has(a.active)),
:global(.entry:hover),
:global(.entry:focus-visible)
{
@@ -66,12 +122,27 @@ nav[hidden]{
padding-block: 1.5rem;
display: flex;
flex-direction: row;
gap: 1rem;
gap: 0.5rem;
justify-content: space-evenly;
max-width: 1000px;
margin: 0;
margin-inline: auto;
}
.links-wrapper {
position: relative;
flex: 1;
}
.active-underline {
position: absolute;
bottom: 1.2rem;
height: 2px;
background-color: var(--red);
transition: left 300ms ease-out, width 300ms ease-out;
pointer-events: none;
}
.active-underline.no-transition {
transition: none;
}
.nav_button{
display: none;
}
@@ -79,6 +150,16 @@ nav[hidden]{
display: none;
padding-inline: 0.5rem;
}
.right-buttons{
display: flex;
align-items: center;
gap: 0.5rem;
}
.header-right{
display: flex;
align-items: center;
gap: 0.5rem;
}
:global(svg.symbol){
height: 4rem;
width: 4rem;
@@ -102,8 +183,17 @@ footer{
}
@media screen and (max-width: 800px) {
.button_wrapper{
.button_wrapper_shadow{
box-shadow: 0 1em 1rem 0rem rgba(0,0,0,0.4);
position: fixed;
width: 100%;
height: 4rem;
top: 0;
left: 0;
z-index: 9;
pointer-events: none;
}
.button_wrapper{
display: flex;
justify-content: space-between;
align-items: center;
@@ -139,7 +229,7 @@ footer{
height: 100vh; /* dvh does not work, breaks because of transition and only being applied after scroll ends*/
margin-bottom: 50vh;
width: min(95svw, 25em);
transition: 100ms;
transition: transform 100ms;
z-index: 10;
flex-direction: column;
justify-content: flex-start !important;
@@ -160,9 +250,15 @@ footer{
margin-bottom: 2rem;
}
.nav_site .links-wrapper {
align-self: flex-start;
width: 100%;
margin: 2rem;
}
:global(.site_header){
flex-direction: column;
padding-top: min(10rem, 10vh);
align-items: flex-start;
}
:global(.site_header li, .site_header a){
font-size: 4rem;
@@ -174,18 +270,49 @@ footer{
:global(.site_header li:focus-within){
transform: unset;
}
.nav_site .header-right{
flex-direction: column;
position: absolute;
bottom: 2rem;
left: 50%;
transform: translateX(-50%);
}
.language-selector-desktop{
display: none;
}
.active-underline {
display: none;
}
:global(.nav_site .site_header a.active) {
text-decoration: underline;
text-decoration-color: var(--red);
text-decoration-thickness: 2px;
text-underline-offset: 0.3rem;
}
}
</style>
<div class=wrapper lang=de>
<div>
<span class=button_wrapper_shadow></span>
<div class=button_wrapper>
<a href="/"><Symbol></Symbol></a>
<button class=nav_button on:click={() => {toggle_sidebar()}}><svg xmlns="http://www.w3.org/2000/svg" height="0.5em" viewBox="0 0 448 512"><!--! Font Awesome Free 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M0 96C0 78.3 14.3 64 32 64H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H32C14.3 128 0 113.7 0 96zM0 256c0-17.7 14.3-32 32-32H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H32c-17.7 0-32-14.3-32-32zM448 416c0 17.7-14.3 32-32 32H32c-17.7 0-32-14.3-32-32s14.3-32 32-32H416c17.7 0 32 14.3 32 32z"/></svg></button>
<div class="right-buttons">
<slot name=language_selector_mobile></slot>
<button class=nav_button on:click={() => {toggle_sidebar()}}><svg xmlns="http://www.w3.org/2000/svg" height="0.5em" viewBox="0 0 448 512"><!--! Font Awesome Free 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M0 96C0 78.3 14.3 64 32 64H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H32C14.3 128 0 113.7 0 96zM0 256c0-17.7 14.3-32 32-32H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H32c-17.7 0-32-14.3-32-32zM448 416c0 17.7-14.3 32-32 32H32c-17.7 0-32-14.3-32-32s14.3-32 32-32H416c17.7 0 32 14.3 32 32z"/></svg></button>
</div>
</div>
<nav hidden class=nav_site>
<a class=entry href="/"><Symbol></Symbol></a>
<slot name=links></slot>
<slot name=right_side></slot>
<div class="links-wrapper">
<slot name=links></slot>
<div class="active-underline" class:no-transition={disableTransition} style="left: {underlineLeft}px; width: {underlineWidth}px;"></div>
</div>
<div class="header-right">
<div class="language-selector-desktop">
<slot name=language_selector_desktop></slot>
</div>
<slot name=right_side></slot>
</div>
</nav>
<slot></slot>

View File

@@ -0,0 +1,163 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { recipeTranslationStore } from '$lib/stores/recipeTranslation';
import { onMount } from 'svelte';
let currentLang = $state('de');
let currentPath = $state('');
let langButton: HTMLButtonElement;
let langOptions: HTMLDivElement;
$effect(() => {
// Update current language and path when page changes
if (typeof window !== 'undefined') {
const path = window.location.pathname;
currentPath = path;
if (path.startsWith('/recipes')) {
currentLang = 'en';
} else if (path.startsWith('/rezepte')) {
currentLang = 'de';
} else if (path === '/') {
// On main page, read from localStorage
const preferredLanguage = localStorage.getItem('preferredLanguage');
currentLang = preferredLanguage === 'en' ? 'en' : 'de';
}
}
});
function toggle_language_options(){
if (langOptions) {
langOptions.hidden = !langOptions.hidden;
}
}
async function switchLanguage(lang: 'de' | 'en') {
// Update the current language state immediately
currentLang = lang;
// Store preference
if (typeof localStorage !== 'undefined') {
localStorage.setItem('preferredLanguage', lang);
}
// Get the current path directly from window
const path = typeof window !== 'undefined' ? window.location.pathname : currentPath;
// If on main page, dispatch event instead of reloading
if (path === '/') {
window.dispatchEvent(new CustomEvent('languagechange', { detail: { lang } }));
return;
}
// If we have recipe translation data from store, use the correct short names
const recipeData = $recipeTranslationStore;
if (recipeData) {
if (lang === 'en' && recipeData.englishShortName) {
await goto(`/recipes/${recipeData.englishShortName}`);
return;
} else if (lang === 'de' && recipeData.germanShortName) {
await goto(`/rezepte/${recipeData.germanShortName}`);
return;
}
}
// Convert current path to target language (for non-recipe pages)
let newPath = path;
if (lang === 'en' && path.startsWith('/rezepte')) {
newPath = path.replace('/rezepte', '/recipes');
} else if (lang === 'de' && path.startsWith('/recipes')) {
newPath = path.replace('/recipes', '/rezepte');
} else if (!path.startsWith('/rezepte') && !path.startsWith('/recipes')) {
// On other pages (glaube, cospend, etc), go to recipe home
newPath = lang === 'en' ? '/recipes' : '/rezepte';
}
await goto(newPath);
}
onMount(() => {
const handleClick = (e: MouseEvent) => {
if(langButton && !langButton.contains(e.target as Node)){
if (langOptions) langOptions.hidden = true;
}
};
document.addEventListener("click", handleClick);
return () => {
document.removeEventListener("click", handleClick);
};
})
</script>
<style>
.language-selector{
position: relative;
}
.language-button{
width: auto;
padding: 0.5rem 1rem;
border-radius: 8px;
background-color: var(--nord3);
color: white;
font-size: 0.9rem;
font-weight: 600;
cursor: pointer;
transition: background-color 100ms;
border: none;
}
.language-button:hover{
background-color: var(--nord2);
}
.language-options{
--bg_color: var(--nord3);
box-sizing: border-box;
border-radius: 5px;
position: absolute;
right: 0;
top: calc(100% + 10px);
background-color: var(--bg_color);
width: 10ch;
padding: 0.5rem;
z-index: 1000;
}
.language-options button{
width: 100%;
background-color: transparent;
color: white;
border: none;
padding: 0.5rem;
margin: 0;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
text-align: left;
transition: background-color 100ms;
}
.language-options button:hover{
background-color: var(--nord2);
}
.language-options button.active{
background-color: var(--nord14);
}
</style>
<div class="language-selector">
<button bind:this={langButton} onclick={toggle_language_options} class="language-button">
{currentLang.toUpperCase()}
</button>
<div bind:this={langOptions} class="language-options" hidden>
<button
class:active={currentLang === 'de'}
onclick={() => switchLanguage('de')}
>
DE
</button>
<button
class:active={currentLang === 'en'}
onclick={() => switchLanguage('en')}
>
EN
</button>
</div>
</div>

View File

@@ -1,99 +1,21 @@
<script lang="ts">
import { onMount } from "svelte";
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { recipeTranslationStore } from '$lib/stores/recipeTranslation';
let { user, showLanguageSelector = false } = $props();
let currentLang = $state('de');
let currentPath = $state('');
$effect(() => {
// Update current language and path when page changes
if (typeof window !== 'undefined') {
const path = window.location.pathname;
currentPath = path;
if (path.startsWith('/recipes')) {
currentLang = 'en';
} else if (path.startsWith('/rezepte')) {
currentLang = 'de';
} else if (path === '/') {
// On main page, read from localStorage
const preferredLanguage = localStorage.getItem('preferredLanguage');
currentLang = preferredLanguage === 'en' ? 'en' : 'de';
}
}
});
let { user } = $props();
function toggle_options(){
const el = document.querySelector("#options")
el.hidden = !el.hidden
}
function toggle_language_options(){
const el = document.querySelector("#language-options")
el.hidden = !el.hidden
}
function switchLanguage(lang: 'de' | 'en') {
// Update the current language state immediately
currentLang = lang;
// Store preference
if (typeof localStorage !== 'undefined') {
localStorage.setItem('preferredLanguage', lang);
}
// Get the current path directly from window
const path = typeof window !== 'undefined' ? window.location.pathname : currentPath;
// If on main page, dispatch event instead of reloading
if (path === '/') {
window.dispatchEvent(new CustomEvent('languagechange', { detail: { lang } }));
return;
}
// If we have recipe translation data from store, use the correct short names
const recipeData = $recipeTranslationStore;
if (recipeData) {
if (lang === 'en' && recipeData.englishShortName) {
goto(`/recipes/${recipeData.englishShortName}`);
return;
} else if (lang === 'de' && recipeData.germanShortName) {
goto(`/rezepte/${recipeData.germanShortName}`);
return;
}
}
// Convert current path to target language (for non-recipe pages)
let newPath = path;
if (lang === 'en' && path.startsWith('/rezepte')) {
newPath = path.replace('/rezepte', '/recipes');
} else if (lang === 'de' && path.startsWith('/recipes')) {
newPath = path.replace('/recipes', '/rezepte');
} else if (!path.startsWith('/rezepte') && !path.startsWith('/recipes')) {
// On other pages (glaube, cospend, etc), go to recipe home
newPath = lang === 'en' ? '/recipes' : '/rezepte';
}
goto(newPath);
}
onMount( () => {
document.addEventListener("click", (e) => {
const userButton = document.querySelector("#button")
const langButton = document.querySelector("#language-button")
if(userButton && !userButton.contains(e.target)){
const options = document.querySelector("#options");
if (options) options.hidden = true;
}
if(langButton && !langButton.contains(e.target)){
const langOptions = document.querySelector("#language-options");
if (langOptions) langOptions.hidden = true;
}
})
})
</script>
@@ -149,59 +71,6 @@
background-position: center;
background-size: contain;
}
.language-selector{
position: relative;
}
#language-button{
width: auto;
padding: 0.5rem 1rem;
border-radius: 8px;
background-color: var(--nord3);
color: white;
font-size: 0.9rem;
font-weight: 600;
cursor: pointer;
transition: background-color 100ms;
}
#language-button:hover{
background-color: var(--nord2);
}
#language-options{
--bg_color: var(--nord3);
box-sizing: border-box;
border-radius: 5px;
position: absolute;
right: 0;
top: calc(100% + 10px);
background-color: var(--bg_color);
width: 10ch;
padding: 0.5rem;
z-index: 1000;
}
#language-options button{
width: 100%;
background-color: transparent;
color: white;
border: none;
padding: 0.5rem;
margin: 0;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
text-align: left;
transition: background-color 100ms;
}
#language-options button:hover{
background-color: var(--nord2);
}
#language-options button.active{
background-color: var(--nord14);
}
.header-right{
display: flex;
align-items: center;
gap: 0.5rem;
}
#options{
--bg_color: var(--nord3);
box-sizing: border-box;
@@ -266,41 +135,17 @@ h2 + p{
}
</style>
<div class="header-right">
{#if showLanguageSelector}
<div class="language-selector">
<button on:click={toggle_language_options} id="language-button">
{currentLang.toUpperCase()}
</button>
<div id="language-options" hidden>
<button
class:active={currentLang === 'de'}
on:click={() => switchLanguage('de')}
>
DE
</button>
<button
class:active={currentLang === 'en'}
on:click={() => switchLanguage('en')}
>
EN
</button>
</div>
{#if user}
<button on:click={toggle_options} style="background-image: url(https://bocken.org/static/user/thumb/{user.nickname}.webp)" id=button>
<div id=options class="speech top" hidden>
<h2>{user.name}</h2>
<p>({user.nickname})</p>
<ul>
<li><a href="https://sso.bocken.org/if/user/#/settings" >Einstellungen</a></li>
<li><a href="/logout" >Log Out</a></li>
</ul>
</div>
{/if}
{#if user}
<button on:click={toggle_options} style="background-image: url(https://bocken.org/static/user/thumb/{user.nickname}.webp)" id=button>
<div id=options class="speech top" hidden>
<h2>{user.name}</h2>
<p>({user.nickname})</p>
<ul>
<li><a href="https://sso.bocken.org/if/user/#/settings" >Einstellungen</a></li>
<li><a href="/logout" >Log Out</a></li>
</ul>
</div>
</button>
{:else}
<a class=entry href=/login>Log In</a>
{/if}
</div>
</button>
{:else}
<a class=entry href=/login>Log In</a>
{/if}

View File

@@ -1,6 +1,7 @@
<script>
import Header from '$lib/components/Header.svelte'
import UserHeader from '$lib/components/UserHeader.svelte';
import LanguageSelector from '$lib/components/LanguageSelector.svelte';
let { data } = $props();
let user = $derived(data.session?.user);
@@ -9,6 +10,8 @@ let user = $derived(data.session?.user);
<Header>
<ul class=site_header slot=links>
</ul>
<UserHeader {user} slot=right_side showLanguageSelector={true}></UserHeader>
<LanguageSelector slot=language_selector_mobile />
<LanguageSelector slot=language_selector_desktop />
<UserHeader {user} slot=right_side></UserHeader>
<slot></slot>
</Header>

View File

@@ -1,6 +1,8 @@
<script>
import { page } from '$app/stores';
import Header from '$lib/components/Header.svelte'
import UserHeader from '$lib/components/UserHeader.svelte';
import UserHeader from '$lib/components/UserHeader.svelte';
import LanguageSelector from '$lib/components/LanguageSelector.svelte';
let { data } = $props();
let user = $derived(data.session?.user);
@@ -9,26 +11,36 @@ const isEnglish = $derived(data.lang === 'en');
const labels = $derived({
allRecipes: isEnglish ? 'All Recipes' : 'Alle Rezepte',
favorites: isEnglish ? 'Favorites' : 'Favoriten',
inSeason: isEnglish ? 'In Season' : 'In Saison',
inSeason: isEnglish ? 'Season' : 'Saison',
category: isEnglish ? 'Category' : 'Kategorie',
icon: 'Icon',
keywords: isEnglish ? 'Keywords' : 'Stichwörter',
tips: isEnglish ? 'Tips' : 'Tipps'
keywords: 'Tags'
});
function isActive(path) {
const currentPath = $page.url.pathname;
// Exact match for recipe lang root
if (path === `/${data.recipeLang}`) {
return currentPath === `/${data.recipeLang}` || currentPath === `/${data.recipeLang}/`;
}
// For other paths, check if current path starts with the link path
return currentPath.startsWith(path);
}
</script>
<Header>
<ul class=site_header slot=links>
<li><a href="/{data.recipeLang}">{labels.allRecipes}</a></li>
<li><a href="/{data.recipeLang}" class:active={isActive(`/${data.recipeLang}`)}>{labels.allRecipes}</a></li>
{#if user}
<li><a href="/{data.recipeLang}/favorites">{labels.favorites}</a></li>
<li><a href="/{data.recipeLang}/favorites" class:active={isActive(`/${data.recipeLang}/favorites`)}>{labels.favorites}</a></li>
{/if}
<li><a href="/{data.recipeLang}/season">{labels.inSeason}</a></li>
<li><a href="/{data.recipeLang}/category">{labels.category}</a></li>
<li><a href="/{data.recipeLang}/icon">{labels.icon}</a></li>
<li><a href="/{data.recipeLang}/tag">{labels.keywords}</a></li>
<li><a href="/rezepte/tips-and-tricks">{labels.tips}</a></li>
<li><a href="/{data.recipeLang}/season" class:active={isActive(`/${data.recipeLang}/season`)}>{labels.inSeason}</a></li>
<li><a href="/{data.recipeLang}/category" class:active={isActive(`/${data.recipeLang}/category`)}>{labels.category}</a></li>
<li><a href="/{data.recipeLang}/icon" class:active={isActive(`/${data.recipeLang}/icon`)}>{labels.icon}</a></li>
<li><a href="/{data.recipeLang}/tag" class:active={isActive(`/${data.recipeLang}/tag`)}>{labels.keywords}</a></li>
</ul>
<UserHeader slot=right_side {user} showLanguageSelector={true}></UserHeader>
<LanguageSelector slot=language_selector_mobile />
<LanguageSelector slot=language_selector_desktop />
<UserHeader slot=right_side {user}></UserHeader>
<slot></slot>
</Header>

View File

@@ -38,19 +38,29 @@
// Close the modal
showModal = false;
paymentId = null;
// Dispatch a custom event to trigger dashboard refresh
if ($page.route.id === '/cospend') {
window.dispatchEvent(new CustomEvent('dashboardRefresh'));
}
}
function isActive(path) {
const currentPath = $page.url.pathname;
// Exact match for cospend root
if (path === '/cospend') {
return currentPath === '/cospend' || currentPath === '/cospend/';
}
// For other paths, check if current path starts with the link path
return currentPath.startsWith(path);
}
</script>
<Header>
<ul class="site_header" slot="links">
<li><a href="/cospend">Dashboard</a></li>
<li><a href="/cospend/payments">All Payments</a></li>
<li><a href="/cospend/recurring">Recurring Payments</a></li>
<li><a href="/cospend" class:active={isActive('/cospend')}>Dashboard</a></li>
<li><a href="/cospend/payments" class:active={isActive('/cospend/payments')}>All Payments</a></li>
<li><a href="/cospend/recurring" class:active={isActive('/cospend/recurring')}>Recurring Payments</a></li>
</ul>
<UserHeader slot="right_side" {user}></UserHeader>

View File

@@ -1,13 +1,20 @@
<script>
import { page } from '$app/stores';
import Header from '$lib/components/Header.svelte'
import UserHeader from '$lib/components/UserHeader.svelte';
export let data
function isActive(path) {
const currentPath = $page.url.pathname;
// Check if current path starts with the link path
return currentPath.startsWith(path);
}
</script>
<Header>
<ul class=site_header slot=links>
<li><a href="/glaube/gebete">Gebete</a></li>
<li><a href="/glaube/rosenkranz">Rosenkranz</a></li>
<li><a href="/glaube/predigten">Predigten</a></li>
<li><a href="/glaube/gebete" class:active={isActive('/glaube/gebete')}>Gebete</a></li>
<li><a href="/glaube/rosenkranz" class:active={isActive('/glaube/rosenkranz')}>Rosenkranz</a></li>
<li><a href="/glaube/predigten" class:active={isActive('/glaube/predigten')}>Predigten</a></li>
</ul>
<UserHeader user={data.session?.user} slot=right_side></UserHeader>
<slot></slot>