437 lines
13 KiB
Vue
437 lines
13 KiB
Vue
<template>
|
|
<header class="site-menu">
|
|
<div class="site-menu-inner">
|
|
<router-link
|
|
class="site-brand"
|
|
to="/"
|
|
>
|
|
<span class="site-brand-mark">S</span>
|
|
<span class="site-brand-heading">
|
|
<span class="site-brand-text-wrap">
|
|
<span class="site-brand-text">Socialize</span>
|
|
<span
|
|
class="site-brand-stage-badge"
|
|
:aria-label="t('nav.brandStageLabel')"
|
|
>
|
|
<span>{{ t('nav.brandStage') }}</span>
|
|
</span>
|
|
</span>
|
|
</span>
|
|
</router-link>
|
|
|
|
<nav
|
|
class="site-nav"
|
|
:aria-label="t('public.nav.ariaLabel')"
|
|
>
|
|
<div
|
|
ref="productMenuRef"
|
|
class="site-nav-group site-nav-product"
|
|
@pointerenter="openProductMenu"
|
|
@pointerleave="scheduleProductMenuClose"
|
|
@focusin="openProductMenu"
|
|
@focusout="scheduleProductMenuClose"
|
|
>
|
|
<button
|
|
type="button"
|
|
:aria-expanded="isProductMenuOpen"
|
|
aria-haspopup="true"
|
|
>
|
|
{{ t('public.nav.product') }}
|
|
</button>
|
|
<div
|
|
class="site-product-panel"
|
|
:class="{ open: isProductMenuOpen }"
|
|
>
|
|
<router-link
|
|
v-for="feature in productFeatureItems"
|
|
:key="feature.slug"
|
|
class="site-product-link"
|
|
:to="`/product/${feature.slug}`"
|
|
@click="closeProductMenu"
|
|
>
|
|
<span class="site-product-icon">
|
|
<svg
|
|
viewBox="0 0 24 24"
|
|
aria-hidden="true"
|
|
>
|
|
<path :d="feature.icon" />
|
|
</svg>
|
|
</span>
|
|
<span>
|
|
<strong>{{ t(`public.features.${feature.slug}.title`) }}</strong>
|
|
<small>{{ t(`public.features.${feature.slug}.description`) }}</small>
|
|
</span>
|
|
</router-link>
|
|
</div>
|
|
</div>
|
|
<router-link to="/pricing">{{ t('public.nav.pricing') }}</router-link>
|
|
<div
|
|
ref="resourcesMenuRef"
|
|
class="site-nav-group"
|
|
@pointerenter="openResourcesMenu"
|
|
@pointerleave="scheduleResourcesMenuClose"
|
|
@focusin="openResourcesMenu"
|
|
@focusout="scheduleResourcesMenuClose"
|
|
>
|
|
<button
|
|
type="button"
|
|
:aria-expanded="isResourcesMenuOpen"
|
|
aria-haspopup="true"
|
|
@click="toggleResourcesMenu"
|
|
>
|
|
{{ t('public.nav.resources') }}
|
|
</button>
|
|
<div
|
|
class="site-nav-menu"
|
|
:class="{ open: isResourcesMenuOpen }"
|
|
>
|
|
<router-link
|
|
to="/blogs"
|
|
@click="closeResourcesMenu"
|
|
>
|
|
{{ t('public.nav.blogs') }}
|
|
</router-link>
|
|
<router-link
|
|
to="/guides"
|
|
@click="closeResourcesMenu"
|
|
>
|
|
{{ t('public.nav.guides') }}
|
|
</router-link>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
|
|
<div
|
|
class="site-language-toggle"
|
|
:aria-label="t('public.nav.language')"
|
|
>
|
|
<button
|
|
type="button"
|
|
:class="{ active: currentLocale === 'en' }"
|
|
@click="setLocale('en')"
|
|
>
|
|
En
|
|
</button>
|
|
<button
|
|
type="button"
|
|
:class="{ active: currentLocale === 'fr' }"
|
|
@click="setLocale('fr')"
|
|
>
|
|
Fr
|
|
</button>
|
|
</div>
|
|
|
|
<router-link
|
|
class="site-login"
|
|
:to="authLink"
|
|
>
|
|
{{ authLabel }}
|
|
</router-link>
|
|
</div>
|
|
</header>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { computed, onBeforeUnmount, onMounted, ref } from 'vue';
|
|
import { useI18n } from 'vue-i18n';
|
|
import { useAuthStore } from '@/features/auth/stores/authStore.js';
|
|
import { productFeatureItems } from '@/static/productFeatures.js';
|
|
|
|
const allowedLocales = ['en', 'fr'];
|
|
const localeStorageKey = 'user-locale';
|
|
const authStore = useAuthStore();
|
|
const { locale, t } = useI18n();
|
|
const isProductMenuOpen = ref(false);
|
|
const isResourcesMenuOpen = ref(false);
|
|
const productMenuRef = ref(null);
|
|
const resourcesMenuRef = ref(null);
|
|
let productMenuCloseTimer = null;
|
|
let resourcesMenuCloseTimer = null;
|
|
|
|
const authLink = computed(() =>
|
|
authStore.isAuthenticated
|
|
? '/app/dashboard'
|
|
: '/login'
|
|
);
|
|
const authLabel = computed(() =>
|
|
authStore.isAuthenticated
|
|
? t('public.nav.openApp')
|
|
: t('public.nav.login')
|
|
);
|
|
const currentLocale = computed(() =>
|
|
allowedLocales.includes(locale.value) ? locale.value : 'en'
|
|
);
|
|
|
|
function setLocale(nextLocale) {
|
|
if (!allowedLocales.includes(nextLocale)) {
|
|
return;
|
|
}
|
|
|
|
locale.value = nextLocale;
|
|
|
|
if (typeof window !== 'undefined') {
|
|
window.sessionStorage.setItem(localeStorageKey, nextLocale);
|
|
}
|
|
}
|
|
|
|
function clearProductMenuCloseTimer() {
|
|
if (productMenuCloseTimer === null) {
|
|
return;
|
|
}
|
|
|
|
window.clearTimeout(productMenuCloseTimer);
|
|
productMenuCloseTimer = null;
|
|
}
|
|
|
|
function clearResourcesMenuCloseTimer() {
|
|
if (resourcesMenuCloseTimer === null) {
|
|
return;
|
|
}
|
|
|
|
window.clearTimeout(resourcesMenuCloseTimer);
|
|
resourcesMenuCloseTimer = null;
|
|
}
|
|
|
|
function openProductMenu() {
|
|
clearProductMenuCloseTimer();
|
|
isProductMenuOpen.value = true;
|
|
}
|
|
|
|
function openResourcesMenu() {
|
|
clearResourcesMenuCloseTimer();
|
|
isResourcesMenuOpen.value = true;
|
|
}
|
|
|
|
function closeProductMenu() {
|
|
clearProductMenuCloseTimer();
|
|
isProductMenuOpen.value = false;
|
|
}
|
|
|
|
function closeResourcesMenu() {
|
|
clearResourcesMenuCloseTimer();
|
|
isResourcesMenuOpen.value = false;
|
|
}
|
|
|
|
function toggleResourcesMenu() {
|
|
clearResourcesMenuCloseTimer();
|
|
isResourcesMenuOpen.value = !isResourcesMenuOpen.value;
|
|
}
|
|
|
|
function scheduleProductMenuClose() {
|
|
clearProductMenuCloseTimer();
|
|
|
|
productMenuCloseTimer = window.setTimeout(() => {
|
|
const activeElement = document.activeElement;
|
|
|
|
if (productMenuRef.value?.contains(activeElement)) {
|
|
return;
|
|
}
|
|
|
|
closeProductMenu();
|
|
}, 120);
|
|
}
|
|
|
|
function scheduleResourcesMenuClose() {
|
|
clearResourcesMenuCloseTimer();
|
|
|
|
resourcesMenuCloseTimer = window.setTimeout(() => {
|
|
const activeElement = document.activeElement;
|
|
|
|
if (resourcesMenuRef.value?.contains(activeElement)) {
|
|
return;
|
|
}
|
|
|
|
closeResourcesMenu();
|
|
}, 120);
|
|
}
|
|
|
|
onMounted(() => {
|
|
const storedLocale = window.sessionStorage.getItem(localeStorageKey);
|
|
|
|
if (allowedLocales.includes(storedLocale)) {
|
|
locale.value = storedLocale;
|
|
}
|
|
});
|
|
|
|
onBeforeUnmount(() => {
|
|
clearProductMenuCloseTimer();
|
|
clearResourcesMenuCloseTimer();
|
|
});
|
|
</script>
|
|
|
|
<style scoped>
|
|
.site-menu {
|
|
@apply sticky top-0 z-30 w-full;
|
|
background: rgba(255, 250, 242, 0.9);
|
|
backdrop-filter: blur(18px);
|
|
border-bottom: 1px solid rgba(23, 32, 51, 0.08);
|
|
}
|
|
|
|
.site-menu-inner {
|
|
@apply mx-auto flex w-full max-w-7xl items-center justify-between gap-4 px-5 py-4 md:px-8;
|
|
}
|
|
|
|
.site-brand {
|
|
@apply flex min-w-0 items-start gap-3 no-underline;
|
|
color: #172033;
|
|
}
|
|
|
|
.site-brand-mark {
|
|
@apply flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-2xl text-base font-black;
|
|
background: linear-gradient(135deg, #ff8a3d 0%, #ef4444 100%);
|
|
color: #fffaf2;
|
|
}
|
|
|
|
.site-brand-heading {
|
|
@apply flex min-w-0 items-center;
|
|
}
|
|
|
|
.site-brand-text-wrap {
|
|
@apply relative inline-flex min-w-0 items-center;
|
|
}
|
|
|
|
.site-brand-text {
|
|
@apply truncate text-lg font-black uppercase tracking-[0.18em];
|
|
line-height: 2.5rem;
|
|
}
|
|
|
|
.site-brand-stage-badge {
|
|
@apply absolute inline-flex h-4 items-center rounded-sm border px-1.5 text-[0.55rem] font-black uppercase tracking-[0.08em];
|
|
background: rgba(255, 231, 199, 0.46);
|
|
border-color: rgba(242, 179, 107, 0.38);
|
|
color: #925000;
|
|
left: 0;
|
|
line-height: 1;
|
|
top: calc(100% - 0.45rem);
|
|
}
|
|
|
|
.site-nav {
|
|
@apply hidden items-center justify-center gap-2 sm:flex;
|
|
}
|
|
|
|
.site-nav a {
|
|
@apply rounded-[0.65rem] px-4 py-2 text-sm font-semibold no-underline transition-colors;
|
|
color: #44516a;
|
|
}
|
|
|
|
.site-nav a:hover,
|
|
.site-nav-group:hover > button {
|
|
background: rgba(23, 32, 51, 0.06);
|
|
color: #172033;
|
|
}
|
|
|
|
.site-nav-group {
|
|
@apply relative;
|
|
}
|
|
|
|
.site-nav-group > button {
|
|
@apply rounded-[0.65rem] px-4 py-2 text-sm font-semibold transition-colors;
|
|
color: #44516a;
|
|
}
|
|
|
|
.site-nav-menu {
|
|
@apply invisible absolute left-0 top-[calc(100%+0.5rem)] flex min-w-36 flex-col gap-1 rounded-[1rem] border p-2 opacity-0 transition-opacity;
|
|
background: rgba(255, 255, 255, 0.98);
|
|
border-color: rgba(23, 32, 51, 0.08);
|
|
box-shadow: 0 18px 40px rgba(23, 32, 51, 0.12);
|
|
}
|
|
|
|
.site-nav-group:hover .site-nav-menu,
|
|
.site-nav-group:focus-within .site-nav-menu,
|
|
.site-nav-menu.open {
|
|
@apply visible opacity-100;
|
|
}
|
|
|
|
.site-nav-menu a {
|
|
@apply rounded-[0.75rem] px-3 py-2;
|
|
}
|
|
|
|
.site-product-panel {
|
|
@apply invisible absolute left-1/2 top-full grid w-[min(56rem,calc(100vw-2rem))] -translate-x-1/2 grid-cols-3 gap-2 rounded-[1.25rem] border p-3 opacity-0 transition-opacity;
|
|
background: rgba(255, 255, 255, 0.98);
|
|
border-color: rgba(23, 32, 51, 0.08);
|
|
box-shadow: 0 24px 60px rgba(23, 32, 51, 0.14);
|
|
}
|
|
|
|
.site-nav-product:hover .site-product-panel,
|
|
.site-nav-product:focus-within .site-product-panel,
|
|
.site-product-panel.open {
|
|
@apply visible opacity-100;
|
|
}
|
|
|
|
.site-product-link {
|
|
@apply grid grid-cols-[2.5rem_1fr] gap-3 rounded-[0.9rem] p-3 no-underline transition-colors;
|
|
}
|
|
|
|
.site-product-link:hover {
|
|
background: rgba(23, 32, 51, 0.05);
|
|
}
|
|
|
|
.site-product-icon {
|
|
@apply flex h-10 w-10 items-center justify-center rounded-[0.75rem];
|
|
background: rgba(15, 118, 110, 0.1);
|
|
color: #0f766e;
|
|
}
|
|
|
|
.site-product-icon svg {
|
|
@apply h-5 w-5;
|
|
fill: currentColor;
|
|
}
|
|
|
|
.site-product-link strong {
|
|
@apply block text-sm font-black leading-5;
|
|
color: #172033;
|
|
}
|
|
|
|
.site-product-link small {
|
|
@apply mt-1 block text-xs font-medium leading-5;
|
|
color: #44516a;
|
|
}
|
|
|
|
.site-language-toggle {
|
|
@apply flex items-center rounded-full border p-1;
|
|
background: rgba(255, 255, 255, 0.64);
|
|
border-color: rgba(23, 32, 51, 0.1);
|
|
}
|
|
|
|
.site-language-toggle button {
|
|
@apply flex h-8 min-w-9 items-center justify-center rounded-full px-3 text-xs font-black uppercase transition-colors;
|
|
color: #44516a;
|
|
}
|
|
|
|
.site-language-toggle button:hover {
|
|
color: #172033;
|
|
}
|
|
|
|
.site-language-toggle button.active {
|
|
background: #172033;
|
|
color: #fffaf2;
|
|
}
|
|
|
|
.site-login {
|
|
@apply flex h-10 min-w-[7.75rem] items-center justify-center rounded-full px-4 text-sm font-bold no-underline transition-colors;
|
|
background: #172033;
|
|
color: #fffaf2;
|
|
}
|
|
|
|
.site-login:hover {
|
|
background: #0f766e;
|
|
}
|
|
|
|
@media (max-width: 420px) {
|
|
.site-brand-heading {
|
|
@apply hidden;
|
|
}
|
|
|
|
.site-menu-inner {
|
|
@apply gap-3;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 820px) {
|
|
.site-product-panel {
|
|
@apply w-[min(30rem,calc(100vw-2rem))] grid-cols-1;
|
|
}
|
|
}
|
|
</style>
|