style: 优化组件样式和玻璃拟态效果

This commit is contained in:
2026-03-22 21:10:01 +08:00
parent 9586c67a46
commit 112a59360e
7 changed files with 822 additions and 226 deletions

View File

@ -1,8 +1,8 @@
<template>
<div class="pr-8 !bg-transparent">
<div class="pl-6 pr-6 !bg-transparent">
<n-card
embedded
class="mt-4 overflow-hidden rounded-[24px] border border-primary bg-[radial-gradient(circle_at_top_left,rgba(255,199,132,0.12),transparent_34%),radial-gradient(circle_at_top_right,rgba(136,203,255,0.1),transparent_28%),linear-gradient(180deg,rgba(255,255,255,0.88),rgba(244,249,253,0.8))] backdrop-blur-[18px]"
class="side-glass-card side-glass-card--tint mt-4 overflow-hidden rounded-[24px]"
>
<template #header>
<div class="flex items-center font-semibold text-slate-700">
@ -17,14 +17,14 @@
<span>今年已过 {{ jq.dayOfYear }} </span>
{{ d }}
</div>
<img v-if="jq.type != 0" class="absolute right-4 top-0" width="120" src="@/assets/images/offwork.png" alt="offwork">
<img v-else class="absolute right-4 top-0" width="120" src="@/assets/images/onwork.png" alt="onwork">
<img v-if="jq.type != 0" class="absolute right-4 -top-15" width="120" src="@/assets/images/offwork.png" alt="offwork">
<img v-else class="absolute right-4 -top-15" width="120" src="@/assets/images/onwork.png" alt="onwork">
</template>
</n-card>
<n-card
embedded
class="mt-4 overflow-hidden rounded-[24px] border border-white/75 bg-[radial-gradient(circle_at_top_left,rgba(255,199,132,0.12),transparent_34%),radial-gradient(circle_at_top_right,rgba(136,203,255,0.1),transparent_28%),linear-gradient(180deg,rgba(255,255,255,0.88),rgba(244,249,253,0.8))] backdrop-blur-[18px]"
class="side-glass-card mt-4 overflow-hidden rounded-[24px]"
>
<template #header>
<div class="flex items-center font-semibold text-slate-700">
@ -63,7 +63,7 @@
<n-card
embedded
class="mt-4 overflow-hidden rounded-[24px] border border-white/75 bg-[radial-gradient(circle_at_top_left,rgba(255,199,132,0.12),transparent_34%),radial-gradient(circle_at_top_right,rgba(136,203,255,0.1),transparent_28%),linear-gradient(180deg,rgba(255,255,255,0.88),rgba(244,249,253,0.8))] backdrop-blur-[18px]"
class="side-glass-card mt-4 overflow-hidden rounded-[24px]"
>
<template #header>
<div class="flex items-center font-semibold text-slate-700">
@ -140,6 +140,51 @@ onUnmounted(() => {
src: url('@/assets/font/LCDML.woff2');
}
.side-glass-card {
position: relative;
border: 1px solid rgba(255, 255, 255, 0.58) !important;
background:
linear-gradient(135deg, rgba(255, 255, 255, 0.34), rgba(255, 255, 255, 0.16)),
linear-gradient(180deg, rgba(241, 248, 252, 0.2), rgba(224, 236, 245, 0.08)) !important;
box-shadow:
0 22px 40px rgba(86, 111, 137, 0.14),
inset 0 1px 0 rgba(255, 255, 255, 0.7),
inset 0 -1px 0 rgba(255, 255, 255, 0.16) !important;
backdrop-filter: blur(24px) saturate(165%);
-webkit-backdrop-filter: blur(24px) saturate(165%);
}
.side-glass-card::before,
.side-glass-card::after {
content: '';
position: absolute;
inset: 0;
pointer-events: none;
border-radius: inherit;
}
.side-glass-card::before {
background:
linear-gradient(135deg, rgba(255, 255, 255, 0.34), rgba(255, 255, 255, 0.08) 48%, rgba(151, 197, 224, 0.08)),
radial-gradient(circle at top left, rgba(255, 255, 255, 0.45), transparent 42%);
opacity: 0.95;
}
.side-glass-card::after {
inset: 1px;
border: 1px solid rgba(255, 255, 255, 0.24);
border-radius: inherit;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.14), transparent 36%, rgba(255, 255, 255, 0.08));
}
.side-glass-card--tint {
background:
radial-gradient(circle at top left, rgba(255, 199, 132, 0.12), transparent 34%),
radial-gradient(circle at top right, rgba(136, 203, 255, 0.1), transparent 28%),
linear-gradient(135deg, rgba(255, 255, 255, 0.34), rgba(255, 255, 255, 0.16)),
linear-gradient(180deg, rgba(241, 248, 252, 0.2), rgba(224, 236, 245, 0.08)) !important;
}
:deep(.n-card > .n-card__content),
:deep(.n-card > .n-card__footer) {
padding: 12px 24px !important;
@ -153,4 +198,12 @@ onUnmounted(() => {
background: transparent !important;
box-shadow:unset;
}
:deep(.side-glass-card > .n-card__content),
:deep(.side-glass-card > .n-card-header),
:deep(.side-glass-card > .n-card__footer) {
position: relative;
z-index: 1;
background: transparent !important;
}
</style>

View File

@ -4,18 +4,18 @@
class="relative hidden items-center justify-between bg-[radial-gradient(circle_at_12%_18%,rgba(215,177,123,0.18),transparent_24%),radial-gradient(circle_at_86%_12%,rgba(120,162,182,0.14),transparent_24%),linear-gradient(180deg,rgba(239,231,218,0.82)_0%,rgba(232,240,239,0.68)_46%,rgba(243,241,232,0.28)_100%)] px-4 py-1.5 shadow-[0_6px_16px_rgba(110,124,112,0.05)] backdrop-blur-[14px] after:pointer-events-none after:absolute after:inset-x-0 after:bottom-[-28px] after:h-[34px] after:bg-[linear-gradient(180deg,rgba(240,236,226,0.58),rgba(232,240,239,0.22),transparent)] after:content-[''] lg:flex"
>
<div
class="flex ml-4 cursor-pointer items-center rounded-full border border-white/65 bg-white/42 px-4 py-1 shadow-[0_8px_20px_rgba(112,137,160,0.07)] backdrop-blur-[14px] transition-colors duration-200 hover:bg-white/56"
class="menu-pill-glass flex ml-4 cursor-pointer items-center rounded-full px-4 py-1 transition-colors duration-200"
@click="goHome"
>
<img :src="logo" alt="柚子的网站" class="h-9 align-middle drop-shadow-[0_6px_14px_rgba(236,102,171,0.15)]" />
</div>
<div class="flex h-[42px] items-center rounded-full border border-white/65 bg-white/42 px-3 py-0.5 shadow-[0_8px_20px_rgba(112,137,160,0.07)] backdrop-blur-[14px]">
<div class="menu-pill-glass flex h-[42px] items-center rounded-full px-1 py-0.5">
<n-menu :icon-size="12" v-model:value="activeKey" mode="horizontal" :options="menuOptions" />
</div>
<div
class="flex cursor-pointer items-center rounded-full border border-white/65 bg-white/42 px-4 py-1.5 text-[#ec66ab] shadow-[0_8px_20px_rgba(112,137,160,0.07)] backdrop-blur-[14px] transition-colors duration-200 hover:bg-white/56"
class="menu-pill-glass flex cursor-pointer items-center rounded-full px-4 py-1.5 text-[#ec66ab] transition-colors duration-200"
@click="gotoHf"
>
<span class="flex items-center truncate text-[15px] font-medium text-slate-600">
@ -30,11 +30,22 @@
<span class="ml-4 text-[15px] font-semibold text-slate-700">{{ temp }}&deg;C</span>
</div>
<div class="mr-4 flex w-1/20 items-center justify-end">
<n-dropdown v-if="userinfo" class="w-[128px] cursor-pointer" :options="oprOp" @select="handleSelect" trigger="hover">
<div class="flex items-center rounded-full border border-white/65 bg-white/46 px-2 py-1 shadow-[0_8px_20px_rgba(112,137,160,0.07)] backdrop-blur-[14px]">
<div class="mr-4 flex items-center justify-end">
<n-dropdown
v-if="userinfo"
class="w-[128px] cursor-pointer"
trigger="hover"
placement="bottom-end"
content-class="w-[400px] overflow-hidden rounded-[26px] border border-white/80 p-2.5"
:theme-overrides="dropdownThemeOverrides"
:node-props="dropdownNodeProps"
:render-label="renderDropdownLabel"
:options="oprOp"
@select="handleSelect"
>
<div class="menu-pill-glass cursor-pointer flex items-center rounded-full px-2 py-1">
<n-avatar round :src="userinfo.ava_url" class="cursor-pointer ring-2 ring-white/80" alt="用户头像" />
<div class="ml-2 truncate text-sm font-medium text-slate-600">{{ userinfo.nickname }}</div>
<div class="ml-4 mr-2 truncate text-sm font-medium text-slate-600">{{ userinfo.nickname }}</div>
</div>
</n-dropdown>
@ -56,13 +67,13 @@
</div>
<div class="relative flex items-center justify-between bg-[radial-gradient(circle_at_12%_18%,rgba(215,177,123,0.18),transparent_24%),radial-gradient(circle_at_86%_12%,rgba(120,162,182,0.14),transparent_24%),linear-gradient(180deg,rgba(239,231,218,0.84)_0%,rgba(232,240,239,0.68)_46%,rgba(243,241,232,0.32)_100%)] px-3 py-2 shadow-[0_6px_16px_rgba(110,124,112,0.05)] backdrop-blur-[14px] after:pointer-events-none after:absolute after:inset-x-0 after:bottom-[-24px] after:h-[30px] after:bg-[linear-gradient(180deg,rgba(240,236,226,0.58),rgba(232,240,239,0.22),transparent)] after:content-[''] lg:hidden">
<div class="flex items-center rounded-full border border-white/65 bg-white/42 px-3 py-1 shadow-[0_8px_20px_rgba(112,137,160,0.07)] backdrop-blur-[14px]">
<div class="menu-pill-glass flex items-center rounded-full px-3 py-1">
<img :src="logo" alt="柚子的网站" class="h-9 align-middle drop-shadow-[0_6px_14px_rgba(236,102,171,0.15)]" />
</div>
<div class="flex items-center mr-2">
<div v-if="userinfo" class="flex items-center rounded-full border border-white/65 bg-white/46 px-2 py-1 shadow-[0_8px_20px_rgba(112,137,160,0.07)] backdrop-blur-[14px]">
<n-avatar :src="userinfo.ava_url" round class="cursor-pointer ring-2 ring-white/80" alt="用户头像" />
<div class="flex items-center mr-2 cursor-pointer">
<div v-if="userinfo" class="menu-pill-glass flex items-center rounded-full px-2 py-1">
<n-avatar :src="userinfo.ava_url" round class=" ring-2 ring-white/80" alt="用户头像" />
<div class="ml-2 text-sm font-medium text-slate-600">{{ userinfo.nickname }}</div>
</div>
@ -113,7 +124,7 @@ import artiSvg from '@/icon/menu/arti.svg';
import homeSvg from '@/icon/menu/home.svg';
import linkSvg from '@/icon/menu/link.svg';
import picSvg from '@/icon/menu/pic.svg';
import type { UploadFileInfo } from 'naive-ui';
import type { DropdownOption, UploadFileInfo } from 'naive-ui';
import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
import { RouterLink, useRoute, useRouter } from 'vue-router';
@ -184,7 +195,47 @@ const oprOp = [
key: 'logout',
label: '退出登录',
}
]
] satisfies DropdownOption[]
const dropdownThemeOverrides = {
padding: '0 0px',
borderRadius: '20px',
color: '#DCE1D8ee',
optionHeightMedium: '52px',
optionColorHover: 'transparent',
optionColorActive: 'transparent',
optionTextColor: '#526377',
optionTextColorHover: '#ec66ab',
optionTextColorActive: '#ec66ab',
peerOverrides: {
Popover: {
color: 'transparent',
boxShadow: 'none',
padding: '0'
}
}
}
function dropdownNodeProps() {
return {
class: 'group rounded-[20px] px-1 py-1'
}
}
function renderDropdownLabel(option: DropdownOption) {
const labelText =
('label' in option && typeof option.label === 'string' && option.label)
|| ('title' in option && typeof option.title === 'string' && option.title)
|| '未知'
return h(
'div',
{
class: 'flex w-full items-center justify-center rounded-[18px] border border-white/0 bg-[linear-gradient(180deg,rgba(255,255,255,0.18),rgba(255,255,255,0.06))] px-5 py-3.5 text-[14px] font-semibold text-slate-700 transition-all duration-300 group-hover:border-white/85 group-hover:bg-[linear-gradient(180deg,rgba(255,255,255,0.82),rgba(255,248,244,0.62))] group-hover:text-[#ec66ab] group-hover:shadow-[0_14px_28px_rgba(102,129,156,0.16)]'
},
labelText
)
}
const editModal = ref(false)
const token = ref('')
@ -394,6 +445,59 @@ onUnmounted(() => {
</script>
<style scoped lang="less">
.menu-pill-glass {
position: relative;
border: 1px solid rgba(255, 255, 255, 0.58);
background:
linear-gradient(135deg, rgba(255, 255, 255, 0.34), rgba(255, 255, 255, 0.16)),
linear-gradient(180deg, rgba(241, 248, 252, 0.22), rgba(224, 236, 245, 0.1));
box-shadow:
0 14px 30px rgba(98, 121, 146, 0.1),
inset 0 1px 0 rgba(255, 255, 255, 0.72),
inset 0 -1px 0 rgba(255, 255, 255, 0.14);
backdrop-filter: blur(22px) saturate(165%);
-webkit-backdrop-filter: blur(22px) saturate(165%);
overflow: hidden;
}
.menu-pill-glass::before,
.menu-pill-glass::after {
content: '';
position: absolute;
inset: 0;
pointer-events: none;
border-radius: inherit;
}
.menu-pill-glass::before {
background:
linear-gradient(135deg, rgba(255, 255, 255, 0.34), rgba(255, 255, 255, 0.08) 48%, rgba(151, 197, 224, 0.08)),
radial-gradient(circle at top left, rgba(255, 255, 255, 0.42), transparent 42%);
opacity: 0.95;
}
.menu-pill-glass::after {
inset: 1px;
border: 1px solid rgba(255, 255, 255, 0.24);
border-radius: inherit;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.12), transparent 38%, rgba(255, 255, 255, 0.06));
}
.menu-pill-glass:hover {
background:
linear-gradient(135deg, rgba(255, 255, 255, 0.42), rgba(255, 255, 255, 0.2)),
linear-gradient(180deg, rgba(241, 248, 252, 0.24), rgba(224, 236, 245, 0.12));
box-shadow:
0 18px 34px rgba(98, 121, 146, 0.13),
inset 0 1px 0 rgba(255, 255, 255, 0.78),
inset 0 -1px 0 rgba(255, 255, 255, 0.18);
}
.menu-pill-glass > * {
position: relative;
z-index: 1;
}
:deep(.n-menu-item-content__icon) {
width: 16px !important;
height: 16px !important;
@ -407,8 +511,14 @@ onUnmounted(() => {
:deep(.n-menu-item) {
display: flex;
align-items: center;
justify-content: center;
padding: 0 12px;
margin-right: 30px !important;
}
:deep(.n-menu-item:last-of-type) {
align-items: center;
margin-right: 0px !important;
}
:deep(.n-menu--horizontal .n-menu-item-content) {
border-radius: 9999px;

View File

@ -24,7 +24,7 @@
</div>
<div
class="mt-6 rounded-full border border-white/62 bg-[linear-gradient(135deg,rgba(255,249,241,0.76),rgba(236,245,243,0.72)),radial-gradient(circle_at_top_right,rgba(255,255,255,0.28),transparent_34%)] px-[1.1rem] py-4 shadow-[inset_0_1px_0_rgba(255,255,255,0.55)]">
class="mt-6 rounded-full border border-white/62 bg-[linear-gradient(135deg,rgba(255,249,241,0.76),rgba(236,245,243,0.72)),radial-gradient(circle_at_top_right,rgba(255,255,255,0.28),transparent_34%)] shadow-[inset_0_1px_0_rgba(255,255,255,0.55)]">
<n-input class="gallery-search" round size="large" v-model:value="kw" @keyup.enter="onSearch"
placeholder="请输入关键词,按回车开始搜索">
<template #suffix>
@ -277,4 +277,17 @@ onUnmounted(() => {
:deep(.n-image img) {
border-radius: 1rem !important;
}
:deep(.n-input) {
--n-border: none !important;
--n-border-hover: none !important;
--n-border-focus: none !important;
--n-box-shadow-focus: none !important;
background: transparent !important;
}
:deep(.n-input__input-el),
:deep(.n-base-selection-label) {
color: #314155 !important;
}
</style>

View File

@ -10,55 +10,71 @@
<n-layout-content class="relative !bg-transparent">
<div ref="searchRef" class="relative z-[1] hidden px-12 pt-8 lg:block">
<!-- <div class="panel-caption">快捷搜索</div> -->
<n-input-group class="overflow-hidden rounded-[22px] border border-white/75 bg-white/72 shadow-[0_18px_45px_rgba(111,144,175,0.16)] backdrop-blur-[16px]">
<n-select
class="w-24 search-select rounded-full"
size="large"
v-model:value="broswer"
:options="options"
@click="cancelSbox"
/>
<n-input
class="flex-1"
size="large"
autofocus
v-model:value="searchWord"
@blur="cancelSbox"
@keyup.enter="search"
placeholder="请输入搜索内容"
>
<template #suffix>
<n-icon class=" cursor-pointer" size="large">
<icon-search />
</n-icon>
</template>
</n-input>
</n-input-group>
<div
v-if="searchBox"
class="absolute left-34 mt-3 z-10 max-w-88 rounded-2xl border border-white/80 bg-[linear-gradient(180deg,rgba(255,255,255,0.88),rgba(244,249,253,0.84))] px-3 py-3 text-sm text-slate-500 shadow-[0_20px_48px_rgba(102,129,156,0.18),inset_0_1px_0_rgba(255,255,255,0.9)] backdrop-blur-[20px] before:pointer-events-none before:absolute before:inset-0 before:rounded-2xl before:bg-[radial-gradient(circle_at_top_left,rgba(255,199,132,0.12),transparent_34%),radial-gradient(circle_at_top_right,rgba(136,203,255,0.1),transparent_28%)] before:content-['']"
>
<div
v-for="(item, idx) in searchItems"
:key="idx"
class="relative z-[1] flex items-center truncate rounded-xl border border-transparent p-3 pr-20 text-slate-500 transition-colors duration-200"
:class="selecedIdx === idx
? 'bg-[linear-gradient(135deg,#ec66ab,#f48a6f)] text-white shadow-[0_10px_22px_rgba(236,102,171,0.22)]'
: 'hover:border-slate-200/90 hover:bg-white/72 hover:text-slate-600'"
:style="{ marginTop: idx === 0 ? '0' : '0.35rem' }"
@click="goExtra(item.menu_link)"
@keyup.enter="goExtra(item.menu_link)"
>
<span v-if="idx">导航</span>
{{ item.menu_name }}
</div>
<div ref="searchShellRef" class="search-shell relative">
<n-input-group class="glass-surface overflow-hidden rounded-[22px]">
<n-select
class="w-24 search-select rounded-full"
size="large"
v-model:value="broswer"
:options="options"
:menu-props="{ class: 'search-select-menu' }"
@click="cancelSbox"
/>
<n-input
ref="searchInputRef"
class="flex-1"
size="large"
autofocus
v-model:value="searchWord"
@blur="cancelSbox"
@keyup.enter="search"
placeholder="请输入搜索内容"
>
<template #suffix>
<n-icon class=" cursor-pointer" size="large">
<icon-search />
</n-icon>
</template>
</n-input>
</n-input-group>
</div>
</div>
<Teleport to="body">
<div v-if="searchBox" class="search-suggest-portal pointer-events-none fixed inset-0 z-[1200]">
<div
class="search-suggest-panel glass-surface glass-floating-surface pointer-events-auto absolute rounded-[26px] px-3 py-3 text-sm text-slate-500"
:style="searchPanelStyle"
>
<div
v-for="(item, idx) in searchItems"
:key="idx"
class="search-suggest-item relative z-[1] flex items-center truncate rounded-[18px] border border-transparent p-3.5 pr-6 text-slate-500 transition-[transform,background-color,color,box-shadow,border-color] duration-200"
:class="selecedIdx === idx
? 'bg-[linear-gradient(135deg,#ec66ab,#f48a6f)] text-white shadow-[0_14px_26px_rgba(236,102,171,0.24)]'
: 'hover:-translate-y-0.5 hover:border-white/55 hover:bg-white/62 hover:text-slate-700 hover:shadow-[0_12px_24px_rgba(102,129,156,0.12)]'"
:style="{ marginTop: idx === 0 ? '0' : '0.45rem' }"
@click="goExtra(item.menu_link)"
@keyup.enter="goExtra(item.menu_link)"
>
<span
class="mr-2 inline-flex h-6 shrink-0 items-center rounded-full px-2.5 text-[11px] font-semibold tracking-[0.04em]"
:class="selecedIdx === idx
? 'bg-white/18 text-white'
: idx ? 'bg-slate-700/8 text-slate-500' : 'bg-[rgba(236,102,171,0.12)] text-[rgb(212,88,154)]'"
>
{{ idx ? '导航' : '搜索' }}
</span>
<span class="truncate">{{ item.menu_name }}</span>
</div>
</div>
</div>
</Teleport>
<n-scrollbar class="w-full overflow-x-auto" :style="navStyle" trigger="none">
<div
v-if="navlist.length"
class="mt-6 mb-4 flex flex-wrap items-center gap-2 rounded-[20px] border border-white/70 bg-white/60 px-3 py-3 shadow-[0_12px_35px_rgba(118,144,169,0.12)] backdrop-blur-[14px] lg:mx-12 lg:px-4"
class="glass-surface mt-6 mb-4 flex flex-wrap items-center gap-2 rounded-[20px] px-3 py-3 lg:mx-12 lg:px-4"
>
<d-tag
v-for="tag in tagList"
@ -89,7 +105,7 @@
v-for="(item, index) in navlist"
:key="item.nid ?? index"
embedded
class="h-28 box-border overflow-hidden rounded-[24px] border border-white/75 bg-[linear-gradient(180deg,rgba(255,255,255,0.9),rgba(244,250,255,0.72))] shadow-[0_16px_32px_rgba(118,144,169,0.12)] backdrop-blur-[14px] transition-[transform,box-shadow,border-color] duration-[320ms] [transition-timing-function:cubic-bezier(0.22,1,0.36,1)] hover:-translate-y-1 hover:border-white/90 hover:shadow-[0_22px_38px_rgba(103,128,154,0.16)]"
class="glass-nav-card h-28 box-border overflow-hidden rounded-[24px] transition-[transform,box-shadow,border-color] duration-[320ms] [transition-timing-function:cubic-bezier(0.22,1,0.36,1)] hover:-translate-y-1"
@click="goExtra(item.menu_link)"
@contextmenu.prevent="handdleContextMenu($event, item)"
>
@ -121,7 +137,7 @@
<n-card
embedded
class="h-28 box-border overflow-hidden rounded-[24px] border border-white/75 bg-[linear-gradient(180deg,rgba(255,253,248,0.92),rgba(255,247,235,0.75))] shadow-[0_16px_32px_rgba(118,144,169,0.12)] backdrop-blur-[14px] transition-[transform,box-shadow,border-color] duration-[320ms] [transition-timing-function:cubic-bezier(0.22,1,0.36,1)] hover:-translate-y-1 hover:border-white/90 hover:shadow-[0_22px_38px_rgba(103,128,154,0.16)]"
class="glass-nav-card glass-nav-card--warm h-28 box-border overflow-hidden rounded-[24px] transition-[transform,box-shadow,border-color] duration-[320ms] [transition-timing-function:cubic-bezier(0.22,1,0.36,1)] hover:-translate-y-1"
>
<div @click="addNav" class="w-full h-full flex flex-col items-center justify-center cursor-pointer">
<div
@ -264,6 +280,7 @@ const menuS = '!m-0 py-3 px-6 text-sm text-gray-700 w-full flex items-center jus
const currentClickedItem = ref(0)
const editModal = ref(false)
const editInput = ref('')
const searchInputRef = ref<HTMLInputElement | null>(null)
const visible = ref(false)
const navData = reactive<NavItem>({
@ -275,12 +292,14 @@ const navData = reactive<NavItem>({
const formNav = ref<any>(null)
const navcards = ref<any>(null)
const searchRef = useTemplateRef('searchRef')
const searchShellRef = useTemplateRef('searchShellRef')
const nav: any = $store.nav.useNavStore()
const usrLog = $store.log.useLogStore()
const contentStyle = ref<Record<string, string>>({})
const navStyle = ref<Record<string, string>>({})
const searchPanelStyle = ref<Record<string, string>>({})
const searchWord = ref('')
const broswer = ref('bing')
const searchItems = ref<SearchItem[]>([])
@ -384,6 +403,8 @@ function handleInput() {
tag: item.tag,
}))
]
updateSearchPanelPosition()
}
function handdleKeyup(e: KeyboardEvent) {
@ -559,6 +580,27 @@ function goExtra(link: string) {
window.open(link, '_blank')
}
function updateSearchPanelPosition() {
window.requestAnimationFrame(() => {
const rect = searchShellRef.value?.getBoundingClientRect()
if (!rect) return
const viewportPadding = 24
const offsetLeft = 112
const left = Math.max(viewportPadding, rect.left + offsetLeft)
const availableWidth = Math.max(280, window.innerWidth - left - viewportPadding)
const width = Math.min(672, availableWidth, Math.max(320, rect.width - offsetLeft))
const availableHeight = Math.max(180, window.innerHeight - rect.bottom - 32)
searchPanelStyle.value = {
left: `${Math.round(left)}px`,
top: `${Math.round(rect.bottom + 14)}px`,
width: `${Math.round(width)}px`,
maxHeight: `${Math.round(Math.min(availableHeight, 420))}px`
}
})
}
function search() {
const keyword = searchWord.value.trim()
if (!keyword) return
@ -642,6 +684,10 @@ const handleResize = () => {
navStyle.value = {
height: `${window.innerHeight - (searchHeight + searchH)}px`
}
if (searchBox.value) {
updateSearchPanelPosition()
}
}
const handleWindowKeyup = (event: Event) => {
@ -656,16 +702,27 @@ onMounted(() => {
navStyle.value = {
height: `${window.innerHeight - navcards.value.getBoundingClientRect().y}px`
}
searchInputRef.value?.focus()
getNavList()
window.addEventListener('resize', handleResize)
window.addEventListener('scroll', handleResize, true)
window.addEventListener('keyup', handleWindowKeyup)
})
onBeforeUnmount(() => {
window.removeEventListener('resize', handleResize)
window.removeEventListener('scroll', handleResize, true)
window.removeEventListener('keyup', handleWindowKeyup)
removeTimer()
})
watch(searchBox, (visible) => {
if (visible) {
updateSearchPanelPosition()
}
})
</script>
<style scoped lang="less">
@ -685,7 +742,6 @@ onBeforeUnmount(() => {
:deep(.n-layout-sider) {
background: transparent !important;
backdrop-filter: blur(18px);
border-left: none !important;
box-shadow: none !important;
}
@ -702,6 +758,7 @@ onBeforeUnmount(() => {
padding-left: 0.2rem;
padding-right: 0.2rem;
min-height: 40px !important;
position: relative;
}
:deep(.search-select .n-base-selection__border),
@ -718,7 +775,22 @@ onBeforeUnmount(() => {
justify-content: center;
min-height: 2.5rem !important;
border-radius: 9999px !important;
background: transparent !important;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.28), rgba(255, 255, 255, 0.08)) !important;
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.36),
inset -1px 0 0 rgba(255, 255, 255, 0.1);
padding-right: 0.25rem !important;
}
:deep(.search-select .n-base-selection-input) {
font-weight: 700;
color: #42556b !important;
letter-spacing: 0.04em;
}
:deep(.search-select .n-base-selection__suffix) {
color: #6d839a !important;
margin-right: 0.1rem;
}
:deep(.n-input) {
@ -734,6 +806,220 @@ onBeforeUnmount(() => {
color: #314155 !important;
}
.glass-surface,
.glass-nav-card {
position: relative;
border: 1px solid rgba(255, 255, 255, 0.58) !important;
background:
linear-gradient(135deg, rgba(255, 255, 255, 0.34), rgba(255, 255, 255, 0.16)),
linear-gradient(180deg, rgba(241, 248, 252, 0.2), rgba(224, 236, 245, 0.08)) !important;
box-shadow:
0 22px 40px rgba(86, 111, 137, 0.14),
inset 0 1px 0 rgba(255, 255, 255, 0.7),
inset 0 -1px 0 rgba(255, 255, 255, 0.16) !important;
backdrop-filter: blur(24px) saturate(165%);
-webkit-backdrop-filter: blur(24px) saturate(165%);
}
.glass-surface {
box-shadow:
0 18px 38px rgba(86, 111, 137, 0.12),
inset 0 1px 0 rgba(255, 255, 255, 0.68),
inset 0 -1px 0 rgba(255, 255, 255, 0.14) !important;
}
.glass-floating-surface {
box-shadow:
0 24px 52px rgba(86, 111, 137, 0.18),
inset 0 1px 0 rgba(255, 255, 255, 0.74),
inset 0 -1px 0 rgba(255, 255, 255, 0.16) !important;
}
.search-shell {
isolation: isolate;
}
.search-suggest-portal {
isolation: isolate;
}
.search-suggest-panel {
overflow: auto;
transform-origin: top left;
animation: search-panel-enter 180ms cubic-bezier(0.22, 1, 0.36, 1);
}
.search-suggest-item {
min-height: 3.25rem;
}
@keyframes search-panel-enter {
from {
opacity: 0;
transform: translateY(-6px) scale(0.98);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
.glass-nav-card::before,
.glass-nav-card::after,
.glass-surface::before,
.glass-surface::after {
content: '';
position: absolute;
inset: 0;
pointer-events: none;
border-radius: inherit;
}
.glass-nav-card::before,
.glass-surface::before {
background:
linear-gradient(135deg, rgba(255, 255, 255, 0.34), rgba(255, 255, 255, 0.08) 48%, rgba(151, 197, 224, 0.08)),
radial-gradient(circle at top left, rgba(255, 255, 255, 0.45), transparent 42%);
opacity: 0.95;
}
.glass-nav-card::after,
.glass-surface::after {
inset: 1px;
border: 1px solid rgba(255, 255, 255, 0.24);
background: linear-gradient(180deg, rgba(255, 255, 255, 0.14), transparent 36%, rgba(255, 255, 255, 0.08));
}
.glass-nav-card:hover {
border-color: rgba(255, 255, 255, 0.74) !important;
box-shadow:
0 28px 48px rgba(86, 111, 137, 0.18),
inset 0 1px 0 rgba(255, 255, 255, 0.78),
inset 0 -1px 0 rgba(255, 255, 255, 0.2) !important;
}
.glass-nav-card--warm {
background:
linear-gradient(135deg, rgba(255, 249, 242, 0.4), rgba(255, 245, 231, 0.2)),
linear-gradient(180deg, rgba(255, 236, 211, 0.14), rgba(244, 225, 204, 0.06)) !important;
}
:deep(.glass-nav-card > .n-card__content) {
position: relative;
z-index: 1;
background: transparent !important;
}
.glass-surface > * {
position: relative;
z-index: 1;
}
:global(.search-select-menu) {
position: relative;
overflow: hidden;
padding: 6px;
border: 1px solid rgba(255, 255, 255, 0.58) !important;
border-radius: 22px !important;
background:
linear-gradient(135deg, rgba(255, 255, 255, 0.34), rgba(255, 255, 255, 0.16)),
linear-gradient(180deg, rgba(241, 248, 252, 0.2), rgba(224, 236, 245, 0.08)) !important;
box-shadow:
0 24px 52px rgba(86, 111, 137, 0.18),
inset 0 1px 0 rgba(255, 255, 255, 0.74),
inset 0 -1px 0 rgba(255, 255, 255, 0.16) !important;
backdrop-filter: blur(24px) saturate(165%);
-webkit-backdrop-filter: blur(24px) saturate(165%);
transform-origin: top left;
animation: search-panel-enter 180ms cubic-bezier(0.22, 1, 0.36, 1);
}
:global(.search-select-menu::before),
:global(.search-select-menu::after) {
content: '';
position: absolute;
inset: 0;
pointer-events: none;
border-radius: inherit;
}
:global(.search-select-menu::before) {
background:
linear-gradient(135deg, rgba(255, 255, 255, 0.34), rgba(255, 255, 255, 0.08) 48%, rgba(151, 197, 224, 0.08)),
radial-gradient(circle at top left, rgba(255, 255, 255, 0.45), transparent 42%);
opacity: 0.95;
}
:global(.search-select-menu::after) {
inset: 1px;
border: 1px solid rgba(255, 255, 255, 0.24);
background: linear-gradient(180deg, rgba(255, 255, 255, 0.14), transparent 36%, rgba(255, 255, 255, 0.08));
}
:global(.search-select-menu .n-base-select-menu-option-wrapper),
:global(.search-select-menu .n-base-select-menu__empty) {
position: relative;
z-index: 1;
}
:global(.search-select-menu .n-base-select-menu-option-wrapper) {
padding: 0 !important;
}
:global(.search-select-menu .n-base-select-option) {
min-height: 2.45rem;
margin: 0;
padding-left: 0.7rem !important;
padding-right: 0.7rem !important;
border-radius: 16px;
color: #475569;
font-weight: 700;
letter-spacing: 0;
transition:
transform 0.2s ease,
background-color 0.2s ease,
color 0.2s ease,
box-shadow 0.2s ease,
border-color 0.2s ease;
}
:global(.search-select-menu .n-base-select-option:not(.n-base-select-option--selected).n-base-select-option--pending) {
transform: none;
background: rgba(255, 255, 255, 0.62);
box-shadow:
0 8px 16px rgba(120, 144, 168, 0.1),
inset 0 1px 0 rgba(255, 255, 255, 0.45);
}
:global(.search-select-menu .n-base-select-option--selected) {
background: linear-gradient(135deg, rgba(255, 240, 246, 0.98), rgba(255, 233, 223, 0.96));
color: #364152;
box-shadow:
0 10px 18px rgba(236, 102, 171, 0.12),
inset 0 1px 0 rgba(255, 255, 255, 0.52);
}
:global(.search-select-menu .n-base-select-option--selected .n-base-select-option__content),
:global(.search-select-menu .n-base-select-option--selected .n-base-select-option__check) {
color: inherit;
}
:global(.search-select-menu .n-base-select-option__content) {
display: block;
position: relative;
z-index: 1;
}
:global(.search-select-menu .n-base-select-option__check) {
display: none !important;
}
:global(.search-select-menu .n-base-select-menu__empty) {
padding: 0.6rem 0.4rem;
color: #64748b;
}
:deep(.modal-input .n-input) {
background: #ffffff !important;
border: 1px solid rgba(198, 212, 226, 0.92) !important;
@ -819,3 +1105,4 @@ onBeforeUnmount(() => {
align-items: center;
}
</style>

View File

@ -1,20 +1,25 @@
<template>
<div
class="relative overflow-hidden bg-[radial-gradient(circle_at_12%_18%,rgba(215,177,123,0.24),transparent_24%),radial-gradient(circle_at_86%_12%,rgba(120,162,182,0.18),transparent_24%),radial-gradient(circle_at_50%_100%,rgba(176,198,172,0.14),transparent_28%),linear-gradient(180deg,#efe7da_0%,#e8f0ef_44%,#f3f1e8_100%)]"
:style="boxStyle"
>
<div class="pointer-events-none absolute left-[-5rem] top-6 h-80 w-80 rounded-full bg-[rgba(234,176,107,0.45)] opacity-48 blur-[88px]"></div>
<div class="pointer-events-none absolute right-[-5rem] top-32 h-[22rem] w-[22rem] rounded-full bg-[rgba(118,176,214,0.32)] opacity-48 blur-[88px]"></div>
<div class="pointer-events-none absolute inset-0 opacity-14 [background-image:linear-gradient(rgba(255,255,255,0.18)_1px,transparent_1px),linear-gradient(90deg,rgba(255,255,255,0.18)_1px,transparent_1px)] [background-size:36px_36px] [mask-image:linear-gradient(180deg,rgba(0,0,0,0.8),transparent_85%)]"></div>
:style="boxStyle">
<div
class="pointer-events-none absolute left-[-5rem] top-6 h-80 w-80 rounded-full bg-[rgba(234,176,107,0.45)] opacity-48 blur-[88px]">
</div>
<div
class="pointer-events-none absolute right-[-5rem] top-32 h-[22rem] w-[22rem] rounded-full bg-[rgba(118,176,214,0.32)] opacity-48 blur-[88px]">
</div>
<div
class="pointer-events-none absolute inset-0 opacity-14 [background-image:linear-gradient(rgba(255,255,255,0.18)_1px,transparent_1px),linear-gradient(90deg,rgba(255,255,255,0.18)_1px,transparent_1px)] [background-size:36px_36px] [mask-image:linear-gradient(180deg,rgba(0,0,0,0.8),transparent_85%)]">
</div>
<div class="relative z-[1] mx-auto flex h-full w-[86%] gap-6 py-6">
<aside class="hidden w-[21rem] shrink-0 lg:block">
<div class="sticky top-6">
<section class="relative rounded-[28px] border border-white/62 bg-[linear-gradient(145deg,rgba(255,251,246,0.68),rgba(237,246,244,0.56))] p-6 shadow-[0_20px_50px_rgba(110,124,112,0.11)] backdrop-blur-[20px] after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:bg-[linear-gradient(120deg,rgba(255,255,255,0.24),transparent_45%)] after:content-['']">
<section class="blog-detail-card p-6">
<div class="relative z-[1]">
<h1 class="mt-3 text-[2rem] text-center leading-[1.2] text-[#2c3a4a]">阅读目录</h1>
<div class="mt-6 rounded-[22px] border border-white/70 bg-white/58 p-4 shadow-[0_12px_28px_rgba(112,128,118,0.1)]">
<div class="blog-detail-card-soft mt-6 p-4">
<div class="mb-3 flex items-center justify-between">
<span class="text-sm font-semibold text-[#526376]">目录</span>
<span class="rounded-full cursor-pointer bg-primary px-3 py-1 text-xs text-white">正文导航</span>
@ -23,35 +28,30 @@
<div class="text-[13px] leading-5 text-[#526376]">
<template v-for="item in tocItems" :key="item.id">
<details v-if="item.children.length" class="group" :open="item.open">
<summary class="flex cursor-pointer list-none items-center gap-2 py-1 text-[#526376] marker:hidden select-none">
<span class="text-[11px] text-[#8a98a1] transition-transform duration-200 group-open:rotate-90"></span>
<summary
class="flex cursor-pointer list-none items-center gap-2 py-1 text-[#526376] marker:hidden select-none">
<span
class="truncate transition-colors duration-200"
class="text-[11px] text-[#8a98a1] transition-transform duration-200 group-open:rotate-90"></span>
<span class="truncate transition-colors duration-200"
:class="isNodeActive(item) ? 'text-primary font-semibold' : 'hover:text-[#2f4050]'"
@click.stop="scrollToHeading(item.id)"
>
@click.stop="scrollToHeading(item.id)">
{{ item.text }}
</span>
</summary>
<div class="ml-4 border-l border-[#d7dfdf] pl-3">
<div v-for="child in item.children" :key="child.id" class="py-1">
<div
class="cursor-pointer truncate transition-colors duration-200"
<div class="cursor-pointer truncate transition-colors duration-200"
:class="activeHeadingId === child.id ? 'text-primary font-semibold' : 'text-[#687983] hover:text-[#2f4050]'"
@click="scrollToHeading(child.id)"
>
@click="scrollToHeading(child.id)">
{{ child.text }}
</div>
</div>
</div>
</details>
<div
v-else
class="cursor-pointer py-1 transition-colors duration-200"
<div v-else class="cursor-pointer py-1 transition-colors duration-200"
:class="activeHeadingId === item.id ? 'text-primary font-semibold' : 'text-[#526376] hover:text-[#2f4050]'"
@click="scrollToHeading(item.id)"
>
@click="scrollToHeading(item.id)">
{{ item.text }}
</div>
</template>
@ -66,23 +66,25 @@
</div>
</aside>
<section class="min-w-0 flex-1 overflow-hidden rounded-[30px] border border-white/70 bg-transparent p-4 ring-1 ring-inset ring-[rgba(255,255,255,0.38)] shadow-[0_20px_46px_rgba(109,126,117,0.1)] sm:p-6">
<n-scrollbar ref="detailScrollbar" :style="contentStyle" trigger="none" class="pr-1 mb-4 overflow-hidden">
<section class="blog-list-panel min-w-0 flex-1 overflow-hidden p-4 sm:p-6">
<n-scrollbar ref="detailScrollbar" :style="contentStyle" trigger="none" class="pr-1 mb-4 rounded-[3em] overflow-hidden">
<div class="space-y-5 pr-1">
<section class="rounded-[28px] border border-white/68 bg-transparent p-6 ring-1 ring-inset ring-[rgba(255,255,255,0.34)] shadow-[0_18px_38px_rgba(116,132,124,0.09)]">
<section class="blog-detail-card p-6">
<div class="flex flex-col gap-4 md:flex-row md:items-start md:justify-between">
<div class="min-w-0">
<div class="text-[0.82rem] font-semibold tracking-[0.16em] text-[#718195]">文章详情</div>
<h1 class="my-4 !w-full ml-1/2 text-center text-[1.9rem] font-semibold leading-[1.3] text-[#2f4050] sm:text-[2.15rem]">
<h1
class="my-4 !w-full ml-1/2 text-center text-[1.9rem] font-semibold leading-[1.3] text-[#2f4050] sm:text-[2.15rem]">
{{ blogData.title || '文章内容' }}
</h1>
</div>
<n-breadcrumb class="shrink-0 rounded-full border border-white/65 bg-transparent px-4 text-[#526376]" separator="|">
<n-breadcrumb class="shrink-0 rounded-full border border-white/52 bg-white/16 px-4 text-[#526376]"
separator="|">
<n-breadcrumb-item href="/blog/blog">文章</n-breadcrumb-item>
<n-breadcrumb-item>{{ blogData.title }}</n-breadcrumb-item>
</n-breadcrumb>
</div>
<div class="flex flex-wrap items-center justify-right gap-x-5 gap-y-2 text-sm text-[#708091]">
<div class="flex flex-wrap items-center justify-right gap-x-5 gap-y-2 text-sm text-[#708091]">
<em class="flex items-center gap-1 not-italic text-primary">
<n-icon><icon-author /></n-icon>
{{ blogData.nickname }}
@ -94,19 +96,15 @@
</div>
<div class="mt-4 flex flex-wrap justify-center gap-3">
<div
v-for="item in tags"
:key="item"
:style="{ backgroundColor: getTagColor(item) }"
class="inline-flex cursor-pointer items-center gap-1 rounded-full px-3 py-1 text-sm text-white shadow-[0_8px_20px_rgba(88,108,125,0.16)]"
>
<div v-for="(item, index) in tags" :key="index" :style="{ backgroundColor: getTagColor(item) }"
class="inline-flex cursor-pointer items-center gap-1 rounded-full px-3 py-1 text-sm text-white shadow-[0_8px_20px_rgba(88,108,125,0.16)]">
<n-icon><icon-tag /></n-icon>
{{ item }}
</div>
</div>
</section>
<section class="rounded-[26px] border border-white/66 bg-transparent p-4 ring-1 ring-inset ring-[rgba(255,255,255,0.32)] shadow-[0_16px_34px_rgba(116,132,124,0.09)] sm:p-5">
<section class="blog-detail-card p-4 sm:p-5">
<n-collapse @item-header-click="AISum" arrow-placement="right">
<n-collapse-item>
<template #header>
@ -127,14 +125,21 @@
</n-collapse>
</section>
<section class="rounded-[26px] border border-white/66 bg-transparent px-5 py-6 ring-1 ring-inset ring-[rgba(255,255,255,0.32)] shadow-[0_18px_36px_rgba(116,132,124,0.09)] sm:px-8 sm:py-8">
<MdPreview
class="relative article-preview !bg-transparent"
:modelValue="markdown"
/>
<section class="blog-detail-card px-5 py-6 sm:px-8 sm:py-8">
<MdPreview class="relative article-preview !bg-transparent" :modelValue="markdown" />
</section>
</div>
</n-scrollbar>
<button
v-show="showBackToTop"
type="button"
aria-label="回到顶部"
class="absolute bottom-6 right-6 z-[3] flex h-11 w-11 items-center justify-center rounded-full outline-none border-none bg-white/24 text-primary shadow-[0_16px_28px_rgba(109,126,117,0.14),inset_0_1px_0_rgba(255,255,255,0.24)] backdrop-blur-[16px] transition-all duration-300 hover:-translate-y-1 hover:bg-white/36"
@click="scrollToTop"
>
<icon-backtop class="h-5 w-5" />
</button>
</section>
</div>
</div>
@ -223,6 +228,7 @@ const editorRef = ref<ExposeParam>()
const detailScrollbar = ref<DetailScrollbarInst | null>(null)
const scrollElement = ref<HTMLElement | undefined>(undefined)
const activeHeadingId = ref('')
const showBackToTop = ref(false)
const headingElementMap = shallowRef(new Map<string, HTMLElement>())
const route = useRoute()
const nav = $store.nav.useNavStore() as { navH: number }
@ -353,6 +359,7 @@ function syncPreviewHeadings() {
function updateActiveHeading() {
const container = scrollElement.value
showBackToTop.value = !!container && container.scrollTop > 240
const previewRoot = getPreviewRoot()
if (!container || !previewRoot) return
@ -392,6 +399,14 @@ function scrollToHeading(id: string) {
activeHeadingId.value = id
}
function scrollToTop() {
detailScrollbar.value?.scrollTo?.({
top: 0,
behavior: 'smooth',
})
activeHeadingId.value = tocItems.value[0]?.id || ''
}
watch([markdown, tocItems], async () => {
await nextTick()
requestAnimationFrame(() => {
@ -416,18 +431,18 @@ async function getBlogDetail() {
async function AISum() {
if (aimask.value || !blogData.value.cont) return
const response = await fetch('http://38.12.26.19:8317/v1/chat/completions', {
const response = await fetch('https://api.siliconflow.cn/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer sk-7wys75B2TsQYceMbZ'
Authorization: 'Bearer sk-jwwmhmxsjtseyekknqmamlvzmrkmwfvuacnssbwfufogrkdg'
},
body: JSON.stringify({
model: 'gpt-5.4',
model: 'Qwen/Qwen3-8B',
messages: [
{
role: 'system',
content: '你是一个摘要生成工具请用中文总结我发送给你的文章内容不要换行不要超过300字只介绍文章内容不需要提出建议。开头固定为“这篇文章介绍了”,语气自然一些。'
content: '你是一个摘要生成工具请用中文总结我发送给你的文章内容不要换行不要超过300字只介绍文章内容语气需要俏皮一些,不需要提出建议。开头固定为“这篇文章介绍了”。'
},
{ role: 'user', content: blogData.value.cont }
],

View File

@ -1,138 +1,193 @@
<template>
<div
class="relative overflow-hidden bg-[radial-gradient(circle_at_12%_18%,rgba(215,177,123,0.24),transparent_24%),radial-gradient(circle_at_86%_12%,rgba(120,162,182,0.18),transparent_24%),radial-gradient(circle_at_50%_100%,rgba(176,198,172,0.14),transparent_28%),linear-gradient(180deg,#efe7da_0%,#e8f0ef_44%,#f3f1e8_100%)]"
:style="boxStyle"
>
<div class="pointer-events-none absolute left-[-5rem] top-6 h-80 w-80 rounded-full bg-[rgba(234,176,107,0.45)] opacity-48 blur-[88px]"></div>
<div class="pointer-events-none absolute right-[-5rem] top-32 h-[22rem] w-[22rem] rounded-full bg-[rgba(118,176,214,0.32)] opacity-48 blur-[88px]"></div>
<div class="pointer-events-none absolute inset-0 opacity-14 [background-image:linear-gradient(rgba(255,255,255,0.18)_1px,transparent_1px),linear-gradient(90deg,rgba(255,255,255,0.18)_1px,transparent_1px)] [background-size:36px_36px] [mask-image:linear-gradient(180deg,rgba(0,0,0,0.8),transparent_85%)]"></div>
:style="boxStyle">
<div
class="pointer-events-none absolute left-[-5rem] top-6 h-80 w-80 rounded-full bg-[rgba(234,176,107,0.45)] opacity-48 blur-[88px]">
</div>
<div
class="pointer-events-none absolute right-[-5rem] top-32 h-[22rem] w-[22rem] rounded-full bg-[rgba(118,176,214,0.32)] opacity-48 blur-[88px]">
</div>
<div
class="pointer-events-none absolute inset-0 opacity-14 [background-image:linear-gradient(rgba(255,255,255,0.18)_1px,transparent_1px),linear-gradient(90deg,rgba(255,255,255,0.18)_1px,transparent_1px)] [background-size:36px_36px] [mask-image:linear-gradient(180deg,rgba(0,0,0,0.8),transparent_85%)]">
</div>
<div class="relative z-[1] mx-auto flex h-full w-[86%] gap-6 py-6">
<aside class="hidden w-[21rem] shrink-0 lg:block">
<div class="sticky top-6">
<section class="relative rounded-[28px] border border-white/62 bg-[linear-gradient(145deg,rgba(255,251,246,0.68),rgba(237,246,244,0.56))] p-6 shadow-[0_20px_50px_rgba(110,124,112,0.11)] backdrop-blur-[20px] after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:bg-[linear-gradient(120deg,rgba(255,255,255,0.24),transparent_45%)] after:content-['']">
<div class="relative z-[1]">
<h1 class="mt-3 text-[2rem] text-center leading-[1.2] text-[#2c3a4a]">文章分类</h1>
<div class="mt-6 grid gap-4">
<button
type="button"
@click="clearCate"
class="flex items-center justify-between rounded-[22px] border px-4 py-4 text-left transition-[transform,box-shadow,border-color,background-color,color] duration-300 [transition-timing-function:cubic-bezier(0.22,1,0.36,1)]"
:class="currentCateIdx === -1
? 'border-transparent bg-[linear-gradient(135deg,#ec66ab,#f48a6f)] text-white shadow-[0_16px_34px_rgba(236,102,171,0.24)]'
: 'border-white/70 bg-white/66 text-[#435468] shadow-[0_12px_28px_rgba(112,128,118,0.1)] hover:-translate-y-0.5 hover:border-white/90 hover:bg-white/76'"
>
<span class="flex items-center gap-3">
<icon-arti class="h-5 w-5" :class="currentCateIdx === -1 ? 'text-white' : 'text-primary'" />
<span class="text-base font-semibold">全部文章</span>
</span>
<span class="rounded-full px-3 py-1 text-sm font-semibold" :class="currentCateIdx === -1 ? 'bg-white/20 text-white' : 'bg-primary text-white'">
{{ allTotal }}
</span>
</button>
<button
v-for="(item, idx) in cateList"
:key="item.cid"
type="button"
@click="clickCate(item, idx)"
class="flex items-center justify-between rounded-[22px] border px-4 py-4 text-left transition-[transform,box-shadow,border-color,background-color,color] duration-300 [transition-timing-function:cubic-bezier(0.22,1,0.36,1)]"
:class="currentCateIdx === idx
? 'border-transparent bg-[linear-gradient(135deg,#ec66ab,#f48a6f)] text-white shadow-[0_16px_34px_rgba(236,102,171,0.24)]'
: 'border-white/70 bg-white/66 text-[#435468] shadow-[0_12px_28px_rgba(112,128,118,0.1)] hover:-translate-y-0.5 hover:border-white/90 hover:bg-white/76'"
>
<span class="flex min-w-0 items-center gap-3">
<icon-arti class="h-5 w-5 shrink-0" :class="currentCateIdx === idx ? 'text-white' : 'text-primary'" />
<span class="truncate text-base font-semibold">{{ item.name }}</span>
</span>
<span class="shrink-0 rounded-full px-3 py-1 text-sm font-semibold" :class="currentCateIdx === idx ? 'bg-white/20 text-white' : 'bg-primary text-white'">
{{ item.total || 0 }}
</span>
</button>
<div class="relative z-[1] mx-auto flex h-full w-[86%] items-stretch gap-6 py-6">
<aside class="hidden h-full w-[21rem] shrink-0 lg:block">
<section class="blog-glass-panel flex h-full flex-col p-5">
<div class="relative z-[1] flex h-full min-h-0 flex-col">
<div class="flex items-start justify-between gap-4">
<div class="min-w-0">
<h1 class="mt-2 text-[1.8rem] leading-[1.15] text-[#2c3a4a]">文章分类</h1>
</div>
</div>
</section>
</div>
<div class="mt-5 grid min-h-0 flex-1 gap-3"
:style="{ gridTemplateRows: `repeat(${categoryButtonCount}, minmax(0, 1fr))` }">
<button type="button" @click="clearCate" class="blog-filter-btn" :class="currentCateIdx === -1
? 'blog-filter-btn-active'
: 'blog-filter-btn-idle'">
<span class="relative z-[1] flex min-w-0 items-center gap-4">
<span
class="flex h-10 w-10 shrink-0 items-center justify-center rounded-[16px] shadow-[inset_0_1px_0_rgba(255,255,255,0.25)]"
:class="currentCateIdx === -1
? 'bg-white/18'
: 'bg-white/62 text-primary'">
<icon-arti class="h-5 w-5" :class="currentCateIdx === -1 ? 'text-white' : 'text-primary'" />
</span>
<span class="min-w-0">
<span class="block text-[0.98rem] font-semibold tracking-[0.01em] leading-[1.2]">全部文章</span>
<span class="mt-1 block text-[0.74rem] leading-5"
:class="currentCateIdx === -1 ? 'text-white/78' : 'text-[#7b8796]'">
浏览站内全部内容
</span>
</span>
</span>
<span class="blog-filter-total"
:class="currentCateIdx === -1 ? 'bg-white/14 text-white' : 'bg-white/68 text-primary'">
<span class="text-[1.25rem] font-semibold leading-none">{{ allTotal }}</span>
<span class="mt-1 text-[0.62rem] font-semibold tracking-[0.22em]"
:class="currentCateIdx === -1 ? 'text-white/72' : 'text-[#91a0af]'">
POSTS
</span>
</span>
</button>
<button v-for="(item, idx) in cateList" :key="item.cid" type="button" @click="clickCate(item, idx)"
class="blog-filter-btn" :class="currentCateIdx === idx
? 'blog-filter-btn-active'
: 'blog-filter-btn-idle'">
<span class="relative z-[1] flex min-w-0 items-center gap-4">
<span
class="flex h-11 w-11 shrink-0 items-center justify-center rounded-[18px] shadow-[inset_0_1px_0_rgba(255,255,255,0.25)]"
:class="currentCateIdx === idx
? 'bg-white/18'
: 'bg-white/62 text-primary'">
<icon-arti class="h-5 w-5 shrink-0"
:class="currentCateIdx === idx ? 'text-white' : 'text-primary'" />
</span>
<span class="min-w-0">
<span class="block truncate text-xl font-semibold tracking-[0.01em] leading-[1.2]">{{
item.name }}</span>
</span>
</span>
<span class="blog-filter-total"
:class="currentCateIdx === idx ? 'bg-white/14 text-white' : 'bg-white/68 text-primary'">
<span class="text-[1.25rem] font-semibold leading-none">{{ item.total || 0 }}</span>
<span class="mt-1 text-[0.62rem] font-semibold tracking-[0.22em]"
:class="currentCateIdx === idx ? 'text-white/72' : 'text-[#91a0af]'">
POSTS
</span>
</span>
</button>
</div>
</div>
</section>
</aside>
<section class="min-w-0 flex-1 rounded-[30px] border border-white/62 bg-white/42 p-4 shadow-[0_18px_42px_rgba(109,126,117,0.1)] backdrop-blur-[18px] sm:p-6">
<section class="blog-list-panel min-w-0 flex h-full flex-1 flex-col p-4 sm:p-6">
<div class="pointer-events-none absolute right-[-4rem] top-12 h-40 w-40 rounded-full bg-[rgba(236,102,171,0.16)] blur-[88px]"></div>
<div class="pointer-events-none absolute left-[-3rem] bottom-10 h-36 w-36 rounded-full bg-[rgba(118,176,214,0.14)] blur-[82px]"></div>
<div class="mb-5 flex flex-col gap-4 md:flex-row md:items-start md:justify-between">
<div>
<div class="relative z-[1]">
<div class="text-[0.82rem] font-semibold tracking-[0.16em] text-[#718195]">文章列表</div>
<div class="mt-2 text-[1.6rem] font-semibold text-[#2f4050]">{{ currentCateLabel }}</div>
</div>
<div class="inline-flex items-center whitespace-nowrap rounded-full border border-white/80 bg-white/68 px-4 py-[0.65rem] text-[#526376]">
当前 {{ blogList.length }}
<div class="blog-stat-card relative z-[1] min-w-[126px] text-right">
<div class="relative z-[1] flex items-end justify-end">
<span class="relative z-[1] mr-3 text-sm text-[#8c99a8]">当前列表统计</span>
<span class="text-[2rem] leading-none font-semibold text-primary">{{ blogList.length }}</span>
<span class=" text-sm ml-1 font-medium text-[#748395]"></span>
</div>
</div>
</div>
<div class="mb-4 flex flex-wrap gap-2 lg:hidden">
<button
type="button"
@click="clearCate"
class="rounded-full border px-4 py-2 text-sm font-semibold transition-colors duration-200"
:class="currentCateIdx === -1 ? 'border-transparent bg-[linear-gradient(135deg,#ec66ab,#f48a6f)] text-white' : 'border-white/80 bg-white/70 text-[#526376]'"
>
<button type="button" @click="clearCate" class="blog-mobile-filter-pill"
:class="currentCateIdx === -1 ? 'blog-mobile-filter-pill-active' : ''">
全部
</button>
<button
v-for="(item, idx) in cateList"
:key="item.cid"
type="button"
@click="clickCate(item, idx)"
class="rounded-full border px-4 py-2 text-sm font-semibold transition-colors duration-200"
:class="currentCateIdx === idx ? 'border-transparent bg-[linear-gradient(135deg,#ec66ab,#f48a6f)] text-white' : 'border-white/80 bg-white/70 text-[#526376]'"
>
<button v-for="(item, idx) in cateList" :key="item.cid" type="button" @click="clickCate(item, idx)"
class="blog-mobile-filter-pill" :class="currentCateIdx === idx ? 'blog-mobile-filter-pill-active' : ''">
{{ item.name }} · {{ item.total || 0 }}
</button>
</div>
<n-scrollbar :style="listStyle" trigger="none" class="pr-1">
<div class="space-y-5">
<div v-if="blogList.length === 0" class="rounded-[24px] border border-dashed border-white/75 bg-white/48 px-6 py-16 text-center text-[#7a8797]">
暂时还没有相关文章
<n-scrollbar trigger="none" class="relative z-[1] min-h-0 flex-1 pr-1">
<div class="blog-scroll-panel min-h-full">
<div class="space-y-5">
<div v-if="blogList.length === 0"
class="rounded-[24px] border border-dashed border-white/46 bg-white/20 px-6 py-16 text-center text-[#7a8797] backdrop-blur-[16px]">
暂时还没有相关文章
</div>
<article v-for="(item, index) in blogList" :key="item.aid"
class="blog-list-card blog-card-animate group cursor-pointer"
:style="{ animationDelay: `${index * 90}ms` }" @click="$router.push(`/blog/${item.aid}`)">
<div
class="pointer-events-none absolute -right-10 top-[-2rem] h-28 w-28 rounded-full bg-[rgba(236,102,171,0.18)] transition duration-500 group-hover:scale-125 group-hover:opacity-100">
</div>
<div
class="pointer-events-none absolute -left-8 bottom-[-2rem] h-24 w-24 rounded-full bg-[rgba(104,170,201,0.16)] transition duration-500 group-hover:translate-x-2">
</div>
<div class="pointer-events-none absolute inset-x-6 top-0 h-18 bg-[linear-gradient(180deg,rgba(255,255,255,0.24),transparent)] opacity-80 "></div>
<div class="relative z-[1]">
<div class="flex flex-col gap-3 md:flex-row md:items-start md:justify-between">
<div
class="inline-flex w-fit items-center rounded-full border border-white/58 bg-white/22 px-3 py-1 text-[0.72rem] font-semibold tracking-[0.18em] text-[#7a8797] shadow-[0_8px_16px_rgba(102,123,112,0.08)] backdrop-blur-[14px]">
ARTICLE {{ String(index + 1).padStart(2, '0') }}
</div>
<div class="flex flex-wrap justify-end gap-x-4 gap-y-2 text-sm text-[#708091]">
<em class="flex items-center gap-1 not-italic">
<n-icon><icon-date /></n-icon>
{{ formatTime(item.updated_at, 'YYYY年MM月DD日') }}
</em>
<em class="flex items-center gap-1 not-italic">
<n-icon><icon-pen /></n-icon>
{{ getSize(item.cont) }}
</em>
<em class="flex items-center gap-1 not-italic">
<n-icon><icon-author /></n-icon>
{{ item.nickname }}
</em>
</div>
</div>
<h2
class="mt-4 text-[1.45rem] font-semibold leading-[1.35] text-primary transition-transform duration-300 group-hover:translate-x-1">
{{ item.title }}
</h2>
<p class="mt-3 line-clamp-3 indent-[2em] leading-[1.9] text-[#66778b]">{{ item.pro }}</p>
<div class="mt-5 flex flex-wrap items-center justify-between gap-4">
<div class="flex flex-wrap gap-3">
<div v-for="(tag, tagIdx) in getTags(item.tags)" :key="tag"
:style="{ backgroundColor: getTagColor(tag), animationDelay: `${index * 90 + tagIdx * 60 + 140}ms` }"
class="blog-chip-animate inline-flex items-center gap-1 rounded-full px-3 py-1 text-sm text-white shadow-[0_8px_20px_rgba(88,108,125,0.16)]">
<icon-tag2 class="h-4 w-4" />
{{ tag }}
</div>
</div>
<span
class="inline-flex items-center gap-2 rounded-full border border-white/52 bg-white/24 px-4 py-2 text-sm font-semibold text-[#5e6f82] shadow-[0_10px_18px_rgba(102,123,112,0.08)] backdrop-blur-[16px] transition-all duration-300 group-hover:translate-x-1 group-hover:text-primary">
阅读全文
<span class="text-base leading-none">+</span>
</span>
</div>
</div>
</article>
</div>
<article
v-for="item in blogList"
:key="item.aid"
class="cursor-pointer rounded-[26px] border border-white/72 bg-[linear-gradient(145deg,rgba(255,251,246,0.78),rgba(237,246,244,0.62))] p-6 shadow-[0_16px_34px_rgba(116,132,124,0.12)] backdrop-blur-[18px] transition-[transform,box-shadow,border-color] duration-[320ms] [transition-timing-function:cubic-bezier(0.22,1,0.36,1)] hover:-translate-y-1 hover:border-white/90 hover:shadow-[0_22px_42px_rgba(102,123,112,0.16)]"
@click="$router.push(`/blog/${item.aid}`)"
>
<div class="flex flex-wrap justify-end gap-x-4 gap-y-2 text-sm text-[#708091]">
<em class="flex items-center gap-1 not-italic">
<n-icon><icon-date /></n-icon>
{{ formatTime(item.updated_at, 'YYYY年MM月DD日') }}
</em>
<em class="flex items-center gap-1 not-italic">
<n-icon><icon-pen /></n-icon>
{{ getSize(item.cont) }}
</em>
<em class="flex items-center gap-1 not-italic">
<n-icon><icon-author /></n-icon>
{{ item.nickname }}
</em>
</div>
<h2 class="mt-4 text-[1.45rem] font-semibold leading-[1.35] text-primary">{{ item.title }}</h2>
<p class="mt-3 line-clamp-3 leading-[1.85] text-[#66778b]">{{ item.pro }}</p>
<div class="mt-5 flex flex-wrap gap-3">
<div
v-for="tag in getTags(item.tags)"
:key="tag"
:style="{ backgroundColor: getTagColor(tag) }"
class="inline-flex items-center gap-1 rounded-full px-3 py-1 text-sm text-white shadow-[0_8px_20px_rgba(88,108,125,0.16)]"
>
<icon-tag2 class="h-4 w-4" />
{{ tag }}
</div>
</div>
</article>
</div>
<div v-if="blogList.length > page_size" class="mt-8 flex justify-center rounded-[22px] border border-white/70 bg-white/52 px-4 py-5 shadow-[0_10px_24px_rgba(116,132,124,0.08)]">
<n-pagination v-model:page="page_num" :page-count="total" :page-slot="5" @update:page="getBlogList" />
<div v-if="blogList.length > page_size"
class="mt-8 flex justify-center rounded-[22px] border border-white/44 bg-white/18 px-4 py-5 shadow-[0_10px_24px_rgba(116,132,124,0.08)] backdrop-blur-[14px]">
<n-pagination v-model:page="page_num" :page-count="total" :page-slot="5" @update:page="getBlogList" />
</div>
</div>
</n-scrollbar>
</section>
@ -208,7 +263,6 @@ const SOFT_TAG_PALETTE = [
const blogList = ref<BlogItem[]>([])
const boxStyle = ref<Record<string, string>>({})
const listStyle = ref<Record<string, string>>({})
const nav = $store.nav.useNavStore() as { navH: number }
const cateList = ref<CateItem[]>([])
const currentCateIdx = ref(-1)
@ -219,6 +273,7 @@ const category = ref('')
const tagColorList = ref<TagColorItem[]>([])
const allTotal = computed(() => cateList.value.reduce((acc, cur) => acc + (cur.total || 0), 0))
const categoryButtonCount = computed(() => Math.max(cateList.value.length + 1, 1))
const currentCateLabel = computed(() => {
if (currentCateIdx.value === -1) return '全部文章'
@ -229,7 +284,6 @@ const currentCateLabel = computed(() => {
function updateLayout() {
const viewportHeight = window.innerHeight - nav.navH - 1
boxStyle.value = { height: `${viewportHeight}px` }
listStyle.value = { height: `${Math.max(viewportHeight - 124, 280)}px` }
}
function resetPaging() {

View File

@ -1,11 +1,75 @@
import { defineConfig } from "unocss";
import { defineConfig, presetUno } from "unocss";
export default defineConfig({
// ...UnoCSS options
presets: [presetUno()],
shortcuts: {
"blog-glass-panel": "relative overflow-hidden rounded-[30px] border border-white/62 bg-[linear-gradient(145deg,rgba(255,251,246,0.68),rgba(237,246,244,0.56))] shadow-[0_20px_50px_rgba(110,124,112,0.11)] backdrop-blur-[20px] after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:bg-[linear-gradient(120deg,rgba(255,255,255,0.24),transparent_45%)] after:content-['']",
"blog-filter-btn": "relative flex h-full min-h-0 w-full items-center justify-between gap-3 overflow-hidden rounded-[22px] border-none px-4 py-3 text-left outline-none ring-0 backdrop-blur-[26px] transition-[transform,box-shadow,background-color,color] duration-350 [transition-timing-function:cubic-bezier(0.22,1,0.36,1)] before:pointer-events-none before:absolute before:inset-[1px] before:rounded-[20px] before:border-0 before:content-[''] after:pointer-events-none after:absolute after:inset-0 after:bg-[radial-gradient(circle_at_top_left,rgba(255,255,255,0.28),transparent_42%)] after:opacity-80 after:content-['']",
"blog-filter-btn-idle": "bg-[linear-gradient(135deg,rgba(255,255,255,0.5),rgba(255,255,255,0.18))] text-[#435468] shadow-[0_14px_30px_rgba(112,128,118,0.1)] hover:-translate-y-1 hover:translate-x-1 hover:bg-[linear-gradient(135deg,rgba(255,255,255,0.68),rgba(244,248,247,0.3))] hover:shadow-[0_20px_36px_rgba(112,128,118,0.14)]",
"blog-filter-btn-active": "bg-[linear-gradient(135deg,rgba(236,102,171,0.96),rgba(244,138,111,0.88))] text-white shadow-[0_18px_38px_rgba(236,102,171,0.28)]",
"blog-stat-card": "relative overflow-hidden rounded-[22px] border border-white/52 bg-[linear-gradient(135deg,rgba(255,255,255,0.28),rgba(240,247,247,0.14))] px-4 py-3 shadow-[0_18px_34px_rgba(112,128,118,0.12),inset_0_1px_0_rgba(255,255,255,0.2)] backdrop-blur-[24px] before:pointer-events-none before:absolute before:inset-0 before:bg-[linear-gradient(135deg,rgba(255,255,255,0.28),transparent_42%,rgba(151,197,224,0.1))] before:content-[''] after:pointer-events-none after:absolute after:inset-[1px] after:rounded-[20px] after:border after:border-white/18 after:content-['']",
"blog-filter-total": "relative z-[1] flex min-w-[72px] shrink-0 flex-col items-center justify-center rounded-[18px] border-none px-3 py-2 text-center shadow-[inset_0_1px_0_rgba(255,255,255,0.18)]",
"blog-list-panel": "relative overflow-hidden rounded-[30px] border border-white/54 bg-[linear-gradient(145deg,rgba(255,255,255,0.28),rgba(240,247,247,0.14))] shadow-[0_24px_60px_rgba(109,126,117,0.12)] backdrop-blur-[28px] before:pointer-events-none before:absolute before:inset-0 before:bg-[linear-gradient(135deg,rgba(255,255,255,0.24),transparent_38%,rgba(151,197,224,0.08))] before:content-[''] after:pointer-events-none after:absolute after:inset-[1px] after:rounded-[28px] after:border after:border-white/22 after:content-['']",
"blog-scroll-panel": "relative overflow-hidden rounded-[24px] border border-white/36 bg-[linear-gradient(180deg,rgba(255,255,255,0.16),rgba(255,255,255,0.07))] p-4 shadow-[inset_0_1px_0_rgba(255,255,255,0.24)] backdrop-blur-[18px]",
"blog-detail-card": "relative overflow-hidden rounded-[28px] border border-white/54 bg-[linear-gradient(145deg,rgba(255,255,255,0.32),rgba(240,247,247,0.14))] shadow-[0_18px_42px_rgba(109,126,117,0.12),inset_0_1px_0_rgba(255,255,255,0.24)] backdrop-blur-[24px] before:pointer-events-none before:absolute before:inset-0 before:bg-[linear-gradient(135deg,rgba(255,255,255,0.24),transparent_44%,rgba(151,197,224,0.1))] before:content-[''] after:pointer-events-none after:absolute after:inset-[1px] after:rounded-[26px] after:border after:border-white/18 after:content-['']",
"blog-detail-card-soft": "relative overflow-hidden rounded-[22px] border border-white/46 bg-[linear-gradient(145deg,rgba(255,255,255,0.24),rgba(244,248,247,0.12))] shadow-[0_14px_28px_rgba(109,126,117,0.09),inset_0_1px_0_rgba(255,255,255,0.2)] backdrop-blur-[18px] before:pointer-events-none before:absolute before:inset-0 before:bg-[radial-gradient(circle_at_top_left,rgba(255,255,255,0.24),transparent_44%)] before:content-['']",
"blog-list-card": "relative overflow-hidden rounded-[28px] border border-white/52 bg-[linear-gradient(145deg,rgba(255,255,255,0.34),rgba(240,247,247,0.16))] p-6 shadow-[0_18px_40px_rgba(116,132,124,0.14),inset_0_1px_0_rgba(255,255,255,0.26)] backdrop-blur-[24px] before:pointer-events-none before:absolute before:inset-0 before:bg-[linear-gradient(135deg,rgba(255,255,255,0.28),transparent_48%,rgba(151,197,224,0.1))] before:content-[''] after:pointer-events-none after:absolute after:inset-[1px] after:rounded-[26px] after:border after:border-white/18 after:content-[''] transition-[transform,box-shadow,border-color] duration-[320ms] [transition-timing-function:cubic-bezier(0.22,1,0.36,1)] hover:-translate-y-2 hover:border-white/72 hover:shadow-[0_28px_52px_rgba(102,123,112,0.18),inset_0_1px_0_rgba(255,255,255,0.32)]",
"blog-card-animate": "opacity-0 [will-change:transform,opacity] motion-safe:[animation:blog-card-in_720ms_cubic-bezier(0.22,1,0.36,1)_both]",
"blog-chip-animate": "opacity-0 [will-change:transform,opacity] motion-safe:[animation:blog-chip-in_560ms_cubic-bezier(0.2,0.8,0.2,1)_both]",
"blog-mobile-filter-pill": "rounded-full border border-white/72 bg-white/58 px-4 py-2 text-sm font-semibold text-[#526376] shadow-[0_10px_22px_rgba(112,128,118,0.08)] backdrop-blur-[14px] transition-[transform,box-shadow,border-color,background-color,color] duration-250 hover:-translate-y-0.5 hover:border-white/90 hover:bg-white/74",
"blog-mobile-filter-pill-active": "border-transparent bg-[linear-gradient(135deg,#ec66ab,#f48a6f)] text-white shadow-[0_12px_24px_rgba(236,102,171,0.22)]",
"home-glass-surface": "relative border border-white/58 bg-[linear-gradient(135deg,rgba(255,255,255,0.34),rgba(255,255,255,0.16)),linear-gradient(180deg,rgba(241,248,252,0.2),rgba(224,236,245,0.08))] shadow-[0_18px_38px_rgba(86,111,137,0.12),inset_0_1px_0_rgba(255,255,255,0.68),inset_0_-1px_0_rgba(255,255,255,0.14)] backdrop-blur-[24px] saturate-[165%] before:pointer-events-none before:absolute before:inset-0 before:rounded-[inherit] before:bg-[linear-gradient(135deg,rgba(255,255,255,0.34),rgba(255,255,255,0.08)_48%,rgba(151,197,224,0.08)),radial-gradient(circle_at_top_left,rgba(255,255,255,0.45),transparent_42%)] before:opacity-[0.95] before:content-[''] after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:border after:border-white/24 after:bg-[linear-gradient(180deg,rgba(255,255,255,0.14),transparent_36%,rgba(255,255,255,0.08))] after:content-['']",
"home-glass-floating": "shadow-[0_24px_52px_rgba(86,111,137,0.18),inset_0_1px_0_rgba(255,255,255,0.74),inset_0_-1px_0_rgba(255,255,255,0.16)]",
"home-nav-card": "home-glass-surface transition-[transform,box-shadow,border-color] duration-[320ms] [transition-timing-function:cubic-bezier(0.22,1,0.36,1)] hover:border-white/74 hover:shadow-[0_28px_48px_rgba(86,111,137,0.18),inset_0_1px_0_rgba(255,255,255,0.78),inset_0_-1px_0_rgba(255,255,255,0.2)]",
"home-nav-card-warm": "bg-[linear-gradient(135deg,rgba(255,249,242,0.4),rgba(255,245,231,0.2)),linear-gradient(180deg,rgba(255,236,211,0.14),rgba(244,225,204,0.06))]",
"home-search-select": "rounded-full bg-transparent px-0.5 py-0 min-h-[2.5rem] relative before:pointer-events-none before:absolute before:inset-[1px] before:rounded-[9999px] before:bg-[linear-gradient(135deg,rgba(255,255,255,0.28),rgba(255,255,255,0.08))] before:shadow-[inset_0_1px_0_rgba(255,255,255,0.36),inset_-1px_0_0_rgba(255,255,255,0.1)] before:content-['']",
"home-search-panel-animate": "overflow-auto origin-top motion-safe:[animation:home-search-panel-enter_180ms_cubic-bezier(0.22,1,0.36,1)_both]",
},
preflights: [
{
getCSS: () => `
@keyframes blog-card-in {
0% {
opacity: 0;
transform: translate3d(0, 28px, 0) scale(0.97);
}
65% {
opacity: 1;
}
100% {
opacity: 1;
transform: translate3d(0, 0, 0) scale(1);
}
}
@keyframes blog-chip-in {
0% {
opacity: 0;
transform: translate3d(0, 12px, 0) scale(0.92);
}
100% {
opacity: 1;
transform: translate3d(0, 0, 0) scale(1);
}
}
@keyframes home-search-panel-enter {
from {
opacity: 0;
transform: translateY(-6px) scale(0.98);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
`,
},
],
theme: {
colors: {
primary: "#ec66ab",
deepp: "#a7415d"
deepp: "#a7415d",
},
},
});