基于 moonlit-mountain 二开的 Halo 博客主题「敬山月」,这段时间陆陆续续改了不少细节,记一笔都动了什么。
开屏动画主题色适配
原版开屏动画所有颜色硬编码——纯黑背景、白色圆环、固定蓝金配色,切到月白浅色主题时整个开场就是一坨黑。改成全部走主题变量:背景用 `var(--page)`,两个圆环用 `rgb(var(--accent-a-rgb) / 0.4)` 和 `rgb(var(--accent-b-rgb) / 0.35)`,图标和文字分别取 `var(--accent-a)`、`var(--text)`、`var(--muted)`。深夜是深色开场,月白是浅色开场,自动跟着月相走。
```css
.intro-overlay {
background: var(--page);
}
.intro-ring--a {
border-color: rgb(var(--accent-a-rgb) / 0.4);
}
.intro-ring--b {
border-color: rgb(var(--accent-b-rgb) / 0.35);
}
.intro-icon {
color: var(--accent-a);
}
.intro-title {
color: var(--text);
}
.intro-sub {
color: var(--muted);
}
```同时加了 `sessionStorage` 判断,同一标签页首次进入播一次,刷新不再重复放,避免每次 F5 都被动画打断阅读。
上一篇 / 下一篇卡片封面图
原版上下篇导航就是两块纯色面板,跟文章本身完全脱节。改成有封面图的文章自动把封面当背景,叠一层 135° 深色渐变遮罩保证文字可读。默认状态封面灰度处理,鼠标悬停恢复彩色,卡片上浮 2px,边框变月相金。
Thymeleaf 模板里动态拼背景图:
```html
<nav class="post-nav" th:with="cursor = ${postFinder.cursor(post.metadata.name)}">
<a th:if="${cursor.hasPrevious()}"
th:href="@{${cursor.previous.status.permalink}}"
th:style="${!#strings.isEmpty(cursor.previous.spec.cover)
? 'background-image: url(' + cursor.previous.spec.cover + ')' : ''}">
<span th:text="#{page.post.previousPost}">上一篇</span>
<strong th:text="${cursor.previous.spec.title}"></strong>
</a>
</nav>
```用属性选择器区分有无封面图,没封面的文章保持原来的简洁面板样式,不套遮罩也不灰度:
```css
.post-nav a {
position: relative;
overflow: hidden;
border-radius: 16px;
background-size: cover;
border: 1px solid rgb(var(--surface-rgb) / 15%);
}
.post-nav a[style*="background-image"] {
filter: grayscale(1);
}
.post-nav a[style*="background-image"]:hover {
filter: grayscale(0);
}
.post-nav a[style*="background-image"]::before {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(135deg,
rgb(0 0 0 / 70%) 0%, rgb(0 0 0 / 50%) 60%, rgb(0 0 0 / 35%) 100%);
}
```继续阅读卡片高光边框
继续阅读"区域的文章卡片原来 hover 只有一条底部细线,太素了。改成鼠标跟随的渐变高光边框——鼠标移到哪,边框上就有一团月相金到蓝的光跟着走。实现思路是卡片上挂两个 CSS 变量 `--mouse-x` / `--mouse-y`,JS 监听 mousemove 更新位置,CSS 用径向渐变画在边框上。
```js
document.addEventListener("mousemove", (e) => {
const card = e.target.closest(".recent-card");
if (!card) return;
const rect = card.getBoundingClientRect();
card.style.setProperty("--mouse-x", `${e.clientX - rect.left}px`);
card.style.setProperty("--mouse-y", `${e.clientY - rect.top}px`);
}, { passive: true });
``````css
.recent-card { border: 1.5px solid transparent; }
.recent-card::before {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
padding: 1.5px;
background: radial-gradient(
220px circle at var(--mouse-x, 50%) var(--mouse-y, 50%),
rgb(var(--accent-a-rgb) / 0.9),
rgb(var(--accent-b-rgb) / 0.5) 40%,
transparent 70%
);
-webkit-mask: linear-gradient(#fff 0 0) content-box,
linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask: linear-gradient(#fff 0 0) content-box,
linear-gradient(#fff 0 0);
mask-composite: exclude;
opacity: 0;
transition: opacity 300ms ease;
}
.recent-card:hover::before { opacity: 1; }
```关键是用 `mask-composite: exclude` 把渐变裁成只有边框那 1.5px 可见,中间实心背景不被光效穿透。
其他零碎
导航栏滚下去自动隐藏、往上滚恢复显示;关于我面板用文章封面淡化做背景、渐变遮罩边角对齐修复;社交图标 Iconify URL 斜杠兼容修复。搜索弹窗是 Halo 插件的 Shadow DOM,外部 CSS 穿不透,试过注入 style 也没生效,最后放弃保持原生。
版本停在 1.0.1,后面有新想法再继续磨。