diff --git a/BlogCard.svg b/BlogCard.svg
index c8d6ae0..515fa20 100644
--- a/BlogCard.svg
+++ b/BlogCard.svg
@@ -77,7 +77,7 @@
y="0"
class="header"
data-testid="header"
- >Mayx's Blog Article
+ >Mayx's Blog Posts
diff --git a/_layouts/default.html b/_layouts/default.html
index 3b43854..b3f6133 100644
--- a/_layouts/default.html
+++ b/_layouts/default.html
@@ -117,6 +117,7 @@ layout: xslt_container
+
diff --git a/assets/js/blog-console.js b/assets/js/blog-console.js
new file mode 100644
index 0000000..da8e373
--- /dev/null
+++ b/assets/js/blog-console.js
@@ -0,0 +1,866 @@
+/*!
+ * blog-console.js — Mayx's Blog DevTools Console API
+ */
+(function (global) {
+ 'use strict';
+
+ /* =====================================================================
+ * 0. 常量与配置
+ * ===================================================================== */
+
+ var config = {
+ /** list() 默认每页条数 */
+ pageSize: 10,
+ /** show() 单次最多渲染的行数,防止超长文章刷屏 */
+ maxShowLines: 600,
+ /** 摘要/预览截断长度 */
+ previewLength: 120
+ };
+
+ /* =====================================================================
+ * 1. 控制台样式(DevTools 用 %c + CSS)
+ * ===================================================================== */
+
+ var S = {
+ reset: '',
+ title: 'font-weight:bold;font-size:13px;color:#3fb950',
+ sub: 'color:#8b949e',
+ num: 'color:#d29922;font-weight:bold',
+ date: 'color:#58a6ff',
+ strong: 'font-weight:bold',
+ link: 'color:#58a6ff;text-decoration:underline',
+ ok: 'color:#3fb950;font-weight:bold',
+ warn: 'color:#d29922',
+ err: 'color:#f85149;font-weight:bold',
+ dim: 'color:#8b949e',
+ code: 'color:#e3b341;font-family:ui-monospace,Consolas,monospace;background:rgba(110,118,129,.18);padding:0 3px;border-radius:3px',
+ quote: 'color:#8b949e;font-style:italic',
+ hr: 'color:#6e7781',
+ tag: 'color:#a371f7',
+ h: [
+ '',
+ 'font-weight:bold;font-size:16px;color:#f85149',
+ 'font-weight:bold;font-size:15px;color:#3fb950',
+ 'font-weight:bold;font-size:14px;color:#d29922',
+ 'font-weight:bold;font-size:13px;color:#58a6ff',
+ 'font-weight:bold;font-size:13px;color:#a371f7',
+ 'font-weight:bold;font-size:13px;color:#39c5cf'
+ ]
+ };
+
+ /**
+ * 分段样式打印器。收集 [文本, CSS] 片段,分批 flush 到 console.log,
+ * 既能保证多行样式连续,又避免单条消息过长被浏览器截断。
+ */
+ function Printer(chunkSize) {
+ this.chunk = chunkSize || 160;
+ this.fmt = [];
+ this.css = [];
+ this.pending = 0;
+ }
+ /**
+ * 追加一个带样式的片段(不换行)。
+ * @param {string} text 文本内容
+ * @param {string} [css] CSS 样式串,省略则使用默认样式
+ */
+ Printer.prototype.push = function (text, css) {
+ // 转义 %,防止与 console 的格式化占位符冲突
+ this.fmt.push('%c' + String(text).replace(/%/g, '%%'));
+ this.css.push(css || '');
+ this.pending++;
+ return this;
+ };
+ /** 追加一行(自动换行),并在片段数超过阈值时 flush。 */
+ Printer.prototype.line = function (text, css) {
+ this.push((text === undefined ? '' : text) + '\n', css);
+ if (this.pending >= this.chunk) this.flush();
+ return this;
+ };
+ /** 结束当前行。 */
+ Printer.prototype.br = function () {
+ this.push('\n', '');
+ return this;
+ };
+ /** 把已累积的片段输出到控制台。 */
+ Printer.prototype.flush = function () {
+ if (!this.fmt.length) return this;
+ var msg = this.fmt.join('');
+ // 去掉行尾多余换行,避免每次 flush 产生空行
+ msg = msg.replace(/\n$/, '');
+ console.log.apply(console, [msg].concat(this.css));
+ this.fmt = [];
+ this.css = [];
+ this.pending = 0;
+ return this;
+ };
+
+ /** 打印一条分隔标题横幅。 */
+ function banner(text) {
+ var p = new Printer();
+ p.line('── ' + text + ' ' + repeat('─', Math.max(2, 46 - strWidth(text))), S.title);
+ p.flush();
+ }
+
+ function repeat(ch, n) { return n > 0 ? new Array(n + 1).join(ch) : ''; }
+
+ /** 粗略估算显示宽度(中日韩字符按 2 计)。 */
+ function strWidth(s) {
+ var w = 0;
+ for (var i = 0; i < s.length; i++) {
+ w += /[\u2E80-\uFFFF]/.test(s[i]) ? 2 : 1;
+ }
+ return w;
+ }
+
+ /** 打印错误并返回 null,统一失败出口。 */
+ function fail(msg) {
+ console.log('%c⚠️ ' + msg, S.err);
+ return null;
+ }
+
+ /* =====================================================================
+ * 2. 宿主环境依赖层
+ * ===================================================================== */
+
+ /**
+ * GitHub Issues 访问用的 Basic 凭据。
+ * @returns {{headers:object, owner:string, repo:string}}
+ */
+ function githubAuth() {
+ var g = GitalkConfig;
+ return {
+ headers: { Authorization: 'Basic ' + btoa(g.clientID + ':' + g.clientSecret) },
+ owner: g.owner,
+ repo: g.repo
+ };
+ }
+
+ /**
+ * 读取搜索索引 search.json,复用 main.js 的 getSearchJSON(带 localStorage 缓存)。
+ * @returns {Promise} search.json 原始数组
+ */
+ function loadSearchJSON() {
+ return new Promise(function (resolve) {
+ getSearchJSON(resolve);
+ });
+ }
+
+ /**
+ * 懒加载站点自带的 SimpleJekyllSearch。
+ * 该库只在 /search.html 里被引入,其他页面需要时动态注入同一个文件,
+ * 从而保证控制台搜索与页面搜索使用完全一致的匹配/排序逻辑。
+ * @returns {Promise} SimpleJekyllSearch 工厂函数
+ * @throws {Error} 脚本加载失败时 reject
+ */
+ function ensureSimpleJekyllSearch() {
+ if (typeof global.SimpleJekyllSearch === 'function') {
+ return Promise.resolve(global.SimpleJekyllSearch);
+ }
+ return new Promise(function (resolve, reject) {
+ var s = document.createElement('script');
+ s.src = '/assets/js/simple-jekyll-search.min.js';
+ s.async = true;
+ s.onload = function () {
+ if (typeof global.SimpleJekyllSearch === 'function') resolve(global.SimpleJekyllSearch);
+ };
+ s.onerror = function () {
+ reject(new Error('无法加载 SimpleJekyllSearch'));
+ };
+ document.head.appendChild(s);
+ });
+ }
+
+ /* =====================================================================
+ * 3. 通用请求工具
+ * ===================================================================== */
+
+ /**
+ * 请求 JSON,失败返回 null(不抛异常,便于控制台链式使用)。
+ * @param {string} url
+ * @param {object} [options] fetch 选项
+ * @returns {Promise}
+ */
+ function fetchJSON(url, options) {
+ return fetch(url, options || {})
+ .then(function (r) { return r.ok ? r.json() : null; })
+ .catch(function () { return null; });
+ }
+
+ /**
+ * 请求纯文本,失败返回 null。
+ * @param {string} url
+ * @param {object} [options] fetch 选项
+ * @returns {Promise}
+ */
+ function fetchText(url, options) {
+ return fetch(url, options || {})
+ .then(function (r) { return r.ok ? r.text() : null; })
+ .catch(function () { return null; });
+ }
+
+ /** 解码 HTML 实体(search.json 的 title 经过 Liquid escape 过滤器处理)。 */
+ function unescapeHTML(str) {
+ if (!str || str.indexOf('&') === -1) return str || '';
+ var el = document.createElement('textarea');
+ el.innerHTML = str;
+ return el.value;
+ }
+
+ /* =====================================================================
+ * 4. 数据层
+ * ===================================================================== */
+
+ var _articles = null;
+
+ /**
+ * 规范化 search.json 的一条记录。
+ * @param {object} item search.json 原始项
+ * @param {number} index 从 0 开始的下标
+ * @returns {{num:number,title:string,url:string,date:string,category:string,tags:string[],content:string,excerpt:string,link:string}}
+ */
+ function normalize(item, index) {
+ var content = item.content || '';
+ var tags = (item.tags || '')
+ .split(',')
+ .map(function (t) { return t.trim(); })
+ .filter(Boolean);
+ return {
+ num: index + 1,
+ title: unescapeHTML(item.title || ''),
+ url: item.url || '',
+ date: item.date || '',
+ category: item.category || '',
+ tags: tags,
+ content: content,
+ excerpt: content.slice(0, config.previewLength) +
+ (content.length > config.previewLength ? '……' : ''),
+ link: item.url || ''
+ };
+ }
+
+ /**
+ * 取得全部文章(含缓存)。
+ * 注意:search.json 由 Jekyll 生成时已排除 layout 为 encrypt 的加密文章,
+ * 因此这里的序号 num 是「非加密文章」的序号。
+ * @param {boolean} [force] 传 true 强制重新拉取
+ * @returns {Promise} 规范化后的文章数组,最新的在前
+ */
+ function getArticles(force) {
+ if (_articles && !force) return Promise.resolve(_articles);
+ return loadSearchJSON().then(function (data) {
+ _articles = (data || []).map(normalize);
+ return _articles;
+ });
+ }
+
+ /**
+ * 把各种形式的标识解析为一篇文章。
+ * @param {number|string} id 序号(1 起) / 文章 URL / 标题(支持模糊包含匹配)
+ * @returns {Promise