varo-website/src/app/layout/Menu.svelte
2025-06-03 22:05:03 +02:00

186 lines
5.4 KiB
Svelte

<script lang="ts">
import MenuHome from '@assets/img/menu-home.webp';
import MenuSignup from '@assets/img/menu-signup.webp';
import MenuRules from '@assets/img/menu-rules.webp';
import MenuFaq from '@assets/img/menu-faq.webp';
import MenuFeedback from '@assets/img/menu-feedback.webp';
import MenuAdmins from '@assets/img/menu-admins.webp';
import MenuButton from '@assets/img/menu-button.webp';
import MenuInventoryBar from '@assets/img/menu-inventory-bar.webp';
import MenuSelectedFrame from '@assets/img/menu-selected-frame.webp';
import { isBrowser } from '@antfu/utils';
import { navigate } from 'astro:transitions/client';
import { onMount } from 'svelte';
// html bindings
let navElem: HTMLDivElement;
// states
let navPaths = $state([
{
name: 'Startseite',
sprite: MenuHome.src,
href: '',
active: false
},
{
name: 'Registrieren',
sprite: MenuSignup.src,
href: 'signup',
active: false
},
{
name: 'Regeln',
sprite: MenuRules.src,
href: 'rules',
active: false
},
{
name: 'FAQ',
sprite: MenuFaq.src,
href: 'faq',
active: false
},
{
name: 'Feedback & Kontakt',
sprite: MenuFeedback.src,
href: 'feedback',
active: false
},
{
name: 'Admins',
sprite: MenuAdmins.src,
href: 'admins',
active: false
}
]);
let showMenuPermanent = $state(isBrowser ? localStorage.getItem('showMenuPermanent') === 'true' : false);
let isTouch = $state(false);
let isOpen = $state(false);
let windowHeight = $state(0);
// lifecycle
$effect(() => {
localStorage.setItem('showMenuPermanent', `${showMenuPermanent}`);
});
onMount(() => {
updateActiveNavPath();
new MutationObserver(updateActiveNavPath).observe(document.head, { childList: true });
});
// functions
function updateActiveNavPath() {
for (let i = 0; i < navPaths.length; i++) {
navPaths[i].active = new URL(document.baseURI).pathname + navPaths[i].href === window.location.pathname;
}
}
</script>
<svelte:window bind:innerHeight={windowHeight} />
<svelte:body
ontouchend={(e) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
if (isTouch && !navElem.contains(e.target)) showMenuPermanent = false;
}}
/>
<div
class="fixed bottom-4 right-4 sm:left-4 sm:right-[initial] group/menu-bar flex flex-col-reverse justify-center items-center z-50 main-menu"
bind:this={navElem}
>
<button
class={isTouch ? 'btn btn-square relative w-16 h-16' : 'btn btn-square group/menu-button relative w-16 h-16'}
onclick={() => {
if (!isTouch) {
let activePath = navPaths.find((path) => path.active);
if (activePath !== undefined) {
navigate(activePath.href);
}
showMenuPermanent = !showMenuPermanent;
}
}}
ontouchend={() => {
isTouch = true;
showMenuPermanent = !showMenuPermanent;
}}
>
<img class="absolute w-full h-full p-1 pixelated" src={MenuButton.src} alt="menu" />
<img
class="opacity-0 transition-opacity delay-50 group-hover/menu-button:opacity-100 absolute w-full h-full p-[3px] pixelated"
class:opacity-100={isOpen || (isTouch && showMenuPermanent)}
src={MenuSelectedFrame.src}
alt="menu hover"
/>
</button>
<div
class:hidden={!(isOpen || showMenuPermanent)}
class={isTouch ? 'pb-3' : 'group-hover/menu-bar:block pb-3'}
onmouseenter={() => (isOpen = true)}
onmouseleave={() => (isOpen = false)}
>
<ul class="bg-base-200 rounded">
{#each navPaths as navPath, i (navPath.href)}
<li
class="flex justify-center tooltip"
class:tooltip-left={windowHeight > 450}
class:sm:tooltip-right={windowHeight > 450}
class:tooltip-top={windowHeight <= 450}
class:tooltip-open={isTouch || windowHeight <= 450}
data-tip={navPath.name}
>
<a
class="btn btn-square border-none group/menu-item relative w-[3.5rem] h-[3.5rem] flex justify-center items-center"
href={navPath.href}
onclick={() => navigate(navPath.href)}
>
<div
style="background-image: url({MenuInventoryBar.src}); background-position: -{i * 3.5}rem 0;"
class="block w-full h-full bg-no-repeat bg-horizontal-sprite pixelated"
></div>
<div class="absolute flex justify-center items-center w-full h-full">
<img class="w-1/2 h-1/2 pixelated" src={navPath.sprite} alt={navPath.name} />
</div>
<img
class="transition-opacity delay-50 group-hover/menu-item:opacity-100 absolute w-full h-full pixelated scale-110 z-10"
class:opacity-0={!navPath.active}
src={MenuSelectedFrame.src}
alt="menu hover"
/>
</a>
</li>
{/each}
</ul>
</div>
</div>
<style>
@media (max-height: 450px) {
.main-menu {
flex-direction: row;
}
.main-menu > div {
padding: 0.25rem 0 0 0.5rem;
}
.main-menu li {
display: inline-block;
&::before {
transform-origin: 0;
transform: rotate(-90deg);
margin-bottom: -0.5rem;
}
}
}
.pixelated {
image-rendering: pixelated;
}
.bg-horizontal-sprite {
background-size: auto 100%;
}
</style>