astro_avtourist/frontend/src/components/layout/header/Header.astro
2026-05-07 20:37:05 +05:00

782 lines
22 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 Logo from "./Logo.astro";
import Navbar from "./Navbar.astro";
import MobileMenu from "./MobileMenu.astro";
import { COMPANY } from "@constants";
---
<header class="header-wrapper" id="header">
<!-- 1. Контейнер шапки -->
<div class="site-container header-container">
<div class="header-column header-left animate-load" data-delay="0">
<Logo />
</div>
<!--Navbar -->
<div
class="header-column header-center desktop-nav animate-load"
data-delay="100"
>
<Navbar />
</div>
<!-- Right side -->
<div class="header-column header-right animate-load" data-delay="200">
<a href={`tel:${COMPANY.phoneClean}`} class="header-phone" id="header-phone">
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="phone-icon"
>
<path
d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"
></path>
</svg>
<span class="phone-number">{COMPANY.phone}</span>
</a>
<div class="header-actions" id="auth-section"></div>
<div class="mobile-actions">
<a href={`tel:${COMPANY.phoneClean}`} class="mobile-phone-btn" aria-label="Позониь: {COMPANY.phone}">
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path
d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"
></path>
</svg>
</a>
<!-- Блок авторизации для мобильных -->
<div class="mobile-auth-block" id="mobile-auth-block"></div>
<button class="burger-btn" id="burger-btn" aria-label="Открыть меню">
<span class="burger-line"></span>
<span class="burger-line"></span>
<span class="burger-line"></span>
</button>
</div>
</div>
</div>
<!-- 2. Линия прогресса (теперь под контейнером, с высоким z-index) -->
<div class="header-progress-container animate-load" data-delay="300">
<div class="header-progress-bar" id="progress-bar"></div>
</div>
</header>
<MobileMenu />
<style>
.header-wrapper {
position: fixed;
top: 0;
left: 0;
width: 100%;
background-color: var(--color-light);
z-index: 1000;
transition:
background-color 0.3s ease,
box-shadow 0.3s ease;
margin: 0 !important;
padding: 0 !important;
border-bottom: 1px solid rgba(30, 48, 80, 0.05);
}
.header-wrapper.scrolled {
background-color: rgba(209, 217, 228, 0.98);
backdrop-filter: blur(10px);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}
.header-container {
display: grid;
grid-template-columns: 1fr auto 1fr;
align-items: center;
height: 80px;
transition: height 0.3s ease;
}
.header-wrapper.scrolled .header-container {
height: 70px;
}
.header-column {
display: flex;
align-items: center;
}
.header-left {
justify-content: flex-start;
}
.header-center {
justify-content: center;
}
.header-right {
justify-content: flex-end;
gap: 1.5rem;
}
.header-actions {
display: flex;
align-items: center;
}
.header-actions {
display: flex;
align-items: center;
}
/* НОМЕР ТЕЛЕФОНА В ШАПКЕ */
.header-phone {
display: flex;
align-items: center;
gap: 0.5rem;
color: #1e3050;
text-decoration: none;
font-weight: 600;
font-size: 0.95rem;
white-space: nowrap;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
padding: 0.6rem 1.2rem;
border-radius: 10px;
overflow: hidden;
letter-spacing: 0.3px;
cursor: pointer;
background: transparent;
position: relative;
}
.header-phone:hover {
color: #0a1a2e;
background: rgba(30, 48, 80, 0.05);
transform: translateY(-2px);
}
/* Эффект перелива при наведении */
.header-phone::after {
content: "";
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(
90deg,
transparent,
rgba(234, 194, 110, 0.1),
transparent
);
transition: left 0.5s ease;
pointer-events: none;
}
.header-phone:hover::after {
left: 100%;
}
.phone-icon {
flex-shrink: 0;
transition: transform 0.3s ease;
}
.header-phone:hover .phone-icon {
transform: scale(1.1);
}
.phone-number {
font-weight: 700;
}
/* ПОЛОСА ПРОГРЕССА */
.header-progress-container {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 3px;
background: rgba(234, 194, 110, 0.1);
z-index: 1001; /* Выше фона шапки */
overflow: hidden;
}
.header-progress-bar {
height: 100%;
width: 0%;
background: linear-gradient(90deg, #eac26e 0%, #f0d68a 50%, #eac26e 100%);
box-shadow: 0 0 10px rgba(234, 194, 110, 0.5);
}
/* БУРГЕР */
.burger-btn {
display: none;
flex-direction: column;
justify-content: space-between;
width: 28px;
height: 18px;
background: transparent;
border: none;
cursor: pointer;
transition: opacity 0.3s ease;
}
.burger-line {
width: 100%;
height: 2.5px;
background-color: #1e3050;
border-radius: 4px;
}
/* Мобильная кнопка телефона */
.mobile-phone-btn {
display: none;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
border-radius: 50%;
background-color: rgba(30, 48, 80, 0.1);
color: #1e3050;
text-decoration: none;
transition: all 0.3s ease;
}
.mobile-phone-btn:hover {
background-color: rgba(30, 48, 80, 0.2);
transform: scale(1.05);
}
.mobile-phone-btn:active {
transform: scale(0.95);
}
.mobile-actions {
display: flex;
align-items: center;
gap: 12px;
}
/* Мобильный блок авторизации */
.mobile-auth-block {
display: flex;
align-items: center;
transition: all 0.3s ease-out;
}
/* Анимация плавного исчезновения */
.mobile-auth-block.fade-out {
opacity: 0;
transform: scale(0.8);
pointer-events: none;
}
.mobile-auth-block.fade-out-complete {
opacity: 0;
width: 0;
padding: 0;
margin: 0;
overflow: hidden;
}
/* Кнопка входа - круглая иконка */
.mobile-auth-btn {
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
padding: 0;
background: transparent;
border: 2px solid #1e3050;
border-radius: 50%;
transition: all 0.3s ease-out;
color: #1e3050;
text-decoration: none;
transition: all 0.3s ease;
}
.mobile-auth-btn:hover {
background: #1e3050;
color: #fff;
}
/* Кнопка выхода - круглая иконка */
.mobile-auth-btn.logout-btn {
width: 36px;
height: 36px;
border: 2px solid #ef4444;
border-radius: 50%;
color: #ef4444;
}
.mobile-auth-btn.logout-btn:hover {
background: #ef4444;
color: #fff;
}
/* Кнопка входа - как в десктопе */
.mobile-auth-btn {
display: flex;
align-items: center;
gap: 6px;
padding: 0.5rem 0.8rem;
background: transparent;
border: 2px solid #1e3050;
border-radius: 8px;
color: #1e3050;
font-weight: 600;
font-size: 0.85rem;
text-decoration: none;
transition: all 0.3s ease;
}
.mobile-auth-btn:hover {
background: #1e3050;
color: #fff;
}
/* Аватар пользователя - как в десктопе */
.mobile-user-avatar {
width: 36px;
height: 36px;
border-radius: 50%;
background: linear-gradient(135deg, #eac26e 0%, #ce9f40 100%);
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 0.95rem;
cursor: default;
}
/* Кнопка выхода - красная */
.mobile-auth-btn.logout-btn {
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
padding: 0;
background: transparent;
border: 2px solid #ef4444;
border-radius: 50%;
color: #ef4444;
}
.mobile-auth-btn.logout-btn:hover {
background: #ef4444;
color: #fff;
}
/* Скрытие гамбургера при открытом меню */
.burger-btn.active {
opacity: 0;
pointer-events: none;
}
/* Мобильный блок авторизации - только для мобильных */
@media (min-width: 993px) {
.mobile-auth-block {
display: none !important;
}
}
/* --- АНИМАЦИИ ПРИ ЗАГРУЗКЕ --- */
@media (min-width: 993px) {
.mobile-actions {
display: none;
}
}
.animate-load {
opacity: 0;
transform: translateY(-20px);
transition:
opacity 0.6s cubic-bezier(0.4, 0, 0.2, 1),
transform 0.6s cubic-bezier(0.4, 0, 0.2, 1);
will-change: opacity, transform;
}
.animate-load.is-visible {
opacity: 1;
transform: translateY(0);
}
/* Задержки для последовательного появления */
.header-left[data-delay="0"] {
transition-delay: 0s;
}
.header-center[data-delay="100"] {
transition-delay: 0.1s;
}
.header-right[data-delay="200"] {
transition-delay: 0.2s;
}
.header-progress-container[data-delay="300"] {
transition-delay: 0.3s;
}
/* Уважаем prefers-reduced-motion */
@media (prefers-reduced-motion: reduce) {
.animate-load {
opacity: 1;
transform: none;
transition: none;
}
}
@media (min-width: 993px) {
.header-phone {
display: flex;
}
.mobile-phone-btn,
.burger-btn {
display: none;
}
}
@media (max-width: 992px) {
.header-phone {
display: none;
}
.mobile-actions {
display: flex;
align-items: center;
gap: 12px;
}
.mobile-phone-btn,
.burger-btn,
.mobile-auth-block {
display: flex;
}
.header-right {
gap: 0.5rem;
justify-content: flex-end;
}
.header-right > *:not(.mobile-actions) {
display: none;
}
/* Показываем мобильный блок авторизации только на мобильных */
.mobile-auth-block {
display: none;
}
}
@media (max-width: 992px) {
.mobile-auth-block {
display: flex;
}
}
@media (max-width: 767px) {
.header-container {
height: 64px;
padding: 0 1rem;
}
}
</style>
<script>
// Проверка авторизации и отображение правильного меню
function initAuth() {
const authSection = document.getElementById('auth-section');
if (!authSection) return;
const token = localStorage.getItem('auth_token');
const userData = localStorage.getItem('user');
if (token && userData) {
try {
const user = JSON.parse(userData);
const firstLetter = (user.name || user.email || 'U').charAt(0).toUpperCase();
// Скрываем телефон, показываем аватар и кнопку выхода
const displayName = user.name || user.email || 'Пользователь';
authSection.innerHTML = `
<div class="user-display">
<div class="user-avatar" title="${displayName}">${firstLetter}</div>
<button class="logout-btn" id="logout-btn" title="Выйти">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
<polyline points="16 17 21 12 16 7"></polyline>
<line x1="21" y1="12" x2="9" y2="12"></line>
</svg>
</button>
</div>
`;
// Обновляем мобильный блок авторизации
const mobileAuthBlock = document.getElementById('mobile-auth-block');
if (mobileAuthBlock) {
mobileAuthBlock.innerHTML = `
<button class="mobile-auth-btn logout-btn" id="mobile-logout-btn">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
<polyline points="16 17 21 12 16 7"></polyline>
<line x1="21" y1="12" x2="9" y2="12"></line>
</svg>
</button>
`;
document.getElementById('mobile-logout-btn')?.addEventListener('click', async () => {
const mobileAuthBlock = document.getElementById('mobile-auth-block');
// Запускаем анимацию плавного исчезновения
if (mobileAuthBlock) {
mobileAuthBlock.classList.add('fade-out');
}
// Ждем завершения анимации
await new Promise(resolve => setTimeout(resolve, 300));
try {
await fetch('/api/auth/logout', { method: 'POST' });
} catch (e) {
console.error('Logout error:', e);
}
// Очищаем только данные авторизации, НЕ трогаем cookie_consent
localStorage.removeItem('auth_token');
localStorage.removeItem('user');
localStorage.removeItem('pocketbase_auth');
// Удаляем cookie pb_auth всеми возможными способами
const cookieName = 'pb_auth';
const domains = ['', window.location.hostname, '.' + window.location.hostname];
const paths = ['/', '/api'];
domains.forEach(domain => {
paths.forEach(path => {
document.cookie = `${cookieName}=; path=${path}; domain=${domain}; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
document.cookie = `${cookieName}=; path=${path}; domain=${domain}; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax`;
document.cookie = `${cookieName}=; path=${path}; domain=${domain}; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Strict`;
});
});
// Альтернативный способ
document.cookie = 'pb_auth=; path=/; max-age=0';
document.querySelector('.header-right')?.classList.remove('auth-active');
window.location.reload();
});
}
// Обработчик выхода
document.getElementById('logout-btn')?.addEventListener('click', async () => {
// Находим блок user-display
const userDisplay = document.querySelector('.user-display');
// Запускаем анимацию плавного исчезновения
if (userDisplay) {
userDisplay.classList.add('fade-out');
}
// Ждем завершения анимации
await new Promise(resolve => setTimeout(resolve, 300));
try {
await fetch('/api/auth/logout', { method: 'POST' });
} catch (e) {
console.error('Logout error:', e);
}
// Очищаем только данные авторизации, НЕ трогаем cookie_consent
localStorage.removeItem('auth_token');
localStorage.removeItem('user');
localStorage.removeItem('pocketbase_auth');
// Удаляем cookie pb_auth всеми возможными способами
const cookieName = 'pb_auth';
const domains = ['', window.location.hostname, '.' + window.location.hostname];
const paths = ['/', '/api'];
domains.forEach(domain => {
paths.forEach(path => {
document.cookie = `${cookieName}=; path=${path}; domain=${domain}; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
document.cookie = `${cookieName}=; path=${path}; domain=${domain}; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax`;
document.cookie = `${cookieName}=; path=${path}; domain=${domain}; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Strict`;
});
});
document.querySelector('.header-right')?.classList.remove('auth-active');
window.location.reload();
});
// Добавляем класс для изменения порядка
document.querySelector('.header-right')?.classList.add('auth-active');
} catch (e) {
showPhone();
}
} else {
showPhone();
}
}
function showPhone() {
const authSection = document.getElementById('auth-section');
if (authSection) authSection.innerHTML = '';
document.querySelector('.header-right')?.classList.remove('auth-active');
}
// Проверка параметра loggedout и показ Toast
function checkLogoutToast() {
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get('loggedout') === 'true') {
// Очистить URL без перезагрузки
window.history.replaceState({}, '', window.location.pathname + window.location.hash);
// Показать Toast
setTimeout(() => {
window.toast?.show('Вы вышли из системы', 'info', 3000);
}, 100);
}
}
// Стили
const authStyle = `
.header-right.auth-active {
display: flex;
flex-direction: row-reverse;
justify-content: flex-start;
}
.header-right.auth-active .header-phone {
order: 2;
}
.header-right.auth-active .header-actions {
order: 1;
}
.user-display {
display: flex;
align-items: center;
gap: 0.5rem;
transition: all 0.3s ease-out;
}
/* Анимация плавного исчезновения для десктопа */
.user-display.fade-out {
opacity: 0;
transform: scale(0.8);
pointer-events: none;
}
.user-display.fade-out-complete {
opacity: 0;
width: 0;
gap: 0;
margin: 0;
padding: 0;
overflow: hidden;
}
.user-avatar {
width: 36px;
height: 36px;
border-radius: 50%;
background: linear-gradient(135deg, #eac26e 0%, #ce9f40 100%);
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 0.9rem;
}
.logout-btn {
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
background: #ef4444;
border: none;
border-radius: 8px;
color: #fff;
cursor: pointer;
transition: all 0.3s ease;
}
.logout-btn:hover {
background: #dc2626;
transform: scale(1.05);
}
@media (max-width: 992px) {
.user-avatar {
width: 32px;
height: 32px;
font-size: 0.8rem;
}
.logout-btn {
width: 32px;
height: 32px;
}
.logout-btn svg {
width: 16px;
height: 16px;
}
}
`;
document.addEventListener("DOMContentLoaded", () => {
const styleEl = document.createElement('style');
styleEl.textContent = authStyle;
document.head.appendChild(styleEl);
initAuth();
checkLogoutToast();
const animatedElements = document.querySelectorAll(".animate-load");
animatedElements.forEach((el) => {
const delay = parseInt((el as HTMLElement).dataset.delay || "0");
setTimeout(() => {
el.classList.add("is-visible");
}, delay);
});
});
document.addEventListener("DOMContentLoaded", () => {
const burgerBtn = document.getElementById("burger-btn");
const mobileMenuOverlay = document.getElementById("mobile-menu-overlay");
const progressBar = document.getElementById("progress-bar");
const header = document.getElementById("header");
function updateScroll() {
const scrollTop = window.scrollY || document.documentElement.scrollTop;
const docHeight =
document.documentElement.scrollHeight -
document.documentElement.clientHeight;
if (progressBar && docHeight > 0) {
const scrollPercent = (scrollTop / docHeight) * 100;
progressBar.style.width = `${Math.min(100, Math.max(0, scrollPercent))}%`;
}
if (scrollTop > 10) header?.classList.add("scrolled");
else header?.classList.remove("scrolled");
}
window.addEventListener("scroll", updateScroll, { passive: true });
// Открытие меню - добавляем active к бургеру для скрытия
burgerBtn?.addEventListener("click", () => {
mobileMenuOverlay?.classList.add("active");
burgerBtn?.classList.add("active");
document.body.style.overflow = "hidden";
});
updateScroll();
});
</script>