Files
yourpart3/frontend/src/components/AppNavigation.vue
Torsten Schulz (local) 07604cc9fa feat(navigation): enhance adult verification handling and notifications
- Updated navigationController to simplify the eroticChat menu structure.
- Enhanced adminService to notify users of adult verification status changes, including previous status.
- Improved AppNavigation and related components to register and unregister socket listeners for adult verification updates.
- Added localized messages for adult verification notifications in English, German, and Spanish.
- Introduced a verification hint in the EroticAccessView to guide users on document submission.
2026-03-27 13:23:44 +01:00

1001 lines
27 KiB
Vue
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.
<template>
<nav
ref="navRoot"
class="app-navigation"
:class="{ 'app-navigation--suppress-hover': suppressHover }"
>
<div class="nav-primary">
<ul>
<!-- Hauptmenü -->
<li
v-for="(item, key) in menu"
:key="key"
class="mainmenuitem"
:class="{ 'mainmenuitem--active': isItemActive(item), 'mainmenuitem--expanded': isMainExpanded(key), 'mainmenuitem--disabled': item.disabled }"
tabindex="0"
role="button"
:title="item.disabled ? $t(item.disabledReasonKey || 'socialnetwork.erotic.lockedShort') : undefined"
:aria-haspopup="hasTopLevelSubmenu(item) ? 'menu' : undefined"
:aria-expanded="hasTopLevelSubmenu(item) ? String(isMainExpanded(key)) : undefined"
@click="handleItem(item, $event, key)"
@keydown.enter.prevent="handleItem(item, $event, key)"
@keydown.space.prevent="handleItem(item, $event, key)"
>
<span
v-if="item.icon"
:style="`background-image:url('/images/icons/${item.icon}')`"
class="menu-icon"
>&nbsp;</span>
<span class="mainmenuitem__label">{{ $t(`navigation.${key}`) }}</span>
<span v-if="item.disabled" class="menu-lock-badge">18+</span>
<span v-if="hasTopLevelSubmenu(item)" class="mainmenuitem__caret">&#x25BE;</span>
<!-- Untermenü Ebene 1 -->
<ul v-if="hasTopLevelSubmenu(item)" class="submenu1" :class="{ 'submenu1--open': isMainExpanded(key) }">
<li
v-for="(subitem, subkey) in item.children"
:key="subkey"
tabindex="0"
role="menuitem"
:class="{ 'submenu1__item--expanded': isSubExpanded(`${key}:${subkey}`), 'submenu-item--disabled': subitem.disabled }"
:title="subitem.disabled ? $t(subitem.disabledReasonKey || 'socialnetwork.erotic.lockedShort') : undefined"
@click="handleSubItem(subitem, subkey, key, $event)"
@keydown.enter.prevent="handleSubItem(subitem, subkey, key, $event)"
@keydown.space.prevent="handleSubItem(subitem, subkey, key, $event)"
>
<span
v-if="subitem.icon"
:style="`background-image:url('/images/icons/${subitem.icon}')`"
class="submenu-icon"
>&nbsp;</span>
<span>{{ subitem?.label || $t(`navigation.m-${key}.${subkey}`) }}</span>
<span v-if="subitem.disabled" class="menu-lock-badge">18+</span>
<span
v-if="hasSecondLevelSubmenu(subitem, subkey)"
class="subsubmenu"
>&#x25B6;</span>
<!-- ForumUnterliste -->
<ul
v-if="subkey === 'forum' && forumList.length"
class="submenu2"
:class="{ 'submenu2--open': isSubExpanded(`${key}:${subkey}`) }"
>
<li
v-for="forum in forumList"
:key="forum.id"
tabindex="0"
role="menuitem"
@click="handleItem({ action: 'openForum', params: forum.id }, $event)"
@keydown.enter.prevent="handleItem({ action: 'openForum', params: forum.id }, $event)"
@keydown.space.prevent="handleItem({ action: 'openForum', params: forum.id }, $event)"
>
{{ forum.name }}
</li>
</ul>
<!-- Vokabeltrainer-Unterliste (Sprachen) -->
<ul
v-else-if="subkey === 'vocabtrainer' && vocabLanguagesList.length"
class="submenu2"
:class="{ 'submenu2--open': isSubExpanded(`${key}:${subkey}`) }"
>
<li
tabindex="0"
role="menuitem"
@click="handleItem({ path: '/socialnetwork/vocab/new' }, $event)"
@keydown.enter.prevent="handleItem({ path: '/socialnetwork/vocab/new' }, $event)"
@keydown.space.prevent="handleItem({ path: '/socialnetwork/vocab/new' }, $event)"
>
{{ $t('navigation.m-sprachenlernen.m-vocabtrainer.newLanguage') }}
</li>
<li
v-for="lang in vocabLanguagesList"
:key="lang.id"
tabindex="0"
role="menuitem"
@click="handleItem({ path: `/socialnetwork/vocab/${lang.id}` }, $event)"
@keydown.enter.prevent="handleItem({ path: `/socialnetwork/vocab/${lang.id}` }, $event)"
@keydown.space.prevent="handleItem({ path: `/socialnetwork/vocab/${lang.id}` }, $event)"
>
{{ lang.name }}
</li>
</ul>
<!-- Weiteres Untermenü Ebene 2 -->
<ul
v-else-if="subitem.children"
class="submenu2"
:class="{ 'submenu2--open': isSubExpanded(`${key}:${subkey}`) }"
>
<li
v-for="(subsubitem, subsubkey) in subitem.children"
:key="subsubkey"
tabindex="0"
role="menuitem"
:class="{ 'submenu-item--disabled': subsubitem.disabled }"
:title="subsubitem.disabled ? $t(subsubitem.disabledReasonKey || 'socialnetwork.erotic.lockedShort') : undefined"
@click="handleItem(subsubitem, $event)"
@keydown.enter.prevent="handleItem(subsubitem, $event)"
@keydown.space.prevent="handleItem(subsubitem, $event)"
>
<span
v-if="subsubitem.icon"
:style="`background-image:url('/images/icons/${subsubitem.icon}')`"
class="submenu-icon"
>&nbsp;</span>
<span>{{ subsubitem?.label || $t(`navigation.m-${key}.m-${subkey}.${subsubkey}`) }}</span>
<span v-if="subsubitem.disabled" class="menu-lock-badge">18+</span>
</li>
</ul>
</li>
<!-- Eingeloggte Freunde -->
<li
v-if="item.showLoggedinFriends === 1 && friendsList.length"
v-for="friend in friendsList"
:key="friend.id"
tabindex="0"
role="menuitem"
@click="handleItem({ action: 'openChat', params: friend.id }, $event)"
@keydown.enter.prevent="handleItem({ action: 'openChat', params: friend.id }, $event)"
@keydown.space.prevent="handleItem({ action: 'openChat', params: friend.id }, $event)"
>
{{ friend.username }}
<ul class="submenu2">
<li
tabindex="0"
role="menuitem"
@click="handleItem({ action: 'openChat', params: friend.id }, $event)"
@keydown.enter.prevent="handleItem({ action: 'openChat', params: friend.id }, $event)"
@keydown.space.prevent="handleItem({ action: 'openChat', params: friend.id }, $event)"
>
{{ $t('navigation.m-friends.chat') }}
</li>
<li
tabindex="0"
role="menuitem"
@click="handleItem({ action: 'openProfile', params: friend.id }, $event)"
@keydown.enter.prevent="handleItem({ action: 'openProfile', params: friend.id }, $event)"
@keydown.space.prevent="handleItem({ action: 'openProfile', params: friend.id }, $event)"
>
{{ $t('navigation.m-friends.profile') }}
</li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
<div class="right-block">
<button type="button" @click="accessMailbox" class="mailbox" aria-label="Mailbox"></button>
<span class="logoutblock">
<span class="username">{{ user.username }}</span>
<span class="menuitem" @click="logout">
{{ $t('navigation.logout') }}
</span>
</span>
</div>
</nav>
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import apiClient from '@/utils/axios.js';
import { EventBus } from '@/utils/eventBus.js';
import { showInfo } from '@/utils/feedback.js';
export default {
name: 'AppNavigation',
data() {
return {
forumList: [],
friendsList: [],
vocabLanguagesList: [],
expandedMainKey: null,
expandedSubKey: null,
pinnedMainKey: null,
pinnedSubKey: null,
suppressHover: false,
hoverReleaseTimer: null,
isMobileNav: false,
_forumsChangedHandler: null,
_friendLoginChangedHandler: null,
_reloadMenuHandler: null,
_adultVerificationChangedHandler: null
};
},
computed: {
...mapGetters(['menu', 'user', 'menuNeedsUpdate', 'socket'])
},
watch: {
menuNeedsUpdate(newVal) {
if (newVal) this.loadMenu();
},
$route() {
this.collapseMenus();
},
socket(newSocket) {
this.unregisterSocketListeners();
if (newSocket) {
this.registerSocketListeners(newSocket);
}
}
},
created() {
if (this.user?.id) {
this.loadMenu();
this.fetchForums();
this.fetchFriends();
this.fetchVocabLanguages();
}
this.updateViewportState();
window.addEventListener('resize', this.updateViewportState);
document.addEventListener('click', this.handleDocumentClick);
document.addEventListener('keydown', this.handleDocumentKeydown);
if (this.socket) {
this.registerSocketListeners(this.socket);
}
},
beforeUnmount() {
this.unregisterSocketListeners();
window.removeEventListener('resize', this.updateViewportState);
document.removeEventListener('click', this.handleDocumentClick);
document.removeEventListener('keydown', this.handleDocumentKeydown);
if (this.hoverReleaseTimer) {
clearTimeout(this.hoverReleaseTimer);
}
},
methods: {
...mapActions(['loadMenu', 'logout']),
registerSocketListeners(sock) {
if (!sock) return;
this._forumsChangedHandler = () => this.fetchForums();
this._friendLoginChangedHandler = () => this.fetchFriends();
this._reloadMenuHandler = () => this.loadMenu();
this._adultVerificationChangedHandler = async (payload = {}) => {
await this.loadMenu();
if (payload.status === 'approved') {
showInfo(this, this.$t('socialnetwork.erotic.notifications.approved'));
} else if (payload.status === 'rejected') {
showInfo(this, this.$t('socialnetwork.erotic.notifications.rejected'));
}
};
sock.on('forumschanged', this._forumsChangedHandler);
sock.on('friendloginchanged', this._friendLoginChangedHandler);
sock.on('reloadmenu', this._reloadMenuHandler);
sock.on('adultVerificationChanged', this._adultVerificationChangedHandler);
},
unregisterSocketListeners() {
const sock = this.socket;
if (!sock) return;
if (this._forumsChangedHandler) sock.off('forumschanged', this._forumsChangedHandler);
if (this._friendLoginChangedHandler) sock.off('friendloginchanged', this._friendLoginChangedHandler);
if (this._reloadMenuHandler) sock.off('reloadmenu', this._reloadMenuHandler);
if (this._adultVerificationChangedHandler) sock.off('adultVerificationChanged', this._adultVerificationChangedHandler);
this._forumsChangedHandler = null;
this._friendLoginChangedHandler = null;
this._reloadMenuHandler = null;
this._adultVerificationChangedHandler = null;
},
updateViewportState() {
this.isMobileNav = window.innerWidth <= 960;
if (!this.isMobileNav) {
this.expandedMainKey = null;
this.expandedSubKey = null;
}
},
isMainExpanded(key) {
return this.isMobileNav
? this.expandedMainKey === key
: this.pinnedMainKey === key;
},
isSubExpanded(key) {
return this.isMobileNav
? this.expandedSubKey === key
: this.pinnedSubKey === key;
},
toggleMain(key) {
this.expandedMainKey = this.expandedMainKey === key ? null : key;
this.expandedSubKey = null;
},
toggleSub(key) {
this.expandedSubKey = this.expandedSubKey === key ? null : key;
},
togglePinnedMain(key) {
this.pinnedMainKey = this.pinnedMainKey === key ? null : key;
this.pinnedSubKey = null;
},
togglePinnedSub(key) {
this.pinnedSubKey = this.pinnedSubKey === key ? null : key;
},
collapseMenus(options = {}) {
const { blurActiveElement = true } = options;
this.expandedMainKey = null;
this.expandedSubKey = null;
this.pinnedMainKey = null;
this.pinnedSubKey = null;
this.suppressHover = true;
if (this.hoverReleaseTimer) {
clearTimeout(this.hoverReleaseTimer);
}
this.hoverReleaseTimer = window.setTimeout(() => {
this.suppressHover = false;
this.hoverReleaseTimer = null;
}, 180);
if (blurActiveElement) {
this.$nextTick(() => {
if (document.activeElement && typeof document.activeElement.blur === 'function') {
document.activeElement.blur();
}
});
}
},
handleDocumentClick(event) {
const root = this.$refs.navRoot;
if (!root || root.contains(event.target)) {
return;
}
this.collapseMenus({ blurActiveElement: false });
},
handleDocumentKeydown(event) {
if (event.key === 'Escape') {
this.collapseMenus();
}
},
hasChildren(item) {
if (!item?.children) {
return false;
}
if (Array.isArray(item.children)) {
return item.children.length > 0;
}
return Object.keys(item.children).length > 0;
},
hasTopLevelSubmenu(item) {
return this.hasChildren(item) || (item?.showLoggedinFriends === 1 && this.friendsList.length > 0);
},
hasSecondLevelSubmenu(subitem, subkey) {
if (subkey === 'forum') {
return this.forumList.length > 0;
}
if (subkey === 'vocabtrainer') {
return this.vocabLanguagesList.length > 0;
}
return this.hasChildren(subitem);
},
isItemActive(item) {
if (!item?.path || !this.$route?.path) {
return false;
}
if (item.path === '/') {
return this.$route.path === '/';
}
return this.$route.path === item.path || this.$route.path.startsWith(`${item.path}/`);
},
openMultiChat() {
const ref = this.$root.$refs.multiChatDialog;
if (ref && typeof ref.open === 'function') {
ref.open();
} else if (ref?.$refs?.dialog && typeof ref.$refs.dialog.open === 'function') {
ref.$refs.dialog.open();
} else {
console.error('MultiChatDialog nicht bereit oder ohne open()');
}
},
openEroticChat() {
const ref = this.$root.$refs.multiChatDialog;
if (ref && typeof ref.open === 'function') {
ref.open(null, { adultOnly: true });
} else if (ref?.$refs?.dialog && typeof ref.$refs.dialog.open === 'function') {
ref.$refs.dialog.open();
} else {
console.error('MultiChatDialog nicht bereit oder ohne open()');
}
},
accessMailbox() {
const openMessages = () => {
EventBus.emit('open-falukant-messages');
};
if (this.$route?.path?.startsWith('/falukant')) {
openMessages();
return;
}
this.$router.push({ name: 'FalukantOverview' }).then(() => {
window.setTimeout(openMessages, 150);
});
},
async fetchForums() {
try {
const res = await apiClient.get('/api/forum');
this.forumList = res.data;
} catch (err) {
console.error('Error fetching forums:', err);
}
},
async fetchFriends() {
try {
const res = await apiClient.get('/api/socialnetwork/friends/loggedin');
this.friendsList = res.data;
} catch (err) {
console.error('Error fetching friends:', err);
}
},
async fetchVocabLanguages() {
try {
const res = await apiClient.get('/api/vocab/languages');
this.vocabLanguagesList = res.data?.languages || [];
} catch (err) {
console.error('Error fetching vocab languages:', err);
this.vocabLanguagesList = [];
}
},
openForum(forumId) {
this.$router.push({ name: 'Forum', params: { id: forumId } });
},
openProfile(userId) {
this.$root.$refs.userProfileDialog.userId = userId;
this.$root.$refs.userProfileDialog.open();
},
openChat(userId) {
const dialogRef = this.$root.$refs.multiChatDialog;
const friend = this.friendsList.find((entry) => entry.id === userId);
if (!dialogRef || typeof dialogRef.open !== 'function') {
this.openProfile(userId);
return;
}
dialogRef.open();
if (!friend?.username) {
return;
}
window.setTimeout(() => {
if (dialogRef.usersInRoom?.some((user) => user.name === friend.username)) {
dialogRef.selectedTargetUser = friend.username;
}
}, 250);
},
/**
* Einheitliche KlickLogik:
* 1) Nur aufklappen, wenn noch Untermenüs existieren
* 2) Bei `action`: custom action aufrufen
* 3) Bei `view`: Dialog/Window öffnen
* 4) Sonst: normale Router-Navigation
*/
handleItem(item, event, key = null) {
event.stopPropagation();
if (item?.disabled) {
this.handleDisabledItem(item);
return;
}
if (key && this.hasTopLevelSubmenu(item)) {
if (this.isMobileNav) {
this.toggleMain(key);
} else {
this.togglePinnedMain(key);
}
return;
}
if (this.hasChildren(item)) return;
// 2) custom action (openForum, openChat, ...)
if (item.action && typeof this[item.action] === 'function') {
this[item.action](item.params, event);
this.collapseMenus();
return;
}
// 3) view → Dialog/Window
if (item.view) {
const dialogRef = this.$root.$refs[item.class];
if (!dialogRef) {
console.error(`Dialog-Ref '${item.class}' nicht gefunden! Bitte prüfe Ref und Menü-Konfiguration.`);
return;
}
// Robust öffnen: erst open(), sonst auf inneres DialogWidget zurückgreifen
if (typeof dialogRef.open === 'function') {
dialogRef.open();
} else if (dialogRef.$refs?.dialog && typeof dialogRef.$refs.dialog.open === 'function') {
dialogRef.$refs.dialog.open();
} else {
console.error(`Dialog '${item.class}' gefunden, aber keine open()-Methode verfügbar.`);
}
this.collapseMenus();
return;
}
// 4) StandardNavigation
if (item.path) {
this.$router.push(item.path);
this.collapseMenus();
}
},
handleSubItem(item, subkey, parentKey, event) {
event.stopPropagation();
const compoundKey = `${parentKey}:${subkey}`;
if (this.hasSecondLevelSubmenu(item, subkey)) {
if (this.isMobileNav) {
this.toggleSub(compoundKey);
} else {
this.togglePinnedSub(compoundKey);
}
return;
}
this.handleItem(item, event);
},
handleDisabledItem(item) {
const isEroticEntry = item?.path?.startsWith('/socialnetwork/erotic')
|| item?.action === 'openEroticChat';
if (isEroticEntry) {
this.$router.push('/socialnetwork/erotic/access');
this.collapseMenus();
}
}
}
};
</script>
<style lang="scss" scoped>
@use '../assets/styles.scss' as *;
.app-navigation,
.nav-primary > ul {
display: flex;
padding: 0;
margin: 0;
flex-direction: row;
}
.app-navigation {
width: 100%;
max-width: none;
margin: 0 auto;
align-items: center;
gap: 10px;
padding: 6px 12px;
flex-wrap: wrap;
border-radius: 0;
background:
linear-gradient(180deg, rgba(249, 236, 225, 0.98) 0%, rgba(246, 228, 212, 0.98) 100%);
border-top: 1px solid rgba(93, 64, 55, 0.08);
border-bottom: 1px solid rgba(93, 64, 55, 0.12);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.46);
color: var(--color-text-primary);
z-index: 999;
}
.nav-primary {
flex: 1;
min-width: 0;
overflow: visible;
position: relative;
z-index: 1;
}
.nav-primary > ul {
min-width: 0;
justify-content: flex-start;
align-items: center;
gap: 6px;
flex-wrap: wrap;
}
ul {
list-style-type: none;
padding: 0;
margin: 0;
}
.mainmenuitem {
display: flex;
align-items: center;
justify-content: center;
min-height: 36px;
padding: 0 12px;
line-height: 1;
cursor: pointer;
border-radius: 999px;
border: 1px solid transparent;
transition: background-color 0.25s, color 0.25s, transform 0.2s, border-color 0.25s, box-shadow 0.25s;
}
.mainmenuitem:focus-visible,
.submenu1 > li:focus-visible,
.submenu2 > li:focus-visible,
.mailbox:focus-visible,
.menuitem:focus-visible {
outline: 3px solid rgba(120, 195, 138, 0.34);
outline-offset: 2px;
}
.mainmenuitem:hover {
background-color: rgba(248, 162, 43, 0.16);
border-color: rgba(248, 162, 43, 0.2);
transform: translateY(-1px);
}
.mainmenuitem:hover > span {
color: var(--color-primary);
}
.mainmenuitem--expanded {
background-color: rgba(248, 162, 43, 0.16);
border-color: rgba(248, 162, 43, 0.2);
}
.mainmenuitem--disabled,
.submenu-item--disabled {
opacity: 0.72;
cursor: help;
}
.mainmenuitem--disabled:hover {
transform: none;
background-color: rgba(248, 162, 43, 0.1);
border-color: rgba(248, 162, 43, 0.15);
}
.mainmenuitem--active {
background: rgba(255, 255, 255, 0.72);
border-color: rgba(248, 162, 43, 0.22);
box-shadow: 0 6px 14px rgba(93, 64, 55, 0.05);
}
.mainmenuitem__label {
white-space: nowrap;
}
.mainmenuitem__caret {
margin-left: 6px;
font-size: 0.7rem;
color: rgba(95, 75, 57, 0.7);
}
.menu-lock-badge {
display: inline-flex;
margin-left: 6px;
padding: 2px 6px;
border-radius: 999px;
background: rgba(164, 98, 72, 0.14);
color: var(--color-text-secondary);
font-size: 0.68rem;
font-weight: 800;
letter-spacing: 0.04em;
}
a {
text-decoration: none;
}
.right-block {
display: flex;
align-items: center;
gap: 12px;
padding-left: 10px;
margin-left: auto;
flex: 0 0 auto;
border-left: 1px solid rgba(93, 64, 55, 0.12);
position: relative;
z-index: 3;
background:
linear-gradient(180deg, rgba(249, 236, 225, 0.98) 0%, rgba(246, 228, 212, 0.98) 100%);
}
.logoutblock {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 2px;
}
.menuitem {
cursor: pointer;
color: var(--color-primary);
font-weight: 700;
}
.mailbox {
background-image: url('@/assets/images/icons/message24.png');
background-size: contain;
background-repeat: no-repeat;
background-position: center;
width: 38px;
height: 38px;
border-radius: 999px;
background-color: rgba(120, 195, 138, 0.12);
border: 1px solid rgba(93, 64, 55, 0.1);
box-shadow: none;
min-height: 0;
padding: 0;
}
.mainmenuitem { position: relative; font-weight: 700; }
.submenu1 {
position: absolute;
display: block;
border: 1px solid rgba(93, 64, 55, 0.12);
background: rgba(255, 252, 247, 0.99);
left: 0;
top: calc(100% + 10px);
min-width: 240px;
padding: 10px;
border-radius: var(--radius-lg);
box-shadow: 0 18px 30px rgba(93, 64, 55, 0.14);
max-height: 0;
overflow: visible;
opacity: 0;
visibility: hidden;
transition: max-height 0.25s ease-in-out,
opacity 0.05s ease-in-out,
visibility 0s 0.05s;
}
.mainmenuitem:hover .submenu1 {
max-height: 500px;
opacity: 1;
visibility: visible;
transition: max-height 0.25s ease-in-out,
opacity 0.05s ease-in-out,
visibility 0s;
}
.mainmenuitem--expanded .submenu1 {
max-height: 500px;
opacity: 1;
visibility: visible;
transition: max-height 0.25s ease-in-out,
opacity 0.05s ease-in-out,
visibility 0s;
}
.submenu1 > li {
display: block;
padding: 0.75em 0.9em;
line-height: 1.1em;
color: var(--color-text-secondary);
position: relative;
border-radius: 14px;
}
.submenu1 > li:hover {
color: var(--color-text-primary);
background-color: rgba(248, 162, 43, 0.12);
}
.menu-icon,
.submenu-icon {
display: inline-block;
background-repeat: no-repeat;
line-height: 1em;
}
.menu-icon {
width: 24px;
height: 24px;
margin-right: 8px;
}
.submenu-icon {
width: 1.2em;
height: 1em;
margin-right: 3px;
background-size: 1.2em 1.2em;
}
.submenu2 {
position: absolute;
display: block;
background: rgba(255, 252, 247, 0.98);
left: calc(100% + 8px);
top: 0;
min-width: 230px;
padding: 8px;
border-radius: var(--radius-lg);
border: 1px solid rgba(71, 52, 35, 0.12);
box-shadow: 0 14px 24px rgba(93, 64, 55, 0.12);
max-height: 0;
overflow: hidden;
opacity: 0;
visibility: hidden;
transition: max-height 0.25s ease-in-out,
opacity 0.05s ease-in-out,
visibility 0s 0.05s;
}
.submenu1 > li:hover .submenu2 {
max-height: 500px;
opacity: 1;
visibility: visible;
transition: max-height 0.25s ease-in-out,
opacity 0.05s ease-in-out,
visibility 0s;
}
.submenu1__item--expanded .submenu2 {
max-height: 500px;
opacity: 1;
visibility: visible;
transition: max-height 0.25s ease-in-out,
opacity 0.05s ease-in-out,
visibility 0s;
}
.app-navigation--suppress-hover .mainmenuitem:hover .submenu1,
.app-navigation--suppress-hover .submenu1 > li:hover .submenu2 {
max-height: 0;
opacity: 0;
visibility: hidden;
}
.submenu1__item--expanded {
color: var(--color-text-primary);
background-color: rgba(248, 162, 43, 0.08);
}
.submenu2 > li {
padding: 0.75em 0.9em;
line-height: 1em;
color: var(--color-text-secondary);
border-radius: 14px;
}
.submenu2 > li:hover {
color: var(--color-text-primary);
background-color: rgba(120, 195, 138, 0.14);
}
.submenu1 > li:focus-visible,
.submenu2 > li:focus-visible {
color: var(--color-text-primary);
background-color: rgba(248, 162, 43, 0.12);
}
.subsubmenu {
float: right;
font-size: 8pt;
margin-right: -4px;
}
.username {
font-weight: 800;
color: var(--color-text-secondary);
}
@media (max-width: 960px) {
.app-navigation {
margin: 0;
flex-direction: column;
flex-wrap: nowrap;
padding: 8px 10px;
align-items: stretch;
}
.nav-primary,
.nav-primary > ul,
.right-block {
width: 100%;
}
.nav-primary {
overflow-x: auto;
overflow-y: visible;
}
.nav-primary > ul {
min-width: 0;
flex-wrap: wrap;
gap: 8px;
}
.right-block {
justify-content: space-between;
padding-left: 0;
margin-left: 0;
border-left: 0;
padding-top: 6px;
border-top: 1px solid rgba(93, 64, 55, 0.1);
}
.logoutblock {
align-items: flex-start;
}
.mainmenuitem {
min-height: 42px;
width: calc(50% - 4px);
justify-content: flex-start;
padding: 0 14px;
}
.submenu1,
.submenu2 {
position: static;
min-width: 100%;
margin-top: 8px;
max-height: 0;
overflow: hidden;
opacity: 0;
visibility: hidden;
padding: 0 10px;
}
.submenu1--open,
.submenu2--open {
max-height: 1200px;
opacity: 1;
visibility: visible;
padding: 10px;
}
.submenu1 > li,
.submenu2 > li {
min-height: 42px;
display: flex;
align-items: center;
}
.mailbox {
width: 42px;
height: 42px;
}
}
@media (max-width: 640px) {
.mainmenuitem {
width: 100%;
}
.right-block {
flex-wrap: wrap;
gap: 10px;
}
.logoutblock {
width: 100%;
}
}
</style>