233 lines
11 KiB
PHP
233 lines
11 KiB
PHP
<script data-navigate-track>
|
|
// Apply lazy loading to images - runs on initial load and Livewire navigation
|
|
(function() {
|
|
const applyLazyLoading = () => {
|
|
document.querySelectorAll('img:not([loading])')
|
|
.forEach(img => img.loading = 'lazy');
|
|
};
|
|
|
|
// Run immediately if DOM is ready, otherwise wait
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', applyLazyLoading);
|
|
} else {
|
|
applyLazyLoading();
|
|
}
|
|
|
|
// Also run on Livewire navigation
|
|
document.addEventListener('livewire:navigated', applyLazyLoading);
|
|
})();
|
|
</script>
|
|
|
|
<style>
|
|
/* ── Brand text animated gradient ── */
|
|
.text-flow-effect {
|
|
background: linear-gradient(
|
|
90deg,
|
|
hsl(var(--color-primary)) 0%,
|
|
hsl(var(--color-secondary)) 40%,
|
|
hsl(var(--color-primary)) 60%,
|
|
hsl(var(--color-secondary)) 100%
|
|
);
|
|
background-size: 300% auto;
|
|
background-clip: text;
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
animation: textFlowAnimation 6s ease-in-out infinite;
|
|
}
|
|
|
|
@keyframes textFlowAnimation {
|
|
0% { background-position: 0% center; }
|
|
50% { background-position: 300% center; }
|
|
100% { background-position: 0% center; }
|
|
}
|
|
|
|
/* ── Nav link animated underline ── */
|
|
.nav-link-modern {
|
|
position: relative;
|
|
}
|
|
.nav-link-modern::after {
|
|
content: '';
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 50%;
|
|
width: 0;
|
|
height: 2px;
|
|
border-radius: 1px;
|
|
background: hsl(var(--color-primary));
|
|
transition: width 0.25s ease, left 0.25s ease;
|
|
}
|
|
.nav-link-modern:hover::after {
|
|
width: 60%;
|
|
left: 20%;
|
|
}
|
|
|
|
/* ── Avatar ring using theme colors ── */
|
|
.avatar-ring {
|
|
padding: 2px;
|
|
background: linear-gradient(135deg, hsl(var(--color-primary)), hsl(var(--color-secondary)));
|
|
border-radius: 9999px;
|
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
|
}
|
|
.avatar-ring:hover {
|
|
transform: scale(1.05);
|
|
box-shadow: 0 0 10px hsl(var(--color-primary) / 0.3);
|
|
}
|
|
.avatar-ring img {
|
|
display: block;
|
|
border-radius: 9999px;
|
|
border: 2px solid hsl(var(--color-background-secondary));
|
|
}
|
|
</style>
|
|
|
|
<nav x-data="{ scrolled: false, slideOverOpen: false }"
|
|
x-init="
|
|
window.addEventListener('scroll', () => { scrolled = window.scrollY > 20 });
|
|
$watch('slideOverOpen', value => { document.documentElement.style.overflow = value ? 'hidden' : '' });
|
|
"
|
|
:class="{
|
|
'shadow-lg shadow-black/5 border-neutral/80': scrolled,
|
|
'border-neutral': !scrolled
|
|
}"
|
|
class="w-full px-4 lg:px-8 bg-background-secondary border-b flex flex-col lg:flex-row justify-between fixed top-0 z-20 h-auto transition-all duration-300">
|
|
<div class="relative z-50 w-full h-auto">
|
|
|
|
<div class="flex flex-row items-center justify-between w-full h-16">
|
|
|
|
<!-- Logo -->
|
|
<div class="flex flex-row items-center">
|
|
<a href="{{ route('home') }}" class="flex items-center h-10 gap-2.5 group" wire:navigate>
|
|
<x-logo class="h-10 transition-transform duration-200 group-hover:scale-[1.03]" />
|
|
<span class="text-xl font-extrabold tracking-tight leading-none flex items-center text-flow-effect">{{ config('app.name') }}</span>
|
|
</a>
|
|
|
|
<!-- Desktop menu: lg and above -->
|
|
<div class="hidden lg:flex flex-row items-center ml-8 gap-0.5">
|
|
@foreach (\App\Classes\Navigation::getLinks() as $nav)
|
|
@if (isset($nav['children']) && count($nav['children']) > 0)
|
|
<div class="relative">
|
|
<x-dropdown>
|
|
<x-slot:trigger>
|
|
<span class="nav-link-modern flex flex-row items-center px-3 py-2 text-sm font-semibold whitespace-nowrap text-base hover:text-primary transition-colors duration-200">{{ $nav['name'] }}</span>
|
|
</x-slot:trigger>
|
|
<x-slot:content>
|
|
@foreach ($nav['children'] as $child)
|
|
<x-navigation.link :href="route($child['route'], $child['params'] ?? null)"
|
|
:spa="isset($child['spa']) ? $nav['spa'] : true">
|
|
{{ $child['name'] }}
|
|
</x-navigation.link>
|
|
@endforeach
|
|
</x-slot:content>
|
|
</x-dropdown>
|
|
</div>
|
|
@else
|
|
<x-navigation.link :href="route($nav['route'], $nav['params'] ?? null)"
|
|
:spa="isset($nav['spa']) ? $nav['spa'] : true"
|
|
class="nav-link-modern flex items-center px-3 py-2">
|
|
{{ $nav['name'] }}
|
|
</x-navigation.link>
|
|
@endif
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Right-side controls -->
|
|
<div class="flex items-center gap-3">
|
|
|
|
<livewire:components.cart />
|
|
|
|
<!-- User account dropdown (desktop) -->
|
|
@if(auth()->check())
|
|
<div class="hidden lg:block">
|
|
<x-dropdown>
|
|
<x-slot:trigger>
|
|
<div class="flex items-center gap-2.5 cursor-pointer group">
|
|
<div class="avatar-ring">
|
|
<img src="{{ auth()->user()->avatar }}" class="w-8 h-8" alt="avatar" />
|
|
</div>
|
|
<span class="text-sm font-semibold text-base group-hover:text-primary transition-colors duration-200">{{ Auth::user()->first_name }}</span>
|
|
</div>
|
|
</x-slot:trigger>
|
|
<x-slot:content>
|
|
@foreach (\App\Classes\Navigation::getAccountDropdownLinks() as $nav)
|
|
<x-navigation.link :href="route($nav['route'], $nav['params'] ?? null)" :spa="isset($nav['spa']) ? $nav['spa'] : true">
|
|
{{ $nav['name'] }}
|
|
</x-navigation.link>
|
|
@endforeach
|
|
<div class="border-t border-neutral/50 mt-1 pt-1">
|
|
<livewire:auth.logout />
|
|
</div>
|
|
</x-slot:content>
|
|
</x-dropdown>
|
|
</div>
|
|
@else
|
|
<div class="hidden lg:flex items-center gap-2.5">
|
|
<a href="{{ route('login') }}" wire:navigate>
|
|
<button class="px-4 py-2 text-sm font-medium text-base hover:text-primary rounded-lg transition-all duration-200">
|
|
{{ __('navigation.login') }}
|
|
</button>
|
|
</a>
|
|
@if(!config('settings.registration_disabled', false))
|
|
<a href="{{ route('register') }}" wire:navigate>
|
|
<button class="px-5 py-2 text-sm font-semibold text-white bg-gradient-to-r from-primary to-secondary rounded-lg transition-all duration-200 shadow-md shadow-primary/15 hover:shadow-lg hover:shadow-primary/25 hover:brightness-110 active:scale-[0.98]">
|
|
{{ __('navigation.register') }}
|
|
</button>
|
|
</a>
|
|
@endif
|
|
</div>
|
|
@endif
|
|
|
|
<!-- Mobile menu toggle -->
|
|
<button @click="slideOverOpen = !slideOverOpen"
|
|
class="relative w-10 h-10 flex lg:hidden items-center justify-center rounded-xl bg-background hover:bg-neutral/30 transition-all duration-200 group"
|
|
aria-label="Toggle Menu">
|
|
<span x-show="!slideOverOpen" x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0 rotate-90 scale-75" x-transition:enter-end="opacity-100 rotate-0 scale-100" class="absolute inset-0 flex items-center justify-center">
|
|
<x-ri-menu-fill class="size-5 text-base/80 group-hover:text-foreground transition-colors duration-200" />
|
|
</span>
|
|
<span x-show="slideOverOpen" x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0 -rotate-90 scale-75" x-transition:enter-end="opacity-100 rotate-0 scale-100" class="absolute inset-0 flex items-center justify-center">
|
|
<x-ri-close-fill class="size-5 text-base/80 group-hover:text-foreground transition-colors duration-200" />
|
|
</span>
|
|
</button>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Mobile slide-over menu (<1024px) -->
|
|
<template x-teleport="body">
|
|
<div x-show="slideOverOpen" @keydown.window.escape="slideOverOpen=false" x-cloak class="fixed inset-0 z-[99]" aria-modal="true">
|
|
{{-- Backdrop with blur and fade --}}
|
|
<div x-show="slideOverOpen"
|
|
x-transition:enter="transition ease-out duration-300"
|
|
x-transition:enter-start="opacity-0"
|
|
x-transition:enter-end="opacity-100"
|
|
x-transition:leave="transition ease-in duration-200"
|
|
x-transition:leave-start="opacity-100"
|
|
x-transition:leave-end="opacity-0"
|
|
@click="slideOverOpen = false"
|
|
class="absolute inset-0 bg-black/40 backdrop-blur-sm"></div>
|
|
|
|
{{-- Slide-over panel --}}
|
|
<div x-show="slideOverOpen"
|
|
x-transition:enter="transition ease-out duration-300"
|
|
x-transition:enter-start="opacity-0 translate-y-full"
|
|
x-transition:enter-end="opacity-100 translate-y-0"
|
|
x-transition:leave="transition ease-in duration-200"
|
|
x-transition:leave-start="opacity-100 translate-y-0"
|
|
x-transition:leave-end="opacity-0 translate-y-full"
|
|
@click.stop
|
|
class="absolute left-0 right-0 top-16 bg-background-secondary/95 backdrop-blur-xl border-t border-neutral/50 shadow-2xl overflow-y-auto flex flex-col rounded-t-2xl"
|
|
style="height:calc(100dvh - 4rem);">
|
|
<div class="flex flex-col h-full p-6">
|
|
{{-- Swipe handle indicator --}}
|
|
<div class="flex justify-center mb-4">
|
|
<div class="w-10 h-1 bg-neutral/40 rounded-full"></div>
|
|
</div>
|
|
<div class="flex-1 min-h-0 overflow-y-auto">
|
|
<x-navigation.sidebar-links />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
</div>
|
|
</nav> |