1142 lines
29 KiB
Vue
1142 lines
29 KiB
Vue
<template>
|
|
<header class="app-header">
|
|
<template v-if="isHome">
|
|
<div
|
|
class="home-header"
|
|
:class="{ compact: isCompactHomeHeader }"
|
|
>
|
|
<div
|
|
class="home-banner"
|
|
@mousemove="handleBannerMouseMove"
|
|
@mouseleave="resetBannerMotion"
|
|
>
|
|
<div class="banner-backdrop" />
|
|
<div class="animated-banner">
|
|
<div
|
|
v-for="layer in bannerLayers"
|
|
:key="layer.key"
|
|
class="layer"
|
|
:style="getLayerStyle(layer)"
|
|
>
|
|
<img
|
|
:src="layer.src"
|
|
alt=""
|
|
>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="page-shell banner-shell">
|
|
<div class="banner-nav-row">
|
|
<nav class="banner-nav">
|
|
<button
|
|
v-for="entry in homeNavEntries"
|
|
:key="entry.label"
|
|
class="banner-link"
|
|
@click="entry.action()"
|
|
>
|
|
<van-icon
|
|
:name="entry.icon"
|
|
size="16"
|
|
/>
|
|
<span>{{ entry.label }}</span>
|
|
</button>
|
|
</nav>
|
|
|
|
<div class="banner-search-wrap">
|
|
<form
|
|
class="search-bar"
|
|
@submit.prevent="handleSearch"
|
|
>
|
|
<div class="search-content">
|
|
<van-icon
|
|
name="search"
|
|
size="20"
|
|
class="search-icon"
|
|
/>
|
|
<input
|
|
v-model="searchKeyword"
|
|
class="search-input"
|
|
type="text"
|
|
maxlength="100"
|
|
placeholder="搜索视频、用户、关键词"
|
|
>
|
|
</div>
|
|
<button
|
|
type="submit"
|
|
class="search-btn"
|
|
>
|
|
<van-icon
|
|
name="search"
|
|
size="22"
|
|
/>
|
|
</button>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="banner-actions">
|
|
<button
|
|
class="avatar-entry"
|
|
@click="goToProfileOrLogin"
|
|
>
|
|
<van-image
|
|
:src="displayAvatar"
|
|
round
|
|
width="36"
|
|
height="36"
|
|
/>
|
|
</button>
|
|
<button
|
|
class="banner-action"
|
|
@click="router.push('/notifications')"
|
|
>
|
|
<van-badge
|
|
:content="messageStore.unreadNotificationCount || ''"
|
|
:show-zero="false"
|
|
>
|
|
<van-icon
|
|
name="bell"
|
|
size="18"
|
|
/>
|
|
</van-badge>
|
|
<span>消息</span>
|
|
</button>
|
|
<button
|
|
class="banner-action"
|
|
@click="router.push('/my/favorites')"
|
|
>
|
|
<van-icon
|
|
name="star-o"
|
|
size="18"
|
|
/>
|
|
<span>收藏</span>
|
|
</button>
|
|
<button
|
|
class="banner-action"
|
|
@click="router.push('/my/history')"
|
|
>
|
|
<van-icon
|
|
name="clock-o"
|
|
size="18"
|
|
/>
|
|
<span>历史</span>
|
|
</button>
|
|
<button
|
|
class="banner-action"
|
|
@click="router.push(userStore.isLoggedIn ? '/my/videos' : '/login')"
|
|
>
|
|
<van-icon
|
|
name="video-o"
|
|
size="18"
|
|
/>
|
|
<span>创作中心</span>
|
|
</button>
|
|
<button
|
|
class="upload-entry"
|
|
@click="router.push(userStore.isLoggedIn ? '/upload' : '/login')"
|
|
>
|
|
<van-icon
|
|
name="upgrade"
|
|
size="16"
|
|
/>
|
|
<span>投稿</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="home-sticky-bar">
|
|
<div class="page-shell sticky-shell">
|
|
<div
|
|
class="sticky-brand"
|
|
@click="router.push('/')"
|
|
>
|
|
<div class="brand-mark">
|
|
<span class="brand-eye left" />
|
|
<span class="brand-eye right" />
|
|
</div>
|
|
<div class="brand-copy">
|
|
<strong>HAOVP</strong>
|
|
<span>在线视频平台</span>
|
|
</div>
|
|
</div>
|
|
|
|
<nav class="sticky-nav">
|
|
<button
|
|
v-for="entry in stickyEntries"
|
|
:key="entry.label"
|
|
class="sticky-link"
|
|
:class="{ active: entry.active }"
|
|
@click="entry.action()"
|
|
>
|
|
{{ entry.label }}
|
|
</button>
|
|
</nav>
|
|
|
|
<div class="sticky-search-wrap">
|
|
<form
|
|
class="search-bar compact-search"
|
|
@submit.prevent="handleSearch"
|
|
>
|
|
<div class="search-content">
|
|
<input
|
|
v-model="searchKeyword"
|
|
class="search-input"
|
|
type="text"
|
|
maxlength="100"
|
|
placeholder="搜索视频、用户、关键词"
|
|
>
|
|
</div>
|
|
<button
|
|
type="submit"
|
|
class="search-btn"
|
|
>
|
|
<van-icon
|
|
name="search"
|
|
size="20"
|
|
/>
|
|
</button>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="sticky-actions">
|
|
<button
|
|
class="avatar-entry compact"
|
|
@click="goToProfileOrLogin"
|
|
>
|
|
<van-image
|
|
:src="displayAvatar"
|
|
round
|
|
width="32"
|
|
height="32"
|
|
/>
|
|
</button>
|
|
<button
|
|
class="icon-action"
|
|
@click="router.push('/notifications')"
|
|
>
|
|
<van-badge
|
|
:content="messageStore.unreadNotificationCount || ''"
|
|
:show-zero="false"
|
|
>
|
|
<van-icon
|
|
name="bell"
|
|
size="18"
|
|
/>
|
|
</van-badge>
|
|
</button>
|
|
<button
|
|
class="icon-action"
|
|
@click="router.push('/my/favorites')"
|
|
>
|
|
<van-icon
|
|
name="star-o"
|
|
size="18"
|
|
/>
|
|
</button>
|
|
<button
|
|
class="icon-action"
|
|
@click="router.push('/my/history')"
|
|
>
|
|
<van-icon
|
|
name="clock-o"
|
|
size="18"
|
|
/>
|
|
</button>
|
|
<button
|
|
class="upload-entry compact"
|
|
@click="router.push(userStore.isLoggedIn ? '/upload' : '/login')"
|
|
>
|
|
投稿
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<template v-else>
|
|
<div class="topbar">
|
|
<div class="page-shell topbar-shell">
|
|
<nav class="top-nav">
|
|
<button
|
|
v-for="entry in stickyEntries"
|
|
:key="entry.label"
|
|
class="top-link"
|
|
:class="{ active: entry.active }"
|
|
@click="entry.action()"
|
|
>
|
|
{{ entry.label }}
|
|
</button>
|
|
</nav>
|
|
|
|
<div class="top-actions">
|
|
<template v-if="!userStore.isLoggedIn">
|
|
<button
|
|
class="top-link"
|
|
@click="router.push('/login')"
|
|
>
|
|
登录
|
|
</button>
|
|
<button
|
|
class="top-link"
|
|
@click="router.push('/register')"
|
|
>
|
|
注册
|
|
</button>
|
|
</template>
|
|
|
|
<template v-else>
|
|
<button
|
|
class="top-link"
|
|
@click="router.push('/my/favorites')"
|
|
>
|
|
收藏
|
|
</button>
|
|
<button
|
|
class="top-link"
|
|
@click="router.push('/my/history')"
|
|
>
|
|
历史
|
|
</button>
|
|
|
|
<van-popover
|
|
v-model:show="showMessagePopover"
|
|
trigger="click"
|
|
placement="bottom-end"
|
|
>
|
|
<div class="popover-menu">
|
|
<button
|
|
class="popover-item"
|
|
@click="goToMessages"
|
|
>
|
|
<span>私信</span>
|
|
<van-badge
|
|
v-if="messageStore.unreadMessageCount"
|
|
:content="messageStore.unreadMessageCount"
|
|
/>
|
|
</button>
|
|
<button
|
|
class="popover-item"
|
|
@click="goToNotifications"
|
|
>
|
|
<span>通知</span>
|
|
<van-badge
|
|
v-if="messageStore.unreadNotificationCount"
|
|
:content="messageStore.unreadNotificationCount"
|
|
/>
|
|
</button>
|
|
</div>
|
|
<template #reference>
|
|
<button class="top-icon">
|
|
<van-badge
|
|
:content="messageStore.totalUnreadCount || ''"
|
|
:show-zero="false"
|
|
>
|
|
<van-icon
|
|
name="bell"
|
|
size="18"
|
|
/>
|
|
</van-badge>
|
|
</button>
|
|
</template>
|
|
</van-popover>
|
|
</template>
|
|
|
|
<button
|
|
class="upload-entry compact"
|
|
@click="router.push(userStore.isLoggedIn ? '/upload' : '/login')"
|
|
>
|
|
投稿
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mainbar">
|
|
<div class="page-shell mainbar-shell">
|
|
<div
|
|
class="sticky-brand"
|
|
@click="router.push('/')"
|
|
>
|
|
<div class="brand-mark">
|
|
<span class="brand-eye left" />
|
|
<span class="brand-eye right" />
|
|
</div>
|
|
<div class="brand-copy">
|
|
<strong>HAOVP</strong>
|
|
<span>在线视频平台</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="sticky-search-wrap">
|
|
<form
|
|
class="search-bar compact-search"
|
|
@submit.prevent="handleSearch"
|
|
>
|
|
<div class="search-content">
|
|
<input
|
|
v-model="searchKeyword"
|
|
class="search-input"
|
|
type="text"
|
|
maxlength="100"
|
|
placeholder="搜索视频、用户、关键词"
|
|
>
|
|
</div>
|
|
<button
|
|
type="submit"
|
|
class="search-btn"
|
|
>
|
|
<van-icon
|
|
name="search"
|
|
size="20"
|
|
/>
|
|
</button>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="user-area">
|
|
<template v-if="!userStore.isLoggedIn">
|
|
<button
|
|
class="login-btn"
|
|
@click="router.push('/login')"
|
|
>
|
|
登录
|
|
</button>
|
|
</template>
|
|
|
|
<template v-else>
|
|
<button
|
|
class="user-chip"
|
|
@click="goToProfileOrLogin"
|
|
>
|
|
<van-image
|
|
:src="displayAvatar"
|
|
round
|
|
width="36"
|
|
height="36"
|
|
/>
|
|
<div class="user-copy">
|
|
<strong>{{ displayNickname }}</strong>
|
|
<span>{{ displaySignature }}</span>
|
|
</div>
|
|
</button>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</header>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
import { useMessageStore, useUserStore } from '@/stores'
|
|
import { resolveAvatarUrl } from '@/utils/media'
|
|
|
|
type BannerLayer = {
|
|
key: string
|
|
src: string
|
|
depthX: number
|
|
baseX?: number
|
|
baseY?: number
|
|
scale?: number
|
|
brightness?: number
|
|
opacity?: number
|
|
}
|
|
|
|
const router = useRouter()
|
|
const route = useRoute()
|
|
const userStore = useUserStore()
|
|
const messageStore = useMessageStore()
|
|
|
|
const searchKeyword = ref('')
|
|
const showMessagePopover = ref(false)
|
|
const bannerMotionX = ref(0)
|
|
const homeScrollY = ref(0)
|
|
const homeHeaderCompact = ref(false)
|
|
const defaultAvatar =
|
|
'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48Y2lyY2xlIGN4PSI1MCIgY3k9IjUwIiByPSI1MCIgZmlsbD0iI2YzZjRmNiIvPjxjaXJjbGUgY3g9IjUwIiBjeT0iMzUiIHI9IjE1IiBmaWxsPSIjYmRjM2NmIi8+PGVsbGlwc2UgY3g9IjUwIiBjeT0iNzUiIHJ4PSIyNSIgcnk9IjIwIiBmaWxsPSIjYmRjM2NmIi8+PC9zdmc+'
|
|
|
|
const isHome = computed(() => route.path === '/')
|
|
const isCompactHomeHeader = computed(() => homeHeaderCompact.value)
|
|
const displayAvatar = computed(() => resolveAvatarUrl(userStore.userInfo?.avatar, defaultAvatar))
|
|
const displayNickname = computed(() => userStore.userInfo?.nickname || userStore.authUser?.username || '未登录用户')
|
|
const displaySignature = computed(() => userStore.userInfo?.signature || '完善资料后展示个性签名')
|
|
|
|
const bannerLayers: BannerLayer[] = [
|
|
{ key: 'l1', src: '/home-banner/48bee957f95b7bc5f0e1b7fc14b25a4cca26901a.png', depthX: 0, scale: 1 },
|
|
{ key: 'l2', src: '/home-banner/eca4ed09885bef57072526002c8900467a07dc3f.png', depthX: 7, baseX: -9.29, scale: 1 },
|
|
{ key: 'l3', src: '/home-banner/e52d6aecd75db0e310c73185304fd927834ac716.png', depthX: 28, baseX: 92.9, scale: 1 },
|
|
{ key: 'l4', src: '/home-banner/62aee277e1d935580f66d296f349bd358cd956a0.png', depthX: 0, scale: 1 },
|
|
{ key: 'l5', src: '/home-banner/b3ee9e71fbcc0520a1ac32e2b3a2d0dc6086bb9b.png', depthX: 72, baseX: 557.4, scale: 1 },
|
|
{ key: 'l6', src: '/home-banner/85e5279a1d53d4cb46033e1619d78628415f98c4.png', depthX: 0, scale: 1 },
|
|
{ key: 'l7', src: '/home-banner/0095303b06f64e05ba354655c0a6c83fb618f8e7.png', depthX: 0, scale: 1 },
|
|
{ key: 'l8', src: '/home-banner/0415c092aa2c61c1ca25ba69b8c77130f1ab37c3.png', depthX: 0, scale: 1 },
|
|
{ key: 'l9', src: '/home-banner/752ba46c080dca88401aac470f134ac0e6444705.png', depthX: 6, baseX: -8.26, baseY: 2.06, scale: 1 },
|
|
{ key: 'l10', src: '/home-banner/1fb1501cd66d326f296e78a4561f7cc89c48d719.png', depthX: 0, scale: 1 },
|
|
{ key: 'l11', src: '/home-banner/34c3621fc6fed4e16eab30d0448b35554711bfeb.png', depthX: 0, scale: 1 },
|
|
{ key: 'l12', src: '/home-banner/0546ed600f8dcfcf4c2eb3212ed1e12178abdb86.png', depthX: 0, scale: 1 },
|
|
{ key: 'l13', src: '/home-banner/d81527e08e5031badd870819fa801f6fd41fc021.png', depthX: 0, scale: 1 },
|
|
{ key: 'l14', src: '/home-banner/fa622f4b21c7fb66f84ebf87d36faaaa993363f0.png', depthX: 8, baseX: 9.29, scale: 1 },
|
|
{ key: 'l15', src: '/home-banner/0cfe34374720653a8f67d68af761d239eca15da7.png', depthX: 0, scale: 1 },
|
|
{ key: 'l16', src: '/home-banner/9afdb972babc0fed529e241176bad847a8094774.png', depthX: 0, scale: 1 },
|
|
{ key: 'l17', src: '/home-banner/0b9b00f62fd5760dc64fa16c564591957c3ace05.png', depthX: 0, scale: 1 },
|
|
{ key: 'l18', src: '/home-banner/9316d140598d5dc7e86d72e4a99ed903953d2e76.png', depthX: 0, scale: 1 },
|
|
{ key: 'l19', src: '/home-banner/82a15b3fda5ce240bac44b8d0b793b13c3bafc4e.png', depthX: 0, scale: 1 },
|
|
{ key: 'l20', src: '/home-banner/83e19c7274daec8f0e7d97cd6ad888e3e43b8083.png', depthX: 0, scale: 1 },
|
|
{ key: 'l21', src: '/home-banner/4e2110157727360d88357840903ed05b6b94c453.png', depthX: 0, scale: 1 },
|
|
{ key: 'l22', src: '/home-banner/3505e4538c81fce91fa5e6dacd4703d8277df021.png', depthX: 10, baseX: 23.23, scale: 1 },
|
|
{ key: 'l23', src: '/home-banner/8f51a0828e322ca0576f449ceb45cca453735506.png', depthX: 0, scale: 1 },
|
|
{ key: 'l24', src: '/home-banner/3d4a9667931a9a20011a384b7d699c3b286b82ab.png', depthX: 0, scale: 1.12 },
|
|
{ key: 'l25', src: '/home-banner/3c711c23ae57db96468a23e2beca49ce66f12c73.png', depthX: 0, scale: 1 },
|
|
{ key: 'l26', src: '/home-banner/e3607ba671317e2b1e4990a9225cbf9981a7a414.png', depthX: 0, scale: 1 },
|
|
{ key: 'l27', src: '/home-banner/2a4e5f40421d467e841b24eed228910bf965d4a1.png', depthX: 48, baseX: 167.23, scale: 1 },
|
|
{ key: 'l28', src: '/home-banner/446466499bd9615bc950ec4d8ec63647defc05b6.png', depthX: 5, baseY: 10.12, scale: 1.02 },
|
|
{ key: 'l29', src: '/home-banner/f8720df769d23c497096f57b73528265fd6b554a.png', depthX: 7, baseX: -14.86, baseY: 9.91, scale: 1.02 },
|
|
{ key: 'l30', src: '/home-banner/6970b12a96d85dd28a0aeb803eb3428dfec771bb.png', depthX: 8, baseX: -14.86, baseY: 9.91, scale: 1.02 },
|
|
{ key: 'l31', src: '/home-banner/83615a69b1ecab29163a54912260fd624a886906.png', depthX: 12, baseX: 25.81, baseY: 10.32, scale: 1.02 },
|
|
{ key: 'l32', src: '/home-banner/f14b431627327dda93f34b13392a3a773be5e788.png', depthX: 0, baseY: -11.35, scale: 1.14 }
|
|
]
|
|
|
|
const syncSearchKeyword = () => {
|
|
searchKeyword.value = typeof route.query.keyword === 'string' ? route.query.keyword : ''
|
|
}
|
|
|
|
const stickyEntries = computed(() => [
|
|
{ label: '首页', active: route.path === '/', action: () => router.push('/') },
|
|
{ label: '分类', active: route.path.startsWith('/category'), action: () => router.push('/category') },
|
|
{ label: '搜索', active: route.path.startsWith('/search'), action: () => router.push('/search') },
|
|
{
|
|
label: '创作中心',
|
|
active: route.path.startsWith('/my/videos') || route.path.startsWith('/upload'),
|
|
action: () => router.push(userStore.isLoggedIn ? '/my/videos' : '/login')
|
|
}
|
|
])
|
|
|
|
const homeNavEntries = computed(() => [
|
|
{ label: '首页', icon: 'tv-o', action: () => router.push('/') },
|
|
{ label: '分类', icon: 'apps-o', action: () => router.push('/category') }
|
|
])
|
|
|
|
const getLayerStyle = (layer: BannerLayer) => {
|
|
const translateX = (layer.baseX || 0) + bannerMotionX.value * layer.depthX
|
|
const translateY = layer.baseY || 0
|
|
return {
|
|
transform: `translate3d(${translateX}px, ${translateY}px, 0) scale(${layer.scale || 1})`,
|
|
filter: `brightness(${layer.brightness || 1})`,
|
|
opacity: layer.opacity || 1
|
|
}
|
|
}
|
|
|
|
const updateHomeScroll = () => {
|
|
if (!isHome.value) {
|
|
homeScrollY.value = 0
|
|
homeHeaderCompact.value = false
|
|
return
|
|
}
|
|
homeScrollY.value = window.scrollY || 0
|
|
|
|
// Use hysteresis to avoid flashing when scroll position hovers near the threshold.
|
|
if (!homeHeaderCompact.value && homeScrollY.value > 88) {
|
|
homeHeaderCompact.value = true
|
|
} else if (homeHeaderCompact.value && homeScrollY.value < 28) {
|
|
homeHeaderCompact.value = false
|
|
}
|
|
}
|
|
|
|
const handleBannerMouseMove = (event: MouseEvent) => {
|
|
const currentTarget = event.currentTarget as HTMLElement | null
|
|
if (!currentTarget) return
|
|
const rect = currentTarget.getBoundingClientRect()
|
|
const x = (event.clientX - rect.left) / rect.width
|
|
bannerMotionX.value = (x - 0.5) * -1
|
|
}
|
|
|
|
const resetBannerMotion = () => {
|
|
bannerMotionX.value = 0
|
|
}
|
|
|
|
const handleSearch = () => {
|
|
const keyword = searchKeyword.value.trim()
|
|
if (!keyword) return
|
|
router.push({ path: '/search', query: { keyword } })
|
|
}
|
|
|
|
const goToMessages = () => {
|
|
showMessagePopover.value = false
|
|
router.push('/messages')
|
|
}
|
|
|
|
const goToNotifications = () => {
|
|
showMessagePopover.value = false
|
|
router.push('/notifications')
|
|
}
|
|
|
|
const goToProfileOrLogin = () => {
|
|
const id = userStore.userId || userStore.userInfo?.id || userStore.authUser?.id
|
|
if (!id) {
|
|
router.push('/login')
|
|
return
|
|
}
|
|
router.push(`/user/${id}`)
|
|
}
|
|
|
|
onMounted(async () => {
|
|
syncSearchKeyword()
|
|
updateHomeScroll()
|
|
window.addEventListener('scroll', updateHomeScroll, { passive: true })
|
|
|
|
if (!userStore.isLoggedIn) return
|
|
|
|
try {
|
|
if (!userStore.authUser) {
|
|
await userStore.fetchAuthUser()
|
|
}
|
|
if (!userStore.userInfo) {
|
|
await userStore.fetchUserInfo()
|
|
}
|
|
} catch {
|
|
// keep usable
|
|
}
|
|
|
|
messageStore.fetchAllUnreadCount()
|
|
})
|
|
|
|
onBeforeUnmount(() => {
|
|
window.removeEventListener('scroll', updateHomeScroll)
|
|
})
|
|
|
|
watch(() => route.fullPath, () => {
|
|
syncSearchKeyword()
|
|
updateHomeScroll()
|
|
})
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.app-header {
|
|
position: fixed;
|
|
inset: 0 0 auto;
|
|
z-index: 120;
|
|
}
|
|
|
|
.home-header {
|
|
position: relative;
|
|
height: var(--home-banner-height);
|
|
|
|
&.compact {
|
|
.home-banner {
|
|
opacity: 0;
|
|
transform: translateY(-16px);
|
|
pointer-events: none;
|
|
}
|
|
|
|
.home-sticky-bar {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
pointer-events: auto;
|
|
}
|
|
}
|
|
}
|
|
|
|
.home-banner {
|
|
position: absolute;
|
|
inset: 0;
|
|
overflow: hidden;
|
|
background: linear-gradient(180deg, #6e6788 0%, #8d7296 100%);
|
|
transition: opacity 0.18s ease, transform 0.18s ease;
|
|
}
|
|
|
|
.banner-backdrop,
|
|
.animated-banner {
|
|
position: absolute;
|
|
inset: 0;
|
|
}
|
|
|
|
.banner-backdrop {
|
|
background:
|
|
linear-gradient(180deg, rgba(19, 17, 32, 0.3), rgba(19, 17, 32, 0.08) 36%, rgba(19, 17, 32, 0.24)),
|
|
radial-gradient(circle at center, rgba(255, 255, 255, 0.04), transparent 46%);
|
|
}
|
|
|
|
.animated-banner {
|
|
pointer-events: none;
|
|
}
|
|
|
|
.layer {
|
|
position: absolute;
|
|
inset: 0;
|
|
transition: transform 0.12s ease-out;
|
|
will-change: transform;
|
|
|
|
img {
|
|
position: absolute;
|
|
inset: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
max-width: none;
|
|
object-fit: cover;
|
|
user-select: none;
|
|
pointer-events: none;
|
|
}
|
|
}
|
|
|
|
.banner-shell {
|
|
position: relative;
|
|
z-index: 2;
|
|
height: 100%;
|
|
}
|
|
|
|
.banner-nav-row {
|
|
padding-top: 10px;
|
|
display: grid;
|
|
grid-template-columns: minmax(0, 1fr) 560px minmax(520px, auto);
|
|
align-items: center;
|
|
gap: 18px;
|
|
}
|
|
|
|
.banner-nav,
|
|
.banner-actions {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
flex-wrap: nowrap;
|
|
}
|
|
|
|
.banner-link,
|
|
.banner-action {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
padding: 8px 10px;
|
|
border-radius: 8px;
|
|
color: #fff;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
background: transparent;
|
|
white-space: nowrap;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.banner-search-wrap,
|
|
.sticky-search-wrap {
|
|
width: 100%;
|
|
}
|
|
|
|
.banner-search-wrap {
|
|
justify-self: center;
|
|
}
|
|
|
|
.search-bar {
|
|
width: 100%;
|
|
height: 48px;
|
|
display: grid;
|
|
grid-template-columns: 1fr 52px;
|
|
border-radius: 8px;
|
|
overflow: hidden;
|
|
background: rgba(255, 255, 255, 0.92);
|
|
box-shadow: 0 8px 18px rgba(15, 23, 42, 0.12);
|
|
}
|
|
|
|
.compact-search {
|
|
height: 40px;
|
|
background: #f1f2f3;
|
|
box-shadow: inset 0 0 0 1px #e3e5e7;
|
|
}
|
|
|
|
.search-content {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 0 16px;
|
|
}
|
|
|
|
.search-icon {
|
|
color: #9499a0;
|
|
}
|
|
|
|
.search-input {
|
|
width: 100%;
|
|
border: none;
|
|
background: transparent;
|
|
outline: none;
|
|
color: #18191c;
|
|
font-size: 16px;
|
|
}
|
|
|
|
.search-input::placeholder {
|
|
color: #c0c4cc;
|
|
}
|
|
|
|
.search-btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: #18191c;
|
|
}
|
|
|
|
.banner-actions {
|
|
justify-self: end;
|
|
justify-content: flex-end;
|
|
min-width: 520px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.avatar-entry {
|
|
width: 52px;
|
|
height: 52px;
|
|
border-radius: 999px;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: rgba(255, 255, 255, 0.08);
|
|
}
|
|
|
|
.avatar-entry.compact {
|
|
width: 40px;
|
|
height: 40px;
|
|
background: transparent;
|
|
}
|
|
|
|
.upload-entry {
|
|
height: 40px;
|
|
padding: 0 18px;
|
|
border-radius: 10px;
|
|
background: var(--pink-color);
|
|
color: #fff;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
font-size: 14px;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.upload-entry.compact {
|
|
height: 34px;
|
|
padding-inline: 14px;
|
|
border-radius: 8px;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.home-sticky-bar {
|
|
position: absolute;
|
|
inset: 0 0 auto;
|
|
height: 64px;
|
|
opacity: 0;
|
|
transform: translateY(-8px);
|
|
pointer-events: none;
|
|
background: rgba(255, 255, 255, 0.96);
|
|
border-bottom: 1px solid #e3e5e7;
|
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
|
|
transition: opacity 0.18s ease, transform 0.18s ease;
|
|
}
|
|
|
|
.sticky-shell {
|
|
min-height: 64px;
|
|
display: grid;
|
|
grid-template-columns: auto auto minmax(320px, 560px) auto;
|
|
align-items: center;
|
|
gap: 16px;
|
|
}
|
|
|
|
.sticky-nav,
|
|
.sticky-actions {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
}
|
|
|
|
.sticky-link,
|
|
.icon-action {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: #18191c;
|
|
font-size: 13px;
|
|
padding: 6px 8px;
|
|
border-radius: 8px;
|
|
|
|
&.active,
|
|
&:hover {
|
|
background: #f6f7f8;
|
|
}
|
|
}
|
|
|
|
.icon-action {
|
|
width: 32px;
|
|
height: 32px;
|
|
padding: 0;
|
|
}
|
|
|
|
.sticky-brand {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.brand-mark {
|
|
width: 62px;
|
|
height: 36px;
|
|
border-radius: 18px;
|
|
position: relative;
|
|
background: linear-gradient(135deg, #23ade5, #fb7299);
|
|
}
|
|
|
|
.brand-mark::before,
|
|
.brand-mark::after {
|
|
content: '';
|
|
position: absolute;
|
|
top: -8px;
|
|
width: 3px;
|
|
height: 14px;
|
|
background: #2c2c2c;
|
|
border-radius: 999px;
|
|
}
|
|
|
|
.brand-mark::before {
|
|
left: 16px;
|
|
}
|
|
|
|
.brand-mark::after {
|
|
right: 16px;
|
|
}
|
|
|
|
.brand-eye {
|
|
position: absolute;
|
|
top: 14px;
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 999px;
|
|
background: #fff;
|
|
}
|
|
|
|
.brand-eye.left {
|
|
left: 18px;
|
|
}
|
|
|
|
.brand-eye.right {
|
|
right: 18px;
|
|
}
|
|
|
|
.brand-copy {
|
|
display: grid;
|
|
gap: 2px;
|
|
}
|
|
|
|
.brand-copy strong {
|
|
font-size: 22px;
|
|
color: #18191c;
|
|
}
|
|
|
|
.brand-copy span {
|
|
font-size: 11px;
|
|
color: #9499a0;
|
|
}
|
|
|
|
.topbar {
|
|
height: var(--header-top-height);
|
|
color: #fff;
|
|
background:
|
|
linear-gradient(180deg, rgba(6, 16, 31, 0.7), rgba(6, 16, 31, 0.34)),
|
|
radial-gradient(circle at top center, rgba(255, 255, 255, 0.18), transparent 42%);
|
|
backdrop-filter: blur(12px);
|
|
}
|
|
|
|
.topbar-shell,
|
|
.mainbar-shell {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.topbar-shell {
|
|
min-height: var(--header-top-height);
|
|
gap: 16px;
|
|
}
|
|
|
|
.top-nav,
|
|
.top-actions {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
}
|
|
|
|
.top-link,
|
|
.top-icon {
|
|
color: rgba(255, 255, 255, 0.92);
|
|
font-size: 13px;
|
|
line-height: 1;
|
|
padding: 10px 12px;
|
|
border-radius: 999px;
|
|
transition: background 0.2s ease, color 0.2s ease;
|
|
}
|
|
|
|
.top-link:hover,
|
|
.top-link.active,
|
|
.top-icon:hover {
|
|
background: rgba(255, 255, 255, 0.14);
|
|
color: #fff;
|
|
}
|
|
|
|
.top-icon {
|
|
width: 38px;
|
|
height: 38px;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 0;
|
|
}
|
|
|
|
.mainbar {
|
|
height: var(--header-main-height);
|
|
background: #fff;
|
|
border-bottom: 1px solid var(--border-color);
|
|
}
|
|
|
|
.mainbar-shell {
|
|
min-height: var(--header-main-height);
|
|
gap: 20px;
|
|
}
|
|
|
|
.user-area {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: flex-end;
|
|
min-width: 220px;
|
|
}
|
|
|
|
.login-btn {
|
|
height: 38px;
|
|
padding: 0 20px;
|
|
border-radius: 8px;
|
|
color: #fff;
|
|
background: var(--pink-color);
|
|
}
|
|
|
|
.user-chip {
|
|
max-width: 260px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: 6px 10px 6px 6px;
|
|
border-radius: 999px;
|
|
background: #f6f7f8;
|
|
}
|
|
|
|
.user-copy {
|
|
min-width: 0;
|
|
text-align: left;
|
|
display: grid;
|
|
}
|
|
|
|
.user-copy strong,
|
|
.user-copy span {
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.user-copy strong {
|
|
font-size: 14px;
|
|
color: #18191c;
|
|
}
|
|
|
|
.user-copy span {
|
|
font-size: 12px;
|
|
color: #9499a0;
|
|
}
|
|
|
|
.popover-menu {
|
|
min-width: 180px;
|
|
padding: 8px;
|
|
}
|
|
|
|
.popover-item {
|
|
width: 100%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 10px 12px;
|
|
border-radius: 8px;
|
|
text-align: left;
|
|
}
|
|
|
|
.popover-item:hover {
|
|
background: #f6f7f8;
|
|
}
|
|
|
|
@media (max-width: 1280px) {
|
|
.banner-nav-row,
|
|
.sticky-shell {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.banner-actions,
|
|
.sticky-actions {
|
|
justify-self: stretch;
|
|
flex-wrap: wrap;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 900px) {
|
|
.top-nav,
|
|
.brand-copy span,
|
|
.user-copy span,
|
|
.sticky-nav {
|
|
display: none;
|
|
}
|
|
|
|
.mainbar-shell {
|
|
gap: 12px;
|
|
}
|
|
|
|
.user-area {
|
|
min-width: auto;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.home-header {
|
|
height: 132px;
|
|
}
|
|
|
|
.home-header.compact {
|
|
height: 56px;
|
|
}
|
|
|
|
.banner-nav,
|
|
.banner-actions {
|
|
gap: 6px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.banner-link span,
|
|
.banner-action span {
|
|
display: none;
|
|
}
|
|
|
|
.topbar {
|
|
display: none;
|
|
}
|
|
|
|
.brand-copy strong {
|
|
font-size: 20px;
|
|
}
|
|
|
|
.brand-mark {
|
|
width: 50px;
|
|
height: 30px;
|
|
}
|
|
|
|
.user-chip {
|
|
padding-right: 6px;
|
|
}
|
|
|
|
.user-copy {
|
|
display: none;
|
|
}
|
|
}
|
|
</style>
|