diff --git a/content/about.md b/content/about.md
index 6c195b7f..11bd0ca9 100644
--- a/content/about.md
+++ b/content/about.md
@@ -8,27 +8,33 @@ slug: "about"
draft: false
---
-
-
-
-
关于博主
-
Hello, World!
-
-
湖北 · 襄阳 -> 广州
-
00后,性格随和,风趣幽默
-
不活跃就是在跟生活对线~
-
工作:牛马打工人
-
-
-
-
博客构建
-
Powered by Open Source
-
- - 框架:Hugo
- - 托管:Vercel / GitHub Pages
- - 源码:GitHub(heysiu/Ying)
- - 主题:二开Ying主题(自用不开源)
-
-
-
+
+
+
+
+
02 / 这个博客 (Build)
+
+
+
+
框架Hugo · 主题 Ying
+
+
托管CNB 构建 · 又拍云 / 多吉云 / EdgeOne
+
+
评论Artalk · 自建(Cloudflare Workers)
+
+
备份CNB 主仓 + 自建 Gitea 辅仓 + 加密离线包
+
+
始于2020 年,一写六年
+
+
+
+
+Hugo
+Cloudflare
+Docker
+Nginx
+Python
+Shell
+
+
diff --git a/themes/Ying/assets/css/main.css b/themes/Ying/assets/css/main.css
index a4d79742..6c21abaa 100644
--- a/themes/Ying/assets/css/main.css
+++ b/themes/Ying/assets/css/main.css
@@ -4322,6 +4322,284 @@ input[type=submit] {
box-shadow: none !important;
}
+/* ══════════════════════════════════════════════════════════════════════════
+ 关于页 · B+C 方案(时间线 + 数据一览)
+ ──────────────────────────────────────────────────────────────────────────
+ 设计约束:只使用主题既有语言——618px 容器 / zql 衬线 / 8·12px 圆角 /
+ .section-title 大写小标题 / 黑白灰单色。不引入任何强调色。
+ 数据由 partials/about-intro.html 在构建期从 content 实时统计。
+ ★ 关于页文字必须显式左对齐 —— .main 上有 text-align:center,
+ 靠继承会让整页居中。
+
+ ── 字号阶梯(★ 全节唯一字号来源,不要再写裸 rem/px)──
+ 之前每个元素各写各的 rem,实际算出 11.2 / 11.52 / 12.48 / 13.68 /
+ 13.76 / 16.8 六七种「互不相干的小字」,视觉上就是"不统一"。
+ 现在收敛成 4 档 + 2 个特例,所有小字只有「正文 / 标签 / 脚注」三种:
+ --ab-fs-title 17px 区块内小标题(衬线)
+ --ab-fs-body 14px 正文(衬线)
+ --ab-fs-label 13px 标签 · 元信息(等宽/无衬线)
+ --ab-fs-micro 12px 脚注 · 注脚(等宽/无衬线)
+ 特例:--ab-fs-num 28px 数据大数字,--ab-fs-when 13px 时间线日期。
+ ══════════════════════════════════════════════════════════════════════════ */
+.about-page {
+ text-align: left;
+ /* 字号阶梯 */
+ --ab-fs-title: 1.0625rem; /* 17px 衬线小标题 */
+ --ab-fs-body: 0.875rem; /* 14px 正文 */
+ --ab-fs-label: 0.8125rem; /* 13px 标签/元信息 */
+ --ab-fs-micro: 0.75rem; /* 12px 脚注 */
+ --ab-fs-when: 0.8125rem; /* 13px 时间线日期 */
+ --ab-fs-num: 1.75rem; /* 28px 数据大字 */
+ /* 行高 */
+ --ab-lh-tight: 1.5;
+ --ab-lh-body: 1.85;
+}
+
+/* 区块间距统一在这里管:about-intro.html / about.html 里的分节
+ 都用 .about-sec 包裹,不要再写内联 margin-top。 */
+.about-page .about-sec { margin-top: 44px; }
+.about-page .about-sec:first-child { margin-top: 0; }
+/* 小节标题(section-title)本身已有 14px 下边距,紧邻区块时不用再加。
+ 全局 .section-title 是 0.9rem(14.4px),比正文(14px)还小一点点、比标签(13px)大,
+ 落在阶梯的缝里 —— 关于页统一压到「标签级」并加大字距,让它明显是"题头"而不是正文。 */
+.about-page .section-title {
+ font-size: var(--ab-fs-label);
+ letter-spacing: 0.18em;
+ margin-bottom: 18px;
+}
+/* 移动端收紧 */
+@media (max-width: 700px) {
+ .about-page .about-sec { margin-top: 34px; }
+}
+
+.ab-attrs { margin-top: 26px; border-top: 1px solid var(--frame-color); }
+[data-theme="dark"] .ab-attrs { border-top-color: var(--frame-dark-color); }
+.ab-attr {
+ display: flex; gap: 16px; align-items: baseline;
+ padding: 10px 0;
+ border-bottom: 1px solid var(--frame-color);
+ font-size: var(--ab-fs-body); line-height: var(--ab-lh-body);
+}
+[data-theme="dark"] .ab-attr { border-bottom-color: var(--frame-dark-color); }
+.ab-attr:last-child { border-bottom: 0; }
+.ab-attr .k {
+ flex: none; width: 64px;
+ font-size: var(--ab-fs-label); color: var(--sanji-color);
+ letter-spacing: 0.08em;
+}
+.ab-attr .v { color: var(--text-content-color); min-width: 0; }
+[data-theme="dark"] .ab-attr .v { color: var(--text-content-dark-color); }
+
+/* 技术栈标签:与主题 .skill-tag 同观感 */
+.ab-stack { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 14px; }
+.ab-tag {
+ display: inline-flex; align-items: center;
+ padding: 3px 11px; border-radius: 999px;
+ border: 1px solid rgba(148, 163, 184, 0.7);
+ background: rgba(255, 255, 255, 0.86);
+ font-family: 'JetBrains Mono', 'SF Mono', Consolas, monospace;
+ font-size: var(--ab-fs-micro); color: #374151;
+ letter-spacing: 0.01em;
+}
+[data-theme="dark"] .ab-tag {
+ border-color: rgba(31, 41, 55, 0.85);
+ background: rgba(15, 23, 42, 0.96);
+ color: #e5e7eb;
+}
+
+/* ── B 方案:时间线 ──
+ 时间线紧跟在属性列表后面,需要一点上方留白把它和属性区分开 */
+.ab-tl { position: relative; padding-left: 26px; margin-top: 30px; }
+.ab-tl::before {
+ content: ''; position: absolute;
+ left: 5px; top: 7px; bottom: 7px;
+ width: 1px; background: var(--frame-color);
+}
+[data-theme="dark"] .ab-tl::before { background: var(--frame-dark-color); }
+
+.ab-item { position: relative; padding-bottom: 26px; }
+.ab-item:last-child { padding-bottom: 0; }
+.ab-item::before {
+ content: ''; position: absolute;
+ left: -26px; top: 6px;
+ width: 11px; height: 11px; border-radius: 50%;
+ background: var(--background-color);
+ border: 1.5px solid #bec8d2;
+ box-sizing: border-box;
+}
+[data-theme="dark"] .ab-item::before {
+ background: #0f172a; border-color: #4b5563;
+}
+/* 当前节点:实心,用于标出"此刻" */
+.ab-item.is-now::before {
+ background: #111827; border-color: #111827;
+}
+[data-theme="dark"] .ab-item.is-now::before {
+ background: #e5e7eb; border-color: #e5e7eb;
+}
+.ab-when {
+ font-family: 'JetBrains Mono', 'SF Mono', Consolas, monospace;
+ font-size: var(--ab-fs-when); letter-spacing: 0.06em;
+ color: #a8b3c0;
+}
+[data-theme="dark"] .ab-when { color: #6b7280; }
+.ab-h {
+ font-family: 'zql', serif;
+ font-size: var(--ab-fs-title); line-height: var(--ab-lh-tight);
+ color: var(--text-title-color);
+ margin: 4px 0 6px;
+}
+[data-theme="dark"] .ab-h { color: var(--text-title-dark-color); }
+.ab-p {
+ font-size: var(--ab-fs-body); line-height: var(--ab-lh-body);
+ color: var(--text-content-color);
+ margin: 0;
+}
+[data-theme="dark"] .ab-p { color: var(--text-content-dark-color); }
+.ab-p em { font-style: normal; color: var(--sanji-color); }
+
+/* ── C 方案:数据一览 ──
+ 时间线结束 → 数据卡之间需要明显一档间距,否则和最后一条时间线粘在一起 */
+.ab-stats {
+ display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px;
+ margin-top: 32px;
+}
+.ab-stat {
+ text-align: center; padding: 16px 6px;
+ border: 1px solid rgba(0, 0, 0, 0.08);
+ border-radius: 12px;
+}
+[data-theme="dark"] .ab-stat { border-color: rgba(255, 255, 255, 0.08); }
+.ab-stat .n {
+ display: block;
+ font-family: 'JetBrains Mono', 'SF Mono', Consolas, monospace;
+ font-size: var(--ab-fs-num); line-height: 1.1;
+ color: var(--text-title-color);
+}
+[data-theme="dark"] .ab-stat .n { color: var(--text-title-dark-color); }
+.ab-stat .l {
+ display: block; margin-top: 7px;
+ font-size: var(--ab-fs-micro); letter-spacing: 0.1em;
+ color: var(--sanji-color);
+}
+
+.ab-bars { margin-top: 26px; }
+.ab-bar {
+ display: flex; align-items: center; gap: 12px;
+ margin-bottom: 11px; font-size: var(--ab-fs-label);
+}
+.ab-bar:last-child { margin-bottom: 0; }
+.ab-bar .n {
+ flex: none; width: 58px;
+ font-size: var(--ab-fs-label); color: var(--erji-color);
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
+}
+[data-theme="dark"] .ab-bar .n { color: #9ca3af; }
+.ab-bar .t {
+ flex: 1; height: 6px; border-radius: 99px;
+ background: var(--bankuai-color); overflow: hidden;
+}
+[data-theme="dark"] .ab-bar .t { background: rgba(31, 41, 55, 0.75); }
+.ab-bar .t i {
+ display: block; height: 100%; border-radius: 99px;
+ background: #bec8d2;
+}
+[data-theme="dark"] .ab-bar .t i { background: #6b7280; }
+.ab-bar .c {
+ flex: none; width: 34px; text-align: right;
+ font-family: 'JetBrains Mono', 'SF Mono', Consolas, monospace;
+ font-size: var(--ab-fs-micro); color: #a8b3c0;
+}
+[data-theme="dark"] .ab-bar .c { color: #6b7280; }
+.ab-footnote {
+ font-size: var(--ab-fs-micro); line-height: 1.75;
+ color: #a8b3c0;
+ margin: 16px 0 0;
+}
+[data-theme="dark"] .ab-footnote { color: #6b7280; }
+
+/* ── 关于页响应式 ── */
+@media (max-width: 700px) {
+ .ab-attr { gap: 12px; }
+ .ab-attr .k { width: 54px; }
+ .ab-stats { gap: 8px; }
+ .ab-stat { padding: 13px 4px; }
+ .ab-stat .n { font-size: 1.4rem; }
+ .ab-bar .n { width: 48px; }
+}
+
+/* ══════════════════════════════════════════════════════════════════════════
+ 关于页 · 动效层(v7:纯 UIKit 官方写法,与其他页面完全同源,零自定义)
+ ──────────────────────────────────────────────────────────────────────────
+ 全部动画只由 UIKit scrollspy 驱动(归档页/文章页同款机制):
+ · 官方动画类:uk-animation-fade(0.8s 淡入)、
+ uk-animation-slide-{bottom,right}-medium(0.5s / 50px 滑入)
+ —— 官方默认时长就在打包 CSS 里:[class*=uk-animation-]{duration:.5s},
+ fade 0.8s。★ 这里不再覆盖时长/keyframes(v6 的自定义层已删,
+ 之前注释里说的「主题 0.2s 压制」实测并不存在)。
+ · 内容组 repeat: true:移出视口重藏、滚回重播 —— 滚动时页面是活的。
+ 本文件只保留两件 UIKit 管不了的事:分类条宽度填充、减弱动效守卫。
+ ══════════════════════════════════════════════════════════════════════════ */
+
+/* ── 1) 分类条:填充宽度从 0 长出 ──
+ UIKit 只管「淡入+位移」,宽度是它不管的,用 transition 补。
+ 条体被 scrollspy 判定进视口后会带上 .uk-scrollspy-inview,以此放行到目标宽度;
+ repeat 移出视口时类被摘掉 → 宽度回到 0,下次滚进重新长出。 */
+.ab-bar .t i {
+ width: 0;
+ transition: width 0.7s cubic-bezier(0.22, 0.61, 0.36, 1) 0.15s;
+}
+.ab-bar.uk-scrollspy-inview .t i,
+.uk-scrollspy-inview .ab-bar .t i {
+ width: var(--ab-bar-w, 0%);
+}
+
+/* ── 2) 无障碍:尊重系统「减弱动态效果」──
+ scrollspy 默认 hidden:true,会用**内联** visibility:hidden 把未进视口的
+ target 藏起来(注意是打在 target 指向的子元素上)。开了「减弱动效」的用户
+ 若遇到观察器异常就会看到空白,所以这里一律放行可见 + 取消动画。
+ ★ !important 必需 —— 对手是内联样式。 */
+@media (prefers-reduced-motion: reduce) {
+ /* ★ 必须同时覆盖「带 uk-scrollspy 的元素本身」和它的后代:
+ scrollspy 把内联 visibility:hidden 打在 **target 指向的元素**上。
+ 当 target 就是元素自身(如 .section-title 自己写 cls:)时,它自己中招;
+ 当 target 是子元素(如 target: .ab-attr)时,子元素中招。
+ 只写 `[uk-scrollspy] *` 会漏掉前一种情况 → 分节标题在 reduce 下空白。
+ 两行都要有。 */
+ .about-page [uk-scrollspy],
+ .about-page [uk-scrollspy] *,
+ .about-page .uk-animation-fade,
+ .about-page .uk-animation-slide-bottom-medium,
+ .about-page .uk-animation-slide-right-medium,
+ .about-page .uk-animation-slide-top-medium,
+ .about-page .uk-animation-slide-left-medium,
+ .featc-item {
+ visibility: visible !important;
+ opacity: 1 !important;
+ transform: none !important;
+ animation: none !important;
+ }
+ .ab-bar .t i { width: var(--ab-bar-w, 0%); transition: none; }
+}
+
+/* ── 6) 数据卡悬停:极轻的边框加深,与主题其他卡片观感一致 ──
+ 只为「可点」之外的静态卡增加一点呼吸感,不加阴影跳变。 */
+.ab-stat {
+ transition: border-color 0.25s ease;
+}
+.ab-stat:hover { border-color: rgba(0, 0, 0, 0.16); }
+[data-theme="dark"] .ab-stat:hover { border-color: rgba(255, 255, 255, 0.16); }
+
+/* ── 关于页响应式 ── */
+@media (max-width: 700px) {
+ .ab-attr { gap: 12px; }
+ .ab-attr .k { width: 54px; }
+ .ab-stats { gap: 8px; }
+ .ab-stat { padding: 13px 4px; }
+ .ab-stat .n { font-size: 1.4rem; }
+ .ab-bar .n { width: 48px; }
+}
+
/* Selection Popup (Quote Button) */
#selection-popup {
position: fixed;
diff --git a/themes/Ying/assets/js/modules/mypjax.js b/themes/Ying/assets/js/modules/mypjax.js
index c08adee1..36f33397 100644
--- a/themes/Ying/assets/js/modules/mypjax.js
+++ b/themes/Ying/assets/js/modules/mypjax.js
@@ -21,6 +21,32 @@
console.log("Pjax initialized:", pjax);
})();
+// UIKit 重新扫描(PJAX 必需)
+// ─────────────────────────────────────────────────────────────
+// UIKit 只在页面加载时做一次「全量 boot」:它遍历 document.body,把带
+// uk-* 属性的元素逐个 connect 成组件。PJAX 换入的 DOM 是我们用
+// innerHTML 塞进去的,UIKit 的 MutationObserver 虽然监听了 childList,
+// 但因为「换入前属性就已存在」,observer 拿不到 attribute 的新增事件,
+// 加上 PJAX 的替换发生在 UIKit 首次 boot 之后,组件就永远绑不上。
+//
+// 症状:uk-scrollspy 的元素停在 visibility:hidden(动画类的初始态),
+// 下折叠的内容永久不可见,滚动也不会出现。
+//
+// 解法:换页后手动调一次 UIkit.update()。它会从传入节点向上走到
+// document.body,再对整棵子树递归 connect —— 正好补上这次 boot。
+function reinitUIKit(root) {
+ if (typeof window.UIkit === 'undefined' || typeof window.UIkit.update !== 'function') return;
+ var target = root || document.getElementById('pjax-container') || document.body;
+ try {
+ window.UIkit.update(target);
+ } catch (e) {
+ console.warn('UIkit.update failed:', e);
+ }
+}
+
+// 暴露给页面脚本:动态插入带 uk-* 属性的内容后,调一次即可让 UIKit 生效
+window.reinitUIKit = reinitUIKit;
+
// pjax预先加载代码
function pjax_reload() {
console.log("Pjax reload triggered");
@@ -99,6 +125,12 @@ document.addEventListener("pjax:complete", function () {
if (typeof NProgress !== 'undefined') NProgress.done();
pjax_reload();
+ // ★ 关键:pjax_reload() 里刚给评论/说说等元素新加了 uk-scrollspy 属性,
+ // 此刻连同换入的整棵新 DOM 一起交给 UIKit 重新 boot一遍。
+ // 必须在 pjax_reload() 之后调用,否则新加的属性会被漏掉。
+ // (关于页的分节动画也走 UIKit scrollspy,所以同样靠这一步复活。)
+ reinitUIKit();
+
// 动态加载页面特定JS(PJAX适配)
// 检测当前页面是否为文章详情页,如果是则加载page-only.js
var currentPath = window.location.pathname;
diff --git a/themes/Ying/layouts/page/about.html b/themes/Ying/layouts/page/about.html
index 13e22cbf..c5ee93aa 100644
--- a/themes/Ying/layouts/page/about.html
+++ b/themes/Ying/layouts/page/about.html
@@ -1,57 +1,87 @@
{{ define "main" }}
-
-
- {{ .Content }}
-
- {{ $sectionCount := 0 }}
+ {{/* 关于页分节顺序:01 关于我(partial,含属性+时间线+数据)
+ 02 这个博客(content/about.md)
+ 03+ 走心评论 / 热力图 / 技能栈 / 相册,按 hugo.toml 开关动态编号。
+ 间距统一由 .about-sec 管,别写内联 margin。
-
- {{ if site.Params.about.enablefeatured | default true }}
- {{ $sectionCount = add $sectionCount 1 }}
-
-
{{ printf "%02d" $sectionCount }} / 走心评论 (Featured)
- {{ partial "featured-comments.html" . }}
-
- {{ end }}
+ ── 动效:与归档页 archives.html 完全同源的官方写法(v7,零自定义 JS)──
+ 官方文档(uikit.cn/docs/animation):
+ 「将 .uk-animation-* 类之一添加到任何元素。当添加类时会显示动画…要在
+ 其他时间点显示动画,例如当元素进入视口时,您需要使用 JavaScript
+ 添加该类 —— 例如,使用滚动监听组件。」
-
- {{ if site.Params.about.enableheatmap }}
- {{ $sectionCount = add $sectionCount 1 }}
-
-
{{ printf "%02d" $sectionCount }} / 热力图 (Hot Map)
- {{ partial "heatmap.html" . }}
-
- {{ end }}
+ 四条要点(全部页面既有范式,本页照办):
+ ① scrollspy 挂在被包裹的容器上,一个容器托管一组元素。
+ ② target 用后代选择器(.ab-attr),不写 `>` —— 写 `>` 只匹配直接
+ 子元素,结构一变就一个都选不中(v4 动画全灭的根因)。
+ ③ 动画类一律官方:uk-animation-fade(标题淡入 0.8s)、
+ uk-animation-slide-{bottom,right}-medium(内容 50px 滑入 0.5s)。
+ ★ 不要用 -small:位移只有 10px,在这么宽的版面里根本看不出来。
+ ④ 内容组一律 repeat: true —— 移出视口重藏、滚回重播(归档页年份
+ 标题同款),页面滚动时才是「活的」;分节标题不 repeat(同归档 h3)。
-
- {{ if site.Params.about.enableskills }}
- {{ $sectionCount = add $sectionCount 1 }}
-
-
{{ printf "%02d" $sectionCount }} / 技能栈 (System Ops)
- {{ partial "skills.html" . }}
-
- {{ end }}
-
-
- {{ if site.Params.about.enablealbum }}
- {{ $sectionCount = add $sectionCount 1 }}
-
-
{{ printf "%02d" $sectionCount }} / 光影 (Album)
-
- {{ range site.Data.album }}
-
-
-

- {{ if .date }}
{{ .date }}{{ end }}
-
-
{{ .title }}
+ PJAX:换入的 DOM 由 mypjax.js 的 reinitUIKit() 重新 boot(UIKit 的
+ MutationObserver 只反应属性新增,管不了 PJAX 整树换入)。
+ 异步渲染的内容(走心评论卡片)渲染完要自己调 UIkit.update()。 */}}
+
+
+
+
+
01 / 关于我 (Timeline)
+ {{ partial "about-intro.html" . }}
+
+
+ {{ .Content }}
+
+ {{ $sectionCount := 2 }}
+
+
+ {{ if site.Params.about.enablefeatured | default true }}
+ {{ $sectionCount = add $sectionCount 1 }}
+
+
{{ printf "%02d" $sectionCount }} / 走心评论 (Featured)
+ {{ partial "featured-comments.html" . }}
+
+ {{ end }}
+
+
+ {{ if site.Params.about.enableheatmap }}
+ {{ $sectionCount = add $sectionCount 1 }}
+
+
{{ printf "%02d" $sectionCount }} / 热力图 (Hot Map)
+ {{ partial "heatmap.html" . }}
+
+ {{ end }}
+
+
+ {{ if site.Params.about.enableskills }}
+ {{ $sectionCount = add $sectionCount 1 }}
+
+
{{ printf "%02d" $sectionCount }} / 技能栈 (System Ops)
+ {{ partial "skills.html" . }}
+
+ {{ end }}
+
+
+ {{ if site.Params.about.enablealbum }}
+ {{ $sectionCount = add $sectionCount 1 }}
+
+
{{ printf "%02d" $sectionCount }} / 光影 (Album)
+
+ {{ range site.Data.album }}
+
+
+

+ {{ if .date }}
{{ .date }}{{ end }}
+
+ {{ .title }}
+
+ {{ end }}
- {{ end }}
+ {{ end }}
+
- {{ end }}
-
-
-{{ end }}
\ No newline at end of file
+{{ end }}
diff --git a/themes/Ying/layouts/partials/about-intro.html b/themes/Ying/layouts/partials/about-intro.html
new file mode 100644
index 00000000..fbeef2ac
--- /dev/null
+++ b/themes/Ying/layouts/partials/about-intro.html
@@ -0,0 +1,110 @@
+{{/* 关于页 · B+C 方案:时间线(B) + 数据一览(C)
+ ────────────────────────────────────────────────────────────────
+ 数据全部在构建期从 content/ 实时统计,不依赖任何外部接口:
+ · 篇数 / 年数 / 站龄 → .Site.RegularPages 与最早文章日期
+ · 分类占比 → .Site.Taxonomies.categories
+ · 热力图 → 由 heatmap.html 提供(本 partial 不重复渲染)
+ ★ 时间线节点写在下方 $nodes 里,改文案只动那一处。
+ ──────────────────────────────────────────────────────────────── */}}
+
+{{- $posts := where .Site.RegularPages "Type" "posts" -}}
+
+{{/* ── 统计:总篇数 ── */}}
+{{- $total := len $posts -}}
+
+{{/* ── 统计:首篇日期与年数 ──
+ 用最早的一篇(ByDate 升序后的 index 0)作为起点,算站龄。 */}}
+{{- $ordered := $posts.ByDate -}}
+{{- $first := index $ordered 0 -}}
+{{- $firstYear := $first.Date.Format "2006" -}}
+{{- $nowYear := now.Format "2006" -}}
+{{- $spanYears := sub (int $nowYear) (int $firstYear) -}}
+
+{{/* ── 统计:今年更新篇数(当年发文数) ── */}}
+{{- $thisYear := len (where $posts "Date.Year" (int $nowYear)) -}}
+
+{{/* ── 统计:分类占比(取前 5,按篇数降序) ── */}}
+{{- $cats := slice -}}
+{{- with .Site.Taxonomies.categories -}}
+ {{- range . -}}
+ {{- $cats = $cats | append (dict "name" .Page.LinkTitle "count" (len .Pages)) -}}
+ {{- end -}}
+{{- end -}}
+{{- $cats = sort $cats "count" "desc" -}}
+{{- $top := first 5 $cats -}}
+{{- $maxC := 1 -}}
+{{- with $top }}{{ $maxC = (index . 0).count }}{{ end -}}
+
+{{/* ── 时间线节点 ──
+ 想改文案/加节点,只改这个数组即可。isNow = true 的节点会画成实心圆点。 */}}
+{{- $nodes := slice
+ (dict "when" "2020 · 起点"
+ "title" "在襄阳,给博客开了个头"
+ "body" "高考完的那个夏天,怀揣着对互联网的好奇搭了这个小站。第一篇文章还留在归档里。"
+ "isNow" false)
+ (dict "when" "2021 — 2024 · 折腾"
+ "title" "换主题、磨 CSS、把 Typecho 拆了个遍"
+ "body" "那时候不懂代码,到处找教程,把好好的主题改得面目全非,但乐在其中。"
+ "isNow" false)
+ (dict "when" "2025 · 做减法"
+ "title" "从 Typecho 搬到 Hugo"
+ "body" "文章多了才明白,博客的本质是内容,不是花哨的外表。"
+ "isNow" false)
+ (dict "when" "此刻 · 在广州"
+ "title" "毕业两年,深漂广漂中"
+ "body" "不活跃就是在跟生活对线。想去南京落脚,先把手里的事做好。"
+ "isNow" true)
+-}}
+
+
+
+
坐标湖北 · 襄阳 → 广州
+
其人00 后,性格随和,风趣幽默
+
打算未来计划定居江苏南京
+
近况深漂广漂中,不活跃就是在跟生活对线
+
+
+
+
+ {{- range $nodes }}
+
+
{{ .when }}
+
{{ .title }}
+
{{ .body }}
+
+ {{- end }}
+
+
+
+
+
{{ $total }}篇文章
+
{{ $spanYears }}年积累
+
{{ $thisYear }}今年更新
+
+
+{{- if $top }}
+
+
+ {{- range $top }}
+ {{- $pct := div (mul .count 100) $maxC }}
+
+ {{ .name }}
+
+ {{ .count }}
+
+ {{- end }}
+
+
+{{- end }}
+
diff --git a/themes/Ying/layouts/partials/featured-comments.html b/themes/Ying/layouts/partials/featured-comments.html
index d7e05254..3f56d4bd 100644
--- a/themes/Ying/layouts/partials/featured-comments.html
+++ b/themes/Ying/layouts/partials/featured-comments.html
@@ -5,14 +5,14 @@
-
+
走心评论
当我看到各位在不同的时间和故事中,发出一份份走心的评论时,一份温暖之意即笼罩在我的心头。
+
@@ -106,7 +189,7 @@
var avatar = 'https://weavatar.com/avatar/' + esc(c.email_encrypted) + '?d=mp&s=120';
var nick = esc(c.nick);
html +=
- '
' +
+ '' +
'' +
'

' +
'
' + nick + '' +
@@ -117,9 +200,47 @@
'';
}
grid.innerHTML = html;
+
+ // ★ 卡片是 fetch 回来后才插进 DOM 的,UIKit 的 MutationObserver 捕捉不到
+ // (它只对「属性新增」反应,不管新插入的子树)—— 必须手动 boot 一次。
+ // 之后完全交给 scrollspy(repeat: true):
+ // · 这一节在视口内 → 同一批 target 进同一个 update 周期,
+ // delay 依次累加,自动错落浮现;
+ // · 在视口外 → 先藏住,滚到时逐条入场,滚走再滚回会重播。
+ if (typeof window.UIkit !== 'undefined' && typeof window.UIkit.update === 'function') {
+ try { window.UIkit.update(grid); } catch (e) {}
+ }
}
- function load() {
+ // 失败/超时提示:明确告知并给一次重试,比一直挂着骨架屏好
+ function renderRetry(msg) {
+ grid.innerHTML =
+ '
' + esc(msg) + '
' +
+ '
';
+ var btn = document.getElementById('featc-retry-btn');
+ if (btn) btn.addEventListener('click', function () {
+ grid.innerHTML = '
';
+ load(true);
+ });
+ }
+
+ var CACHE_KEY = 'featc_cache_v1';
+ var CACHE_TTL = 10 * 60 * 1000; // 10 分钟
+
+ function readCache() {
+ try {
+ var raw = sessionStorage.getItem(CACHE_KEY);
+ if (!raw) return null;
+ var o = JSON.parse(raw);
+ if (!o || !o.t || (Date.now() - o.t) > CACHE_TTL) return null;
+ return o.list;
+ } catch (e) { return null; }
+ }
+ function writeCache(list) {
+ try { sessionStorage.setItem(CACHE_KEY, JSON.stringify({ t: Date.now(), list: list })); } catch (e) {}
+ }
+
+ function load(force) {
// 排版预览 mock(?featmock=1):接口未部署 / 无精选数据时也能看样式
if (/[?&]featmock=1/.test(location.search)) {
render([
@@ -133,11 +254,33 @@
]);
return;
}
- fetch(API)
+
+ // 会话内缓存:先进关于页已经拉过一次,再进(含 PJAX 来回)直接命中,
+ // 不用再等那 1 秒多。TTL 10 分钟,兼顾新鲜度。
+ if (!force) {
+ var cached = readCache();
+ if (cached) { render(cached); return; }
+ }
+
+ // ★ 超时兜底:接口实测 1–1.5s,但偶有长尾。
+ // 8s 还没回就不再干等,切成「重试」提示 —— 比无限挂骨架屏诚实。
+ var ctrl = ('AbortController' in window) ? new AbortController() : null;
+ var timer = setTimeout(function () { if (ctrl) ctrl.abort(); }, 8000);
+
+ fetch(API, ctrl ? { signal: ctrl.signal } : undefined)
.then(function (r) { return r.ok ? r.json() : Promise.reject(r.status); })
- .then(function (data) { render((data && data.comments) || []); })
- .catch(function () {
- if (grid) grid.innerHTML = '
还没有走心评论,去和博主聊聊吧~
';
+ .then(function (data) {
+ clearTimeout(timer);
+ var list = (data && data.comments) || [];
+ writeCache(list);
+ render(list);
+ })
+ .catch(function (err) {
+ clearTimeout(timer);
+ // 有缓存就先用缓存顶(宁可显示旧数据也别显示错误)
+ var stale = readCache();
+ if (stale) { render(stale); return; }
+ renderRetry('评论暂时没加载出来');
});
}
diff --git a/themes/Ying/layouts/partials/heatmap.html b/themes/Ying/layouts/partials/heatmap.html
index ae89ebe8..979ad7ee 100644
--- a/themes/Ying/layouts/partials/heatmap.html
+++ b/themes/Ying/layouts/partials/heatmap.html
@@ -11,9 +11,14 @@
}
-