移动端美化:悬浮磨砂导航菜单 + 保守档字号
菜单(site-header.astro) - 全屏遮罩居中大链接 -> 仿 Firefly float-panel 的悬浮磨砂卡片: fixed top-[76px] 左右各留 12px,rounded-2xl,浅阴影,max-h 兜底防横屏溢出 - 菜单项 30px -> 18px,行高 44px,圆角 hover 底色;外链右侧加 ↗ - 当前页在构建期用 Astro.url.pathname 判定,高亮为主色文字 + 淡主色底 + 正文标题那道红色短竖线(滑入/当前项显示) - 入场动画改为「面板落下 + 逐项上浮」,节奏从 420ms 收到 300ms - 遮罩从纯色全屏改为 background/45% + 极轻模糊,点它关闭 - JS 未改:data-open / ESC / body 滚动锁 / astro:before-swap 复位 / aria 全部沿用 - 补 :focus-visible 自定义焦点环(替掉浏览器默认那圈)、prefers-reduced-motion、 prefers-reduced-transparency、@supports 回退 磨砂 - global.css 新增 --glass-bg / --glass-border / --glass-blur / --glass-saturate, 由 --card 派生所以亮暗自动跟随 - 移动端页眉仍保持关闭常驻磨砂(原来就是有意为之),只有点开的菜单面板用 字号(只动 <768px,保守档) - 正文/引用/列表 16.5px/1.85 -> 15.5px/1.78,行高密度约省 10% - 文章 h1 30 -> 27px;正文 h2 30 -> 25;h3 24 -> 21;h4 20 -> 18 - 列表区标题 24 -> 21px - 桌面端用 md: 前缀原样恢复 验证(headless Chrome 131,390x844 + 1280/800/700) - 390px:正文 15.5/27.59、h1 27、菜单项 18;面板 445px 高、底边 511 < 844 - 1280/800px:正文 16.5/30.525、h1 48px,与改动前一致 - 亮/暗两套截图正常,无横向溢出
This commit is contained in:
@@ -35,7 +35,7 @@ const primaryTagHref = primaryTag ? `/?tag=${encodeURIComponent(primaryTag)}#hom
|
||||
|
||||
<header class="mb-8 xl:mb-12">
|
||||
<h1
|
||||
class="font-serif text-3xl sm:text-4xl md:text-5xl font-bold text-foreground leading-tight mb-6 flex items-start gap-3"
|
||||
class="font-serif text-[27px] sm:text-4xl md:text-5xl font-bold text-foreground leading-tight mb-6 flex items-start gap-3"
|
||||
data-pagefind-meta="title"
|
||||
>
|
||||
<span class="w-1.5 h-8 sm:h-10 shrink-0 bg-primary rounded-full mt-[0.15em] sm:mt-[0.3em]"></span>
|
||||
@@ -119,17 +119,17 @@ const primaryTagHref = primaryTag ? `/?tag=${encodeURIComponent(primaryTag)}#hom
|
||||
<article
|
||||
class="prose prose-neutral dark:prose-invert max-w-[44rem] mx-auto
|
||||
prose-headings:font-serif prose-headings:font-bold
|
||||
prose-h2:text-3xl prose-h2:mt-12 prose-h2:mb-4 prose-h2:flex prose-h2:items-center prose-h2:gap-3
|
||||
prose-h3:text-2xl prose-h3:mt-8 prose-h3:mb-3
|
||||
prose-h4:text-xl
|
||||
prose-p:text-[16.5px] prose-p:leading-[1.85] prose-p:my-5 prose-p:text-foreground/90
|
||||
prose-h2:text-[25px] md:prose-h2:text-3xl prose-h2:mt-12 prose-h2:mb-4 prose-h2:flex prose-h2:items-center prose-h2:gap-3
|
||||
prose-h3:text-[21px] md:prose-h3:text-2xl prose-h3:mt-8 prose-h3:mb-3
|
||||
prose-h4:text-[18px] md:prose-h4:text-xl
|
||||
prose-p:text-[15.5px] md:prose-p:text-[16.5px] prose-p:leading-[1.78] md:prose-p:leading-[1.85] prose-p:my-5 prose-p:text-foreground/90
|
||||
prose-a:text-primary prose-a:no-underline prose-a:hover:underline
|
||||
prose-blockquote:border-l-primary prose-blockquote:border-l-2 prose-blockquote:pl-4 prose-blockquote:italic prose-blockquote:text-[16.5px] prose-blockquote:leading-[1.85] prose-blockquote:text-content-secondary prose-blockquote:font-normal
|
||||
prose-blockquote:border-l-primary prose-blockquote:border-l-2 prose-blockquote:pl-4 prose-blockquote:italic prose-blockquote:text-[15.5px] md:prose-blockquote:text-[16.5px] prose-blockquote:leading-[1.78] md:prose-blockquote:leading-[1.85] prose-blockquote:text-content-secondary prose-blockquote:font-normal
|
||||
prose-strong:text-foreground prose-strong:font-semibold
|
||||
prose-code:text-primary prose-code:bg-secondary prose-code:px-1.5 prose-code:py-0.5 prose-code:rounded prose-code:text-sm prose-code:before:content-none prose-code:after:content-none
|
||||
prose-pre:bg-secondary prose-pre:border prose-pre:border-border
|
||||
prose-ol:text-[16.5px] prose-ul:text-[16.5px] prose-ol:text-foreground/90 prose-ul:text-foreground/90 prose-ol:my-5 prose-ul:my-5
|
||||
prose-li:marker:text-primary prose-li:my-1.5 prose-li:leading-[1.85]
|
||||
prose-ol:text-[15.5px] md:prose-ol:text-[16.5px] prose-ul:text-[15.5px] md:prose-ul:text-[16.5px] prose-ol:text-foreground/90 prose-ul:text-foreground/90 prose-ol:my-5 prose-ul:my-5
|
||||
prose-li:marker:text-primary prose-li:my-1.5 prose-li:leading-[1.78] md:prose-li:leading-[1.85]
|
||||
prose-table:text-sm
|
||||
prose-th:bg-secondary prose-th:px-4 prose-th:py-2 prose-th:text-left prose-th:font-medium
|
||||
prose-td:px-4 prose-td:py-2 prose-td:border-t prose-td:border-border
|
||||
|
||||
@@ -312,11 +312,11 @@ export function ArticleList({
|
||||
style={{ animation: "fade-in-up 220ms ease-out" }}
|
||||
>
|
||||
{activeCategory === "all" && !activeTag ? (
|
||||
<h2 className="font-serif text-2xl font-bold leading-tight tracking-tight text-foreground sm:text-3xl lg:text-[2.625rem]">
|
||||
<h2 className="font-serif text-[21px] font-bold leading-tight tracking-tight text-foreground sm:text-3xl lg:text-[2.625rem]">
|
||||
{headerMeta.heading}
|
||||
</h2>
|
||||
) : (
|
||||
<h2 className="flex flex-wrap items-end gap-x-2 gap-y-1 font-serif text-2xl font-bold leading-tight tracking-tight sm:text-3xl lg:text-[2.625rem]">
|
||||
<h2 className="flex flex-wrap items-end gap-x-2 gap-y-1 font-serif text-[21px] font-bold leading-tight tracking-tight sm:text-3xl lg:text-[2.625rem]">
|
||||
<span className="text-foreground/68">{headerMeta.eyebrow}</span>
|
||||
<span className="text-foreground/35">/</span>
|
||||
<span className="text-primary">{headerMeta.heading}</span>
|
||||
|
||||
@@ -15,6 +15,12 @@ const navItems: { name: string; href: string; external?: boolean }[] = [
|
||||
{ name: "关于", href: "/about/" },
|
||||
{ name: "开往", href: "https://www.travellings.cn/go.html", external: true },
|
||||
]
|
||||
|
||||
// 静态站:当前页在构建时就知道,直接拿来高亮菜单项。
|
||||
// 切页后页眉会随新页面一起被换掉,所以这个标记始终对应当前页。
|
||||
const currentPath = Astro.url.pathname
|
||||
const isCurrent = (href: string) =>
|
||||
href === "/#home-main" ? currentPath === "/" : currentPath.startsWith(href)
|
||||
---
|
||||
|
||||
<header class="site-header fixed top-0 left-0 right-0 z-50 bg-background/80 backdrop-blur-xl border-b border-border/50 py-4">
|
||||
@@ -74,63 +80,119 @@ const navItems: { name: string; href: string; external?: boolean }[] = [
|
||||
|
||||
<div
|
||||
id="mobile-menu"
|
||||
class="mobile-menu md:hidden fixed inset-0 z-[60] opacity-0 pointer-events-none transition-opacity duration-300 ease-out"
|
||||
class="mobile-menu md:hidden fixed inset-0 z-[60] pointer-events-none"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
class="mobile-menu-bg absolute inset-0 bg-background/95 backdrop-blur-xl"
|
||||
data-dismiss
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
<div class="mobile-menu-bg absolute inset-0" data-dismiss aria-hidden="true"></div>
|
||||
|
||||
<div class="relative h-full flex flex-col">
|
||||
<div class="flex items-center justify-end px-6 py-4">
|
||||
<div
|
||||
class="mobile-menu-panel absolute left-3 right-3 top-[76px] p-2 rounded-2xl border max-h-[calc(100dvh-96px)] overflow-y-auto overscroll-contain"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="站点导航"
|
||||
>
|
||||
<div class="flex items-center justify-between pl-4 pr-1 pb-1.5">
|
||||
<span class="text-[11px] font-medium uppercase tracking-[0.18em] text-muted-foreground">导航</span>
|
||||
<button
|
||||
type="button"
|
||||
class="p-2 -mr-2 text-foreground"
|
||||
class="p-2 rounded-lg text-muted-foreground transition-colors duration-200 hover:bg-secondary hover:text-foreground"
|
||||
aria-label="关闭菜单"
|
||||
data-dismiss
|
||||
>
|
||||
<span class="relative block w-5 h-5">
|
||||
<span class="absolute left-0 top-1/2 w-full h-px -translate-y-1/2 rotate-45 bg-current" />
|
||||
<span class="absolute left-0 top-1/2 w-full h-px -translate-y-1/2 -rotate-45 bg-current" />
|
||||
</span>
|
||||
<svg
|
||||
class="w-4 h-4"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.75"
|
||||
stroke-linecap="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M6 6l12 12M18 6L6 18" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<nav class="flex-1 flex flex-col items-center justify-center gap-7 px-8">
|
||||
<nav class="flex flex-col">
|
||||
{navItems.map((item, i) => (
|
||||
<a
|
||||
href={item.href}
|
||||
target={item.external ? "_blank" : undefined}
|
||||
rel={item.external ? "noreferrer" : undefined}
|
||||
data-astro-prefetch={item.external ? undefined : ""}
|
||||
class="mobile-nav-item group text-3xl font-medium tracking-tight text-foreground"
|
||||
style={`--stagger: ${60 + i * 40}ms`}
|
||||
aria-current={isCurrent(item.href) ? "page" : undefined}
|
||||
class:list={[
|
||||
"mobile-nav-item group relative flex items-center gap-3 h-11 pl-4 pr-3 rounded-xl text-[18px] font-medium tracking-tight transition-colors duration-200",
|
||||
isCurrent(item.href) ? "is-current text-primary" : "text-foreground hover:text-primary",
|
||||
]}
|
||||
style={`--stagger: ${40 + i * 30}ms`}
|
||||
>
|
||||
<span class="relative inline-block">
|
||||
{item.name}
|
||||
<span class="absolute left-0 -bottom-1 h-px w-full origin-right scale-x-0 bg-primary transition-transform duration-300 group-hover:origin-left group-hover:scale-x-100" />
|
||||
</span>
|
||||
<span class="mobile-nav-marker" aria-hidden="true" />
|
||||
<span class="flex-1">{item.name}</span>
|
||||
{item.external && (
|
||||
<svg
|
||||
class="w-3.5 h-3.5 text-muted-foreground/70 transition-transform duration-200 group-hover:-translate-y-0.5 group-hover:translate-x-0.5"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.75"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M7 17L17 7M9 7h8v8" />
|
||||
</svg>
|
||||
)}
|
||||
</a>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* 容器只负责铺满屏幕、拦截点击和定位,透明度交给遮罩层和面板各自过渡 */
|
||||
.mobile-menu[data-open="true"] {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
/* 遮罩:轻轻压暗 + 极轻的模糊,让人一眼看出下面的内容被“压住了”,点它关闭 */
|
||||
.mobile-menu-bg {
|
||||
opacity: 0;
|
||||
background-color: color-mix(in oklch, var(--background) 45%, transparent);
|
||||
transition: opacity 200ms ease-out;
|
||||
}
|
||||
.mobile-menu[data-open="true"] .mobile-menu-bg {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* 悬浮面板:磨砂卡片,从顶部轻轻落下 */
|
||||
.mobile-menu-panel {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px) scale(0.985);
|
||||
transform-origin: top center;
|
||||
background-color: var(--glass-bg);
|
||||
border-color: var(--glass-border);
|
||||
box-shadow: 0 18px 48px -14px oklch(0 0 0 / 0.24);
|
||||
backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
|
||||
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
|
||||
transition:
|
||||
opacity 180ms ease-out,
|
||||
transform 260ms cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
.mobile-menu[data-open="true"] .mobile-menu-panel {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
/* 逐项入场 */
|
||||
.mobile-menu .mobile-nav-item {
|
||||
opacity: 0;
|
||||
transform: translateY(14px);
|
||||
transform: translateY(8px);
|
||||
transition:
|
||||
opacity 420ms cubic-bezier(0.22, 1, 0.36, 1),
|
||||
transform 420ms cubic-bezier(0.22, 1, 0.36, 1);
|
||||
opacity 300ms cubic-bezier(0.22, 1, 0.36, 1),
|
||||
transform 300ms cubic-bezier(0.22, 1, 0.36, 1),
|
||||
background-color 200ms ease-out,
|
||||
color 200ms ease-out;
|
||||
transition-delay: 0ms;
|
||||
}
|
||||
.mobile-menu[data-open="true"] .mobile-nav-item {
|
||||
@@ -139,28 +201,83 @@ const navItems: { name: string; href: string; external?: boolean }[] = [
|
||||
transition-delay: var(--stagger, 0ms);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.mobile-menu,
|
||||
.mobile-menu .mobile-nav-item {
|
||||
transition-duration: 1ms !important;
|
||||
transform: none !important;
|
||||
.mobile-nav-item:hover {
|
||||
background-color: color-mix(in oklch, var(--secondary) 70%, transparent);
|
||||
}
|
||||
.mobile-nav-item.is-current {
|
||||
background-color: color-mix(in oklch, var(--primary) 8%, transparent);
|
||||
}
|
||||
/* 键盘 Tab 进来时的焦点环,比浏览器默认那圈细一点、柔一点 */
|
||||
.mobile-nav-item:focus-visible,
|
||||
.mobile-menu-panel button:focus-visible {
|
||||
outline: 2px solid color-mix(in oklch, var(--primary) 45%, transparent);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
/* 朱墨留白的那道红色短竖线,复用正文标题的语言 */
|
||||
.mobile-nav-marker {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
width: 3px;
|
||||
height: 1rem;
|
||||
border-radius: 9999px;
|
||||
background-color: var(--primary);
|
||||
opacity: 0;
|
||||
transform: translateY(-50%) scaleY(0);
|
||||
transition:
|
||||
opacity 200ms ease-out,
|
||||
transform 200ms cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
.mobile-nav-item:hover .mobile-nav-marker,
|
||||
.mobile-nav-item.is-current .mobile-nav-marker {
|
||||
opacity: 1;
|
||||
transform: translateY(-50%) scaleY(1);
|
||||
}
|
||||
|
||||
/* 不支持 backdrop-filter 的浏览器:退回不透明卡片,别让文字糊在内容上 */
|
||||
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
|
||||
.mobile-menu-panel {
|
||||
background-color: var(--card);
|
||||
}
|
||||
}
|
||||
|
||||
/* Drop the heavy backdrop-filter on small viewports. The fixed header keeps
|
||||
blurring everything underneath while scrolling, which makes mobile compositing
|
||||
expensive. Use a slightly more opaque background instead. */
|
||||
/* 系统开启“减少透明度”时直接关掉磨砂 */
|
||||
@media (prefers-reduced-transparency: reduce) {
|
||||
.mobile-menu-panel {
|
||||
background-color: var(--card);
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.mobile-menu,
|
||||
.mobile-menu-panel,
|
||||
.mobile-menu-bg,
|
||||
.mobile-menu .mobile-nav-item,
|
||||
.mobile-nav-marker {
|
||||
transition-duration: 1ms !important;
|
||||
}
|
||||
.mobile-menu-panel,
|
||||
.mobile-menu .mobile-nav-item {
|
||||
transform: none !important;
|
||||
}
|
||||
.mobile-nav-marker,
|
||||
.mobile-nav-item.is-current .mobile-nav-marker,
|
||||
.mobile-nav-item:hover .mobile-nav-marker {
|
||||
transform: translateY(-50%) scaleY(1) !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 小屏上页眉不再常驻 backdrop-filter:固定元素持续模糊会让移动端合成开销变大,
|
||||
顶部时用实色反而更省更快。菜单面板是点开才出现的、面积也小,保留磨砂。 */
|
||||
@media (max-width: 767px) {
|
||||
.site-header {
|
||||
backdrop-filter: none !important;
|
||||
-webkit-backdrop-filter: none !important;
|
||||
background-color: var(--background) !important;
|
||||
}
|
||||
.mobile-menu-bg {
|
||||
backdrop-filter: none !important;
|
||||
-webkit-backdrop-filter: none !important;
|
||||
background-color: var(--background) !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -48,6 +48,16 @@
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
--radius: 0.5rem;
|
||||
/*
|
||||
* 磨砂玻璃 token —— 只给「浮在内容上方、面积不大」的元素用
|
||||
* (移动端菜单、页眉、搜索面板…)。不要给文章卡片大面积用,
|
||||
* 一屏十几个元素同时 backdrop-filter 会明显掉帧。
|
||||
* 由 --card 派生,所以亮/暗色自动跟随。
|
||||
*/
|
||||
--glass-bg: color-mix(in oklch, var(--card) 72%, transparent);
|
||||
--glass-border: color-mix(in oklch, var(--border) 75%, transparent);
|
||||
--glass-blur: 14px;
|
||||
--glass-saturate: 1.5;
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
--sidebar-foreground: oklch(0.145 0 0);
|
||||
--sidebar-primary: oklch(0.55 0.22 var(--primary-hue, 25));
|
||||
|
||||
Reference in New Issue
Block a user