Populated Content
This commit is contained in:
420
resources/js/components/Header.vue
Normal file
420
resources/js/components/Header.vue
Normal file
@@ -0,0 +1,420 @@
|
||||
<template>
|
||||
<!-- Header -->
|
||||
<header class="header">
|
||||
<section class="navContainer">
|
||||
<nav class="nav">
|
||||
<div class="nav-brand">Dr Edgy Adventures</div>
|
||||
<button class="mobile-menu-btn">
|
||||
<span></span><span></span><span></span>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<div class="nav-menu">
|
||||
<a href="#about" class="nav-link">About</a>
|
||||
<a href="#contact" class="nav-link">Contact</a>
|
||||
<div
|
||||
class="dropdown"
|
||||
:class="{ open: dropdownOpen }"
|
||||
>
|
||||
<button
|
||||
class="dropdown-toggle nav-link"
|
||||
@click="dropdownOpen = !dropdownOpen"
|
||||
>
|
||||
Adventures
|
||||
<svg
|
||||
class="dropdown-icon"
|
||||
width="16" height="16" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor" stroke-width="2"
|
||||
>
|
||||
<polyline points="6,9 12,15 18,9"></polyline>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div class="dropdown-menu" :class="{ 'is-visible': isDropdownVisible }">
|
||||
<a
|
||||
v-for="continent in sortedContinents"
|
||||
:key="continent.id"
|
||||
:href="`/tours/${continent.internal_name}`"
|
||||
>
|
||||
{{ continent.name }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, computed, ref } from 'vue';
|
||||
import { usePage} from "@inertiajs/vue3";
|
||||
import {Continent, GlobalProperties} from "@/types";
|
||||
|
||||
const { continents_with_tours } = usePage().props as unknown as GlobalProperties
|
||||
|
||||
|
||||
const sortedContinents = computed(() => {
|
||||
return [...continents_with_tours].sort((a, b) => a.name.localeCompare(b.name))
|
||||
})
|
||||
|
||||
const dropdownOpen = ref(false)
|
||||
const windowWidth = ref(window.innerWidth);
|
||||
|
||||
|
||||
onMounted(() =>{
|
||||
initDropdown();
|
||||
initHeaderScroll()
|
||||
initMobileMenu()
|
||||
window.addEventListener('resize', handleResize)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
});
|
||||
|
||||
|
||||
const isDropdownVisible = computed(() => {
|
||||
return dropdownOpen.value || windowWidth.value < 768;
|
||||
});
|
||||
|
||||
const handleResize = () => {
|
||||
windowWidth.value = window.innerWidth;
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Mobile menu functionality
|
||||
const initMobileMenu = (): void => {
|
||||
const mobileMenuBtn = document.querySelector('.mobile-menu-btn') as HTMLElement | null;
|
||||
const navMenu = document.querySelector('.nav-menu') as HTMLElement | null;
|
||||
|
||||
if (!mobileMenuBtn || !navMenu) return;
|
||||
|
||||
mobileMenuBtn.addEventListener('click', () => {
|
||||
navMenu.classList.toggle('open');
|
||||
mobileMenuBtn.classList.toggle('open');
|
||||
});
|
||||
};
|
||||
|
||||
// Header background on scroll
|
||||
const initHeaderScroll = (): void => {
|
||||
const header = document.querySelector('.header') as HTMLElement | null;
|
||||
if (!header) return;
|
||||
|
||||
window.addEventListener('scroll', () => {
|
||||
if (window.scrollY > 100) {
|
||||
header.classList.add('scrolled');
|
||||
} else {
|
||||
header.classList.remove('scrolled');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const initDropdown = (): void => {
|
||||
const dropdown = document.querySelector('.dropdown') as HTMLElement | null;
|
||||
const dropdownToggle = dropdown?.querySelector('.dropdown-toggle') as HTMLElement | null;
|
||||
|
||||
if (!dropdown || !dropdownToggle) return;
|
||||
|
||||
dropdownToggle.addEventListener('click', (e: Event) => {
|
||||
e.preventDefault();
|
||||
dropdown.classList.toggle('open');
|
||||
});
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
document.addEventListener('click', (e: Event) => {
|
||||
if (!dropdown.contains(e.target as Node)) {
|
||||
dropdown.classList.remove('open');
|
||||
}
|
||||
});
|
||||
|
||||
// Close dropdown when pressing escape
|
||||
document.addEventListener('keydown', (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
dropdown.classList.remove('open');
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.header {
|
||||
min-height: 5dvh;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
background: rgba(8, 8, 23, 0.95);
|
||||
backdrop-filter: blur(10px);
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.navContainer{
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* Desktop-safe layout guard:
|
||||
This makes the header act like a single centered bar on desktop,
|
||||
and ensures .nav and .nav-menu sit inline whether .nav-menu is
|
||||
inside .nav or a sibling of .nav (the "restructure" case). */
|
||||
@media (min-width: 768px) {
|
||||
|
||||
.navContainer{
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
.navContainer > .nav,
|
||||
.navContainer .nav {
|
||||
width: 80%;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
/* if .nav-menu is a direct child of .header (restructured case),
|
||||
make it behave like the original inline menu */
|
||||
.navContainer > .nav-menu {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
transition: max-height 0.3s ease, opacity 0.3s ease, transform 0.3s ease;
|
||||
pointer-events: none; /* disable clicks when closed */
|
||||
}
|
||||
|
||||
.dropdown-menu.is-visible {
|
||||
max-height: 500px; /* adjust for content */
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
padding: 1em;
|
||||
/* padding / width handled in the desktop guard above */
|
||||
}
|
||||
|
||||
/* brand */
|
||||
.nav-brand {
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
background: var(--gradient-adventure);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
/* links container (desktop default) */
|
||||
.nav-menu {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
flex-wrap: nowrap; /* keep them inline with the brand */
|
||||
white-space: nowrap; /* avoid accidental wrapping */
|
||||
}
|
||||
|
||||
/* links */
|
||||
.nav-link {
|
||||
color: var(--foreground);
|
||||
text-decoration: none;
|
||||
position: relative;
|
||||
transition: var(--transition-smooth);
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
display: inline-flex; /* inline-flex prevents block behaviour */
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.nav-link:hover {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
/* Dropdown (desktop behaviour preserved) */
|
||||
.dropdown {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.dropdown-icon {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.dropdown.open .dropdown-icon {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: -50%;
|
||||
min-width: 200px;
|
||||
background: var(--card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 0;
|
||||
clip-path: polygon(0 0, calc(100% - 12px) 0, 100% 12px, 100% 100%, 12px 100%, 0 calc(100% - 12px));
|
||||
transform: translateY(-10px);
|
||||
transition: var(--transition-smooth);
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
/* Desktop show state (Vue sets .dropdown.open) */
|
||||
.dropdown.open .dropdown-menu {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.dropdown-menu a {
|
||||
display: block;
|
||||
padding: 0.75rem 1rem;
|
||||
color: var(--card-foreground);
|
||||
text-decoration: none;
|
||||
transition: var(--transition-smooth);
|
||||
}
|
||||
|
||||
.dropdown-menu a:hover {
|
||||
background: var(--accent);
|
||||
color: var(--background);
|
||||
}
|
||||
|
||||
/* --- Mobile menu button animation --- */
|
||||
.mobile-menu-btn {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 0.5rem;
|
||||
z-index: 1100; /* ensure it's above the nav overlay */
|
||||
}
|
||||
|
||||
.mobile-menu-btn span {
|
||||
width: 28px;
|
||||
height: 2px;
|
||||
background: var(--foreground);
|
||||
border-radius: 1px;
|
||||
transition: transform 0.3s ease, opacity 0.3s ease;
|
||||
}
|
||||
|
||||
/* Animate to X */
|
||||
.mobile-menu-btn.open span:nth-child(1) {
|
||||
transform: translateY(8px) rotate(45deg);
|
||||
}
|
||||
|
||||
.mobile-menu-btn.open span:nth-child(2) {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.mobile-menu-btn.open span:nth-child(3) {
|
||||
transform: translateY(-8px) rotate(-45deg);
|
||||
}
|
||||
|
||||
/* Mobile override (left intact — this preserves the mobile overlay you said you liked) */
|
||||
@media (max-width: 767px) {
|
||||
.nav-menu {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
|
||||
padding-top: 6rem; /* space for header/hamburger */
|
||||
gap: 2rem;
|
||||
|
||||
/* glass background */
|
||||
background: rgba(8, 8, 23, 0.85);
|
||||
backdrop-filter: blur(12px);
|
||||
|
||||
transform: translateY(-100%);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: transform 0.4s ease, opacity 0.4s ease;
|
||||
z-index: 1000;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.nav-menu.open {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
height: 100dvh;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
/* Dropdown in mobile: behave as inline sublist */
|
||||
.dropdown {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.dropdown-toggle {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
font-size: 1.5rem;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
position: static;
|
||||
opacity: 1 !important;
|
||||
visibility: visible !important;
|
||||
transform: none !important;
|
||||
background: transparent;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
min-width: unset;
|
||||
display: flex !important;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.dropdown-menu a {
|
||||
font-size: 1.25rem;
|
||||
color: var(--foreground);
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
.mobile-menu-btn {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user