AppHeader.vue 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. <template>
  2. <header ref="headerRef" class="w-9/10 lg:w-auto absolute top-7 center-0 z-50 bg-black/80 lg:bg-black/60">
  3. <nav class="flex items-center justify-center h-[55px] px-4 relative">
  4. <div class="hidden md:flex items-center justify-center gap-1">
  5. <NuxtLink
  6. v-for="item in navItems"
  7. :key="item.label"
  8. :to="item.href"
  9. class="relative text-xs font-open-sans font-semibold px-3 uppercase transition-colors duration-200 pb-1 leading-7"
  10. :class="[
  11. activeSection === item.href.slice(1)
  12. ? 'text-[#E3FA6D] after:absolute after:bottom-0 after:left-0 after:w-full after:h-0.5 after:bg-[#E3FA6D]'
  13. : 'text-white hover:text-white/85'
  14. ]"
  15. >
  16. {{ item.label }}
  17. </NuxtLink>
  18. </div>
  19. <NuxtLink to="#hero" class="md:hidden absolute left-4">
  20. <NuxtImg src="/img/logo_2.png" class="h-9 w-auto object-contain" />
  21. </NuxtLink>
  22. <button
  23. class="md:hidden absolute right-4 text-white focus:outline-none"
  24. aria-label="Abrir menu"
  25. @click.stop="menuOpen = !menuOpen"
  26. >
  27. <svg v-if="!menuOpen" xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
  28. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
  29. </svg>
  30. <svg v-else xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
  31. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
  32. </svg>
  33. </button>
  34. </nav>
  35. <div
  36. v-if="menuOpen"
  37. class="md:hidden flex flex-col items-center py-4 gap-4"
  38. >
  39. <NuxtLink
  40. v-for="item in navItems"
  41. :key="item.label"
  42. :to="item.href"
  43. class="relative text-xs font-open-sans font-semibold uppercase transition-colors duration-200"
  44. :class="[
  45. activeSection === item.href.slice(1)
  46. ? 'text-[#E3FA6D]'
  47. : 'text-white hover:text-white/85'
  48. ]"
  49. @click="menuOpen = false"
  50. >
  51. {{ item.label }}
  52. </NuxtLink>
  53. </div>
  54. </header>
  55. </template>
  56. <script setup lang="ts">
  57. const menuOpen = ref(false)
  58. const headerRef = ref<HTMLElement | null>(null)
  59. const activeSection = ref('hero')
  60. const navItems = [
  61. { label: 'Home', href: '#hero' },
  62. { label: 'A Imersão', href: '#imersao' },
  63. { label: 'Metodologia', href: '#metodologia' },
  64. { label: 'Executores', href: '#executores' },
  65. { label: 'Próximos Destinos', href: '#destinos' },
  66. { label: 'Contato', href: '#contato' },
  67. ]
  68. function handleClickOutside(event: MouseEvent) {
  69. if (menuOpen.value && headerRef.value && !headerRef.value.contains(event.target as Node)) {
  70. menuOpen.value = false
  71. }
  72. }
  73. onMounted(() => {
  74. document.addEventListener('click', handleClickOutside)
  75. const sectionIds = navItems.map(i => i.href.slice(1))
  76. const observer = new IntersectionObserver(
  77. (entries) => {
  78. for (const entry of entries) {
  79. if (entry.isIntersecting) {
  80. activeSection.value = entry.target.id
  81. }
  82. }
  83. },
  84. { threshold: 0.3 }
  85. )
  86. sectionIds.forEach(id => {
  87. const el = document.getElementById(id)
  88. if (el) observer.observe(el)
  89. })
  90. onUnmounted(() => {
  91. document.removeEventListener('click', handleClickOutside)
  92. observer.disconnect()
  93. })
  94. })
  95. </script>