astro_advokat/frontend/src/components/layouts/header/Header.astro
2026-03-31 17:31:46 +05:00

449 lines
18 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
import MobileMenu from "./MobileMenu.astro";
import Button from "@components/base/Button.astro";
import { CONTACT_CONSTANTS } from "@constants/constants.ts";
const { pathname } = Astro.url;
const navLinks = [
{ name: "Главная", href: "/", active: pathname === "/" },
{
name: "Услуги",
href: "/services",
active: pathname.startsWith("/services"),
children: [
{
name: "Административные дела",
href: "/services/administrative",
icon: "⚖️",
},
{
name: "Защита должников",
href: "/services/debt-protection",
icon: "💳",
},
{
name: "Арбитражные дела",
href: "/services/arbitration",
icon: "💼",
},
{
name: "Уголовные дела",
href: "/services/criminal",
icon: "⚖️",
},
{ name: "Гражданские дела", href: "/services/civil", icon: "📋" },
{ name: "Семейные дела", href: "/services/family", icon: "👨‍👩‍👧" },
{ name: "Дела СВО", href: "/services/svo", icon: "🛡️" },
],
},
{ name: "Кейсы", href: "/cases", active: pathname === "/cases" },
{ name: "Блог", href: "/blog", active: pathname === "/blog" },
{ name: "О Бюро", href: "/about", active: pathname === "/about" },
{ name: "Контакты", href: "/contacts", active: pathname === "/contacts" },
];
const rawLinks = navLinks.filter((link) => {
if (pathname === "/" && link.href === "/") return false;
return true;
});
---
<header
id="main-header"
class="sticky top-0 w-full py-4 px-4 md:px-8 lg:px-16 z-[100] bg-[#0a0f1c]/90 backdrop-blur-md border-b border-white/5 transition-all duration-300"
>
<div class="container px-4 md:px-8 mx-auto flex items-center justify-between">
<!-- Логотип -->
<a href="/" class="flex items-center group relative py-2">
<div
class="w-0 opacity-0 overflow-hidden transition-all duration-500 ease-[cubic-bezier(0.25,1,0.5,1)] group-hover:w-10 group-hover:opacity-100"
>
<div
class="w-10 h-10 min-w-10 bg-[#bf9b58] flex items-center justify-center rounded"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6 text-[#151b26]"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12c5.16-1.26 9-6.45 9-12V5zm0 4a3 3 0 0 1 3 3a3 3 0 0 1-3 3a3 3 0 0 1-3-3a3 3 0 0 1 3-3m5.13 12A9.7 9.7 0 0 1 12 20.92A9.7 9.7 0 0 1 6.87 17c-.34-.5-.63-1-.87-1.53c0-1.65 2.71-3 6-3s6 1.32 6 3c-.24.53-.53 1.03-.87 1.53"
></path>
</svg>
</div>
</div>
<div
class="flex flex-col transition-all duration-500 ease-[cubic-bezier(0.25,1,0.5,1)] pl-0 group-hover:pl-3"
>
<span
class="text-xl md:text-2xl font-black tracking-tighter text-white uppercase leading-none"
>
ADVOKAT<span class="text-[#bf9b58]">086</span>
</span>
<span
class="text-[10px] text-gray-400 tracking-[0.2em] uppercase hidden sm:block"
>Юридическая защита</span
>
</div>
</a>
<!-- Десктоп меню -->
<nav class="hidden lg:flex items-center gap-8 xl:gap-12">
{
rawLinks.map((link) => (
<div class="relative group">
{link.children ? (
<>
<button
class={`flex items-center gap-2 text-sm md:text-base font-bold uppercase tracking-wide transition-colors duration-300 py-2 ${link.active ? "text-[#bf9b58]" : "text-gray-300 hover:text-[#bf9b58]"}`}
aria-expanded="false"
aria-haspopup="true"
>
{link.name}
<svg
class="w-4 h-4 transition-transform duration-300 ease-out group-hover:-rotate-180"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 9l-7 7-7-7"
/>
</svg>
</button>
<div class="absolute top-full left-1/2 -translate-x-1/2 pt-4 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-300 ease-out transform group-hover:translate-y-0 translate-y-2 min-w-[300px]">
<div class="bg-[#0f1623]/95 backdrop-blur-xl border border-white/10 rounded-xl shadow-2xl shadow-black/50 overflow-hidden p-2 relative">
<div class="absolute -top-1.5 left-1/2 -translate-x-1/2 w-3 h-3 bg-[#0f1623] border-l border-t border-white/10 rotate-45" />
<div class="relative space-y-1">
{link.children.map((child) => (
<a
href={child.href}
class={`flex items-center gap-3 px-4 py-3 rounded-lg text-sm font-medium transition-all duration-200 ${pathname === child.href ? "bg-[#bf9b58]/10 text-[#bf9b58]" : "text-gray-300 hover:bg-white/5 hover:text-[#bf9b58]"}`}
>
{child.icon && (
<span class="text-lg flex-shrink-0">
{child.icon}
</span>
)}
<span>{child.name}</span>
{pathname === child.href && (
<svg
class="w-4 h-4 ml-auto text-[#bf9b58]"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 5l7 7-7 7"
/>
</svg>
)}
</a>
))}
</div>
<div class="mt-2 pt-2 border-t border-white/5">
<a
href={link.href}
class="flex items-center justify-center gap-2 px-4 py-2 text-xs font-bold uppercase tracking-wider text-[#bf9b58] hover:text-white transition-colors duration-200"
>
Все услуги
<svg
class="w-4 h-4 transition-transform group-hover:translate-x-1"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M17 8l4 4m0 0l-4 4m4-4H3"
/>
</svg>
</a>
</div>
</div>
</div>
</>
) : (
<a
href={link.href}
class={`text-sm md:text-base font-bold uppercase tracking-wide transition-colors duration-300 py-2 block ${link.active ? "text-[#bf9b58]" : "text-gray-300 hover:text-[#bf9b58]"}`}
>
{link.name}
</a>
)}
</div>
))
}
</nav>
<div class="flex items-center gap-4 lg:gap-6">
<!-- Иконка телефона для мобильных (видна только на мобильных < md) -->
<a
href={`tel:${CONTACT_CONSTANTS.phone.replace(/[()\s-]/g, '')}`}
class="md:hidden w-10 h-10 rounded-full bg-[#bf9b58]/20 border border-[#bf9b58]/50 flex items-center justify-center hover:bg-[#bf9b58]/30 transition-colors group"
title="Позвонить"
aria-label="Позвонить"
>
<svg
class="w-5 h-5 text-[#bf9b58] group-hover:text-white transition-colors"
fill="currentColor"
viewBox="0 0 24 24"
>
<path d="M6.62 10.79a15.91 15.91 0 006.59 6.59l2.2-2.2a1 1 0 011.11-.27 11.72 11.72 0 003.64.59 1 1 0 011 1V20a1 1 0 01-1 1A17 17 0 013 7a1 1 0 011-1h3.5a1 1 0 011 1 11.72 11.72 0 00.59 3.64 1 1 0 01-.27 1.11z"/>
</svg>
</a>
<!-- Блок авторизации - скрыт на мобильных (< md), показывается на md и выше -->
<!-- ВАЖНО: hidden по умолчанию, скрипт управляет видимостью внутренних блоков, но не трогает hidden у контейнера -->
<div id="auth-block" class="hidden md:flex items-center gap-3">
<!-- Кнопки для авторизованного пользователя -->
<div id="auth-user-block" class="hidden items-center gap-3">
<a
href="/profile"
class="w-10 h-10 rounded-full bg-[#bf9b58]/20 border border-[#bf9b58]/50 flex items-center justify-center hover:bg-[#bf9b58]/30 transition-colors group"
title="Личный кабинет"
>
<span id="user-initial" class="text-white font-bold text-lg group-hover:text-[#bf9b58]"></span>
</a>
<button
id="logout-btn"
class="w-10 h-10 rounded-full border border-red-500/30 flex items-center justify-center hover:bg-red-500/10 transition-colors group"
title="Выход"
>
<svg
class="w-5 h-5 text-red-400 group-hover:text-red-300"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"
/>
</svg>
</button>
</div>
<!-- Блок для неавторизованного -->
<div id="auth-guest-block" class="hidden flex-col items-end">
<button
data-consultation-modal
class="flex items-center gap-2 text-white font-bold text-lg hover:text-[#bf9b58] transition-colors cursor-pointer"
>
<svg
class="w-5 h-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"
/>
</svg>
{CONTACT_CONSTANTS.phone}
</button>
<div class="flex items-center gap-2">
<span class="relative flex h-2 w-2">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75" />
<span class="relative inline-flex rounded-full h-2 w-2 bg-emerald-500" />
</span>
<span class="text-[9px] text-gray-400 font-bold uppercase tracking-widest">
Онлайн
</span>
</div>
</div>
</div>
<!-- Кнопка-бургер -->
<button
id="menu-toggle"
class="lg:hidden relative w-12 h-12 rounded-full border border-transparent flex items-center justify-center z-[1000] cursor-pointer transition-all duration-300"
aria-label="Меню"
aria-expanded="false"
onclick="
const body = document.body;
const isOpen = body.classList.toggle('menu-open');
this.setAttribute('aria-expanded', isOpen);
body.style.overflow = isOpen ? 'hidden' : '';
"
>
<span class="burger-line line-1"></span>
<span class="burger-line line-2"></span>
<span class="burger-line line-3"></span>
</button>
</div>
</div>
<div
id="scroll-progress"
class="absolute bottom-0 left-0 h-0.5 bg-[#bf9b58] w-0 transition-all duration-75 ease-out z-50"
>
</div>
</header>
<MobileMenu links={rawLinks} />
<style>
:global(body.menu-open) #main-header {
background-color: transparent !important;
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
border-color: transparent !important;
}
#menu-toggle[aria-expanded="true"] {
background-color: #151b26;
border-color: rgba(255, 255, 255, 0.1);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
}
.burger-line {
position: absolute;
width: 26px;
height: 2px;
background-color: white;
border-radius: 2px;
transition: all 0.4s cubic-bezier(0.68, -0.6, 0.32, 1.6);
}
.line-1 {
transform: translateY(-8px);
}
.line-2 {
width: 18px;
transform: translateX(4px);
}
.line-3 {
transform: translateY(8px);
}
#menu-toggle[aria-expanded="true"] .line-1 {
transform: translateY(0) rotate(45deg);
background-color: #bf9b58;
width: 22px;
}
#menu-toggle[aria-expanded="true"] .line-2 {
opacity: 0;
transform: translateX(-20px);
}
#menu-toggle[aria-expanded="true"] .line-3 {
transform: translateY(0) rotate(-45deg);
background-color: #bf9b58;
width: 22px;
}
</style>
<script>
const initApp = () => {
const progressBar = document.getElementById("scroll-progress");
if (progressBar) {
const updateScrollProgress = () => {
const scrollTop =
document.documentElement.scrollTop || document.body.scrollTop;
const scrollHeight =
document.documentElement.scrollHeight -
document.documentElement.clientHeight;
const scrollPercent =
scrollHeight > 0 ? (scrollTop / scrollHeight) * 100 : 0;
progressBar.style.width = `${scrollPercent}%`;
};
window.addEventListener("scroll", updateScrollProgress, {
passive: true,
});
updateScrollProgress();
}
const checkAuth = async () => {
const authUserBlock = document.getElementById("auth-user-block");
const authGuestBlock = document.getElementById("auth-guest-block");
const userInitialEl = document.getElementById("user-initial");
if (!authUserBlock || !authGuestBlock) return;
try {
const response = await fetch('/api/auth/me', {
method: 'GET',
credentials: 'include',
});
const data = await response.json();
if (data.authenticated) {
authUserBlock.classList.remove("hidden");
authUserBlock.classList.add("flex");
authGuestBlock.classList.add("hidden");
authGuestBlock.classList.remove("flex");
if (userInitialEl && data.user?.name) {
const firstLetter = data.user.name.trim().charAt(0).toUpperCase();
userInitialEl.textContent = firstLetter;
}
} else {
authUserBlock.classList.add("hidden");
authUserBlock.classList.remove("flex");
authGuestBlock.classList.remove("hidden");
authGuestBlock.classList.add("flex");
}
} catch (error) {
console.error('[Header] Ошибка проверки авторизации:', error);
authUserBlock.classList.add("hidden");
authGuestBlock.classList.remove("hidden");
authGuestBlock.classList.add("flex");
}
};
checkAuth();
const logoutBtn = document.getElementById("logout-btn");
if (logoutBtn) {
logoutBtn.classList.add("cursor-pointer");
logoutBtn.addEventListener("click", async () => {
console.log('[Header] Выход из системы');
try {
const response = await fetch('/api/auth/logout', {
method: 'POST',
credentials: 'include',
});
if (response.ok) {
document.cookie = 'pb_auth=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;';
window.location.href = '/';
}
} catch (error) {
console.error('[Header] Ошибка при выходе:', error);
}
});
}
};
document.addEventListener("close-mobile-menu", () => {
const toggleBtn = document.getElementById("menu-toggle");
document.body.classList.remove("menu-open");
document.body.style.overflow = "";
if (toggleBtn) {
toggleBtn.setAttribute("aria-expanded", "false");
}
});
initApp();
document.addEventListener("astro:page-load", initApp);
</script>