Files
ONE/frontend/src/ui/yohaku/YohakuTop.vue
T
Sakurasan 8fd8674910 余白:文章详情按 innei.in 重排题头与正文 + 顶栏宽度统一 + 纸面留白收紧
参考 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 无警告
2026-09-24 01:03:59 +08:00

154 lines
5.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>