参考 innei.in 的文章页(它本身也用 yohaku 命名,把它的 CSS 拆出来对了值) 题头居中 - 元信息行 justify-content:center + tabular-nums(日期/字数等宽,换行不左右跳), 标题 text-align:center + text-wrap:balance(防居中标题折出「最后一行只落一两个字」); 正文仍回到左对齐,和参考一致 - 这几条都改成 > 直接子元素:原来的 .paper h1 会连正文里的 h1 一起命中 (有的文章正文就是从 `# 标题` 开始的),把正文标题也居中了——手机截图上一眼看到 正文 - 段距 0 0 1.35em -> 上下各 1.5em(参考 .v10dui0 .rich-paragraph),首末元素吃掉外边距 - 补设计稿漏掉的 h1 / h3 / ol / img / figcaption(h1 之前是浏览器默认 2em+.67em 外边距、 h3 是 1.17em,在 16.5px/1.95 的正文里很突兀;插图之前顶满整列且和文字紧贴) - 链接改用 text-decoration(1px、underline-offset 2px、hover 加粗 2px)替掉 border-bottom - 分隔线收成 60px 居中细线;.prose 加 text-autospace:normal(中英混排自动字距) 把 classic 的 .prose 挡回去 - styles.css 那段 .prose 不带 data-ui 前缀,vivid 也照样命中;我们的规则特指度更高, 但只覆盖自己声明过的属性,没声明的就漏过来。量出来并逐条认领: a 的 border-bottom(和新下划线叠成双线)、hr 的 height+background(细线被垫成 2px)、 h3::after(每条 h3 下方多一条 28×2 强调色横杠)、ul/ol 的 1.1em 下边距、 blockquote 末段 p、pre 的 1px 边框、h4(用 classic 的 16.5px 不成层级) - 首字下沉也在这批里:设计稿没有,是 classic 漏过来的固定 52px,参考站余白模式也有, 于是认领成自己的 2.4em(跟着正文字号缩放),短文按 classic 的分法关掉 顶栏宽度 - 撤掉上一版按 /post/* 放宽到 1340 的 .top-in.wide:从首页点进文章时导航会跳一下。 顶栏全站固定 1100,品牌与主题键在首页和文章页落在同一 x(实测 199 / 1207) 纸面与栅格收紧 - .paper padding 44/48/52 -> 30/24/40;--v-measure 680 -> 728;中列仍 776 = 728 + 24×2 (这三个数是一组,只改内边距会让正文在列里偏左:实测离左栏 83px、离右栏 131px) - .article-layout max-width 1340 -> 1272、padding 36/28/60 -> 24/24/44、gap 32 -> 24。 上限必须正好等于「三栏 + 两个 gap + 左右内边距」,声明的 gap 才是渲染出来的 gap: 180+776+220+24×2 = 1224 内容宽,+48 = 1272。否则 space-between 把富余塞回列间隙, 数字改小也不动(实测只改 pad/gap 数字,正文到侧栏反而 78 -> 86) - 手机 .article-layout padding 24/20/48 -> 20/20/40 实测(用列的实际位置反推,不信任声明值;正文宽度用 offsetWidth,.paper 有入场动画的 scale) - 1456:正文到侧栏 78 -> 48,正文 728 不变,布局 1340 -> 1272 - 1280:正文 697 -> 721;1200:617 -> 641;三档实际列间隙都等于声明的 24 - 公网 138.2.17.73:8080 产物 md5 与磁盘一致;npm run build 无警告
154 lines
5.2 KiB
Vue
154 lines
5.2 KiB
Vue
<script setup>
|
||
import { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||
import { RouterLink, useRoute } from 'vue-router'
|
||
import { site } from '../../site'
|
||
|
||
// 余白顶栏。对应设计稿的 <header class="top">:品牌 / 导航 / 操作三块,
|
||
// 滚动过 8px 加 .scrolled(设计稿的「四态里的第一态」)。
|
||
// 样式在 ui/yohaku/chrome.css,选择器已收敛到 html[data-ui="vivid"] 下。
|
||
//
|
||
// 窄屏例外:设计稿在窄屏把导航整个 display:none 掉了,手机上会没有任何导航入口。
|
||
// 这里补一个 ☰ + 全屏菜单(chrome.css 的 .menu / .menu-btn)。
|
||
|
||
const route = useRoute()
|
||
const scrolled = ref(false)
|
||
const menuOpen = ref(false)
|
||
const burger = ref(null)
|
||
const menuEl = ref(null)
|
||
|
||
const links = [
|
||
{ to: '/', label: '时间线', en: 'Timeline' },
|
||
{ to: '/archive', label: '归档', en: 'Archive' },
|
||
{ to: '/tags', label: '标签', en: 'Tags' },
|
||
{ to: '/projects', label: '作品', en: 'Projects' },
|
||
{ to: '/about', label: '关于', en: 'About' }
|
||
]
|
||
|
||
const initial = () => (site.author_name || site.site_title || 'O').trim().slice(0, 1).toUpperCase()
|
||
const title = () => site.site_title?.split(/[·|]/)[0]?.trim() || 'ONE'
|
||
|
||
function isOn(l) {
|
||
return l.to === '/' ? route.path === '/' : route.path.startsWith(l.to)
|
||
}
|
||
|
||
function onScroll() {
|
||
scrolled.value = (window.scrollY || 0) > 8
|
||
}
|
||
|
||
function openMenu() {
|
||
menuOpen.value = true
|
||
// 焦点交给对话框本身(aria-modal 承诺过),Tab 第一下才落到「时间线」。
|
||
// 不直接 focus 第一个链接:即使 touch 上不长出焦点环,也会让首项看起来「被选中」,
|
||
// 和 .on 的高亮抢意思。preventScroll 防止 iOS 顺手滚背景。
|
||
nextTick(() => menuEl.value?.focus({ preventScroll: true }))
|
||
}
|
||
function closeMenu() {
|
||
menuOpen.value = false
|
||
nextTick(() => burger.value?.focus({ preventScroll: true }))
|
||
}
|
||
|
||
// 菜单铺满全屏时锁住底层滚动(iOS 上不锁背景会跟着手指滑)
|
||
watch(menuOpen, (v) => { document.body.style.overflow = v ? 'hidden' : '' })
|
||
// 点了链接 → 路由变了 → 菜单收起
|
||
watch(() => route.fullPath, () => { menuOpen.value = false })
|
||
|
||
function onKey(e) {
|
||
if (e.key === 'Escape') closeMenu()
|
||
}
|
||
|
||
// 窗口拉宽回桌面必须自己退场:v-if 是 JS 状态,CSS 媒体查询管不着它
|
||
const mqWide = window.matchMedia('(min-width: 721px)')
|
||
function onWide(e) { if (e.matches) menuOpen.value = false }
|
||
|
||
onMounted(() => {
|
||
window.addEventListener('scroll', onScroll, { passive: true })
|
||
window.addEventListener('keydown', onKey)
|
||
mqWide.addEventListener('change', onWide)
|
||
onScroll()
|
||
})
|
||
onBeforeUnmount(() => {
|
||
window.removeEventListener('scroll', onScroll)
|
||
window.removeEventListener('keydown', onKey)
|
||
mqWide.removeEventListener('change', onWide)
|
||
document.body.style.overflow = ''
|
||
menuOpen.value = false
|
||
})
|
||
|
||
// 设计稿里主题键是 ◐ / ◑:亮色时显示 ◐(点它去暗),暗色时显示 ◑。
|
||
function toggleTheme() {
|
||
// site.themeMode 是站点级偏好(auto / light / dark),site.js 里有 watcher 负责落盘与写 data-theme。
|
||
// 这里直接改它,不自己碰 document : 单一数据源。
|
||
const dark = document.documentElement.getAttribute('data-theme') === 'vivid-dark'
|
||
site.themeMode = dark ? 'light' : 'dark'
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<header class="top" :class="{ scrolled }">
|
||
<div class="top-in">
|
||
<RouterLink class="brand" to="/">
|
||
<span class="av">{{ initial() }}</span>
|
||
<span>{{ title() }}</span>
|
||
</RouterLink>
|
||
<nav class="nav" aria-label="站点导航">
|
||
<RouterLink
|
||
v-for="l in links"
|
||
:key="l.to"
|
||
:to="l.to"
|
||
:class="{ on: isOn(l) }"
|
||
>{{ l.label }}</RouterLink>
|
||
</nav>
|
||
<div class="top-acts">
|
||
<button class="icon-btn theme-btn" type="button" title="切换明暗" aria-label="切换明暗" @click="toggleTheme">◐</button>
|
||
<button
|
||
ref="burger"
|
||
class="icon-btn menu-btn"
|
||
type="button"
|
||
aria-label="打开菜单"
|
||
aria-controls="v-menu"
|
||
:aria-expanded="menuOpen"
|
||
@click="openMenu"
|
||
>☰</button>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- 窄屏全屏菜单。必须待在 .top 外面:.top 有 backdrop-filter,
|
||
会给 position:fixed 的后代当包含块,铺不满屏。 -->
|
||
<Transition name="v-menu">
|
||
<div
|
||
v-if="menuOpen"
|
||
id="v-menu"
|
||
ref="menuEl"
|
||
class="menu"
|
||
role="dialog"
|
||
aria-modal="true"
|
||
aria-label="站点导航"
|
||
tabindex="-1"
|
||
@click.self="closeMenu"
|
||
>
|
||
<div class="menu-head">
|
||
<span class="m-brand">
|
||
<span class="av">{{ initial() }}</span>
|
||
<span>{{ title() }}</span>
|
||
</span>
|
||
<button class="icon-btn" type="button" aria-label="关闭菜单" @click="closeMenu">✕</button>
|
||
</div>
|
||
<nav class="menu-in">
|
||
<RouterLink
|
||
v-for="l in links"
|
||
:key="l.to"
|
||
:to="l.to"
|
||
:class="{ on: isOn(l) }"
|
||
>
|
||
<span>{{ l.label }}</span>
|
||
<em>{{ l.en }}</em>
|
||
</RouterLink>
|
||
</nav>
|
||
<div class="menu-foot">
|
||
<button class="chip" type="button" @click="toggleTheme">◐ 切换明暗</button>
|
||
</div>
|
||
</div>
|
||
</Transition>
|
||
</template>
|