[{"data":1,"prerenderedAt":1711},["ShallowReactive",2],{"post-\u002Fposts\u002Fnova-theme-implementation":3,"all-posts-nav":1433},{"id":4,"title":5,"body":6,"categories":1419,"date":1421,"description":12,"draft":1422,"extension":1423,"hidden":1422,"meta":1424,"navigation":420,"path":1425,"published":1422,"seo":1426,"stem":1427,"tags":1428,"__hash__":1432},"posts\u002Fposts\u002Fnova-theme-implementation.md","我是怎么从零实现 Nova 主题的",{"type":7,"value":8,"toc":1409},"minimark",[9,13,17,20,31,42,119,122,125,139,149,155,164,200,208,211,216,222,225,346,349,352,363,497,500,546,553,557,560,571,793,799,806,900,904,915,922,1141,1152,1155,1166,1346,1349,1352,1355,1395,1406],[10,11,12],"p",{},"这篇文章记录 Nova 主题的实现思路。Nova 是我为这个博客定制的 Hexo 主题，目标是把学术个人主页和博客合二为一——进来先看到个人介绍，再从里面跳转到博客内容。",[14,15,16],"h2",{"id":16},"整体架构",[10,18,19],{},"Hexo 的工作流程很简单：",[21,22,27],"pre",{"className":23,"code":25,"language":26},[24],"language-text","Markdown 文章 → 渲染器 → layout 模板 → 静态 HTML\n","text",[28,29,25],"code",{"__ignoreMap":30},"",[10,32,33,34,37,38,41],{},"每个页面对应一个 layout，Hexo 按页面类型在 ",[28,35,36],{},"layout\u002F"," 目录下找对应的 ",[28,39,40],{},".ejs"," 文件。Nova 主题的 layout 分工如下：",[43,44,45,58],"table",{},[46,47,48],"thead",{},[49,50,51,55],"tr",{},[52,53,54],"th",{},"文件",[52,56,57],{},"负责的页面",[59,60,61,76,86,99,109],"tbody",{},[49,62,63,69],{},[64,65,66],"td",{},[28,67,68],{},"layout.ejs",[64,70,71,72,75],{},"所有页面的外壳（",[28,73,74],{},"\u003Chtml>\u002F\u003Chead>\u002F\u003Cbody>","）",[49,77,78,83],{},[64,79,80],{},[28,81,82],{},"index.ejs",[64,84,85],{},"首页学术主页",[49,87,88,93],{},[64,89,90],{},[28,91,92],{},"archive.ejs",[64,94,95,98],{},[28,96,97],{},"\u002Fblog\u002F"," 博客列表",[49,100,101,106],{},[64,102,103],{},[28,104,105],{},"post.ejs",[64,107,108],{},"文章详情页",[49,110,111,116],{},[64,112,113],{},[28,114,115],{},"page.ejs",[64,117,118],{},"关于、友链等静态页",[14,120,121],{"id":121},"首页的路由问题",[10,123,124],{},"这是整个主题里最绕的地方。",[10,126,127,128,131,132,135,136,138],{},"Hexo 有一个内置的 ",[28,129,130],{},"index_generator"," 插件，它会把所有文章聚合，生成 ",[28,133,134],{},"index.html","，用的是 ",[28,137,82],{},"。默认行为是渲染文章列表——但我们想要的首页是学术主页，不是文章列表。",[10,140,141,142,148],{},"解决方案是：",[143,144,145,147],"strong",{},[28,146,82],{}," 直接写学术主页的完整 HTML，完全绕过文章列表逻辑","。",[21,150,153],{"className":151,"code":152,"language":26},[24],"\u002F          → index.ejs       → 学术主页（两栏布局）\n\u002Fblog\u002F     → archive.ejs     → 博客卡片列表\n\u002Fposts\u002Fxxx → post.ejs        → 文章详情\n",[28,154,152],{"__ignoreMap":30},[10,156,157,159,160,163],{},[28,158,97],{}," 对应的是 ",[28,161,162],{},"source\u002Fblog\u002Findex.md","，里面只有一行 front-matter：",[21,165,169],{"className":166,"code":167,"language":168,"meta":30,"style":30},"language-yaml shiki shiki-themes github-dark-dimmed github-light","---\nlayout: archive\n---\n","yaml",[28,170,171,180,195],{"__ignoreMap":30},[172,173,176],"span",{"class":174,"line":175},"line",1,[172,177,179],{"class":178},"sr-dd","---\n",[172,181,183,187,191],{"class":174,"line":182},2,[172,184,186],{"class":185},"shb1k","layout",[172,188,190],{"class":189},"ssh_m",": ",[172,192,194],{"class":193},"sXfbr","archive\n",[172,196,198],{"class":174,"line":197},3,[172,199,179],{"class":178},[10,201,202,203,205,206,148],{},"这样 Hexo 就会用 ",[28,204,92],{}," 渲染它，而不是默认的 ",[28,207,82],{},[14,209,210],{"id":210},"两栏学术布局",[10,212,213,215],{},[28,214,82],{}," 的 HTML 结构：",[21,217,220],{"className":218,"code":219,"language":26},[24],".academic-wrapper  (CSS Grid: 280px + 1fr)\n├── .academic-sidebar  (position: sticky)\n│   └── 头像、姓名、机构、社交链接\n└── .academic-content  (右侧滚动区)\n    ├── Biography\n    ├── 技术栈\n    ├── 教育经历\n    └── 最新文章\n",[28,221,219],{"__ignoreMap":30},[10,223,224],{},"CSS 核心只有两行：",[21,226,230],{"className":227,"code":228,"language":229,"meta":30,"style":30},"language-css shiki shiki-themes github-dark-dimmed github-light",".academic-wrapper {\n  display: grid;\n  grid-template-columns: 280px 1fr;\n}\n.profile-sticky {\n  position: sticky;\n  top: calc(var(--nav-height) + 2rem);\n}\n","css",[28,231,232,240,254,276,282,290,303,341],{"__ignoreMap":30},[172,233,234,237],{"class":174,"line":175},[172,235,236],{"class":178},".academic-wrapper",[172,238,239],{"class":189}," {\n",[172,241,242,246,248,251],{"class":174,"line":182},[172,243,245],{"class":244},"swcJU","  display",[172,247,190],{"class":189},[172,249,250],{"class":244},"grid",[172,252,253],{"class":189},";\n",[172,255,256,259,261,264,268,271,274],{"class":174,"line":197},[172,257,258],{"class":244},"  grid-template-columns",[172,260,190],{"class":189},[172,262,263],{"class":244},"280",[172,265,267],{"class":266},"s6PUj","px",[172,269,270],{"class":244}," 1",[172,272,273],{"class":266},"fr",[172,275,253],{"class":189},[172,277,279],{"class":174,"line":278},4,[172,280,281],{"class":189},"}\n",[172,283,285,288],{"class":174,"line":284},5,[172,286,287],{"class":178},".profile-sticky",[172,289,239],{"class":189},[172,291,293,296,298,301],{"class":174,"line":292},6,[172,294,295],{"class":244},"  position",[172,297,190],{"class":189},[172,299,300],{"class":244},"sticky",[172,302,253],{"class":189},[172,304,306,309,311,314,317,320,322,326,329,332,335,338],{"class":174,"line":305},7,[172,307,308],{"class":244},"  top",[172,310,190],{"class":189},[172,312,313],{"class":244},"calc",[172,315,316],{"class":189},"(",[172,318,319],{"class":244},"var",[172,321,316],{"class":189},[172,323,325],{"class":324},"sNjOc","--nav-height",[172,327,328],{"class":189},") ",[172,330,331],{"class":266},"+",[172,333,334],{"class":244}," 2",[172,336,337],{"class":266},"rem",[172,339,340],{"class":189},");\n",[172,342,344],{"class":174,"line":343},8,[172,345,281],{"class":189},[10,347,348],{},"左栏固定不动，右栏随页面滚动。响应式处理也很简单，小屏幕下把 grid 改成单列就行。",[14,350,351],{"id":351},"数据配置",[10,353,354,355,358,359,362],{},"所有个人信息存在 ",[28,356,357],{},"themes\u002Fnova\u002F_config.yml","，在 EJS 里通过 ",[28,360,361],{},"theme.*"," 访问：",[21,364,366],{"className":166,"code":365,"language":168,"meta":30,"style":30},"profile:\n  name: Gu EnHao\n  name_cn: 古恩豪\n  avatar: \u002Fimages\u002Favatar.jpg\n  bio: 新疆大学软件工程在读...\n\neducation:\n  - school: 西湖大学\n    school_en: Westlake University\n    school_logo: \u002Fimages\u002Flogos\u002Fwestlake.png\n    period: 2025.12 – 2026.03\n    degree: 访问学生\n    dept: 自然语言处理实验室\n",[28,367,368,376,386,396,406,416,422,429,442,453,464,475,486],{"__ignoreMap":30},[172,369,370,373],{"class":174,"line":175},[172,371,372],{"class":185},"profile",[172,374,375],{"class":189},":\n",[172,377,378,381,383],{"class":174,"line":182},[172,379,380],{"class":185},"  name",[172,382,190],{"class":189},[172,384,385],{"class":193},"Gu EnHao\n",[172,387,388,391,393],{"class":174,"line":197},[172,389,390],{"class":185},"  name_cn",[172,392,190],{"class":189},[172,394,395],{"class":193},"古恩豪\n",[172,397,398,401,403],{"class":174,"line":278},[172,399,400],{"class":185},"  avatar",[172,402,190],{"class":189},[172,404,405],{"class":193},"\u002Fimages\u002Favatar.jpg\n",[172,407,408,411,413],{"class":174,"line":284},[172,409,410],{"class":185},"  bio",[172,412,190],{"class":189},[172,414,415],{"class":193},"新疆大学软件工程在读...\n",[172,417,418],{"class":174,"line":292},[172,419,421],{"emptyLinePlaceholder":420},true,"\n",[172,423,424,427],{"class":174,"line":305},[172,425,426],{"class":185},"education",[172,428,375],{"class":189},[172,430,431,434,437,439],{"class":174,"line":343},[172,432,433],{"class":189},"  - ",[172,435,436],{"class":185},"school",[172,438,190],{"class":189},[172,440,441],{"class":193},"西湖大学\n",[172,443,445,448,450],{"class":174,"line":444},9,[172,446,447],{"class":185},"    school_en",[172,449,190],{"class":189},[172,451,452],{"class":193},"Westlake University\n",[172,454,456,459,461],{"class":174,"line":455},10,[172,457,458],{"class":185},"    school_logo",[172,460,190],{"class":189},[172,462,463],{"class":193},"\u002Fimages\u002Flogos\u002Fwestlake.png\n",[172,465,467,470,472],{"class":174,"line":466},11,[172,468,469],{"class":185},"    period",[172,471,190],{"class":189},[172,473,474],{"class":193},"2025.12 – 2026.03\n",[172,476,478,481,483],{"class":174,"line":477},12,[172,479,480],{"class":185},"    degree",[172,482,190],{"class":189},[172,484,485],{"class":193},"访问学生\n",[172,487,489,492,494],{"class":174,"line":488},13,[172,490,491],{"class":185},"    dept",[172,493,190],{"class":189},[172,495,496],{"class":193},"自然语言处理实验室\n",[10,498,499],{},"模板里这样用：",[21,501,505],{"className":502,"code":503,"language":504,"meta":30,"style":30},"language-ejs shiki shiki-themes github-dark-dimmed github-light","\u003Ch1>\u003C%= theme.profile.name %>\u003C\u002Fh1>\n\n\u003C% theme.education.forEach(edu => { %>\n\u003Cdiv class=\"ac-edu-item\">\n  \u003Cimg src=\"\u003C%- url_for(edu.school_logo) %>\" alt=\"\u003C%= edu.school %>\">\n  \u003Cspan>\u003C%= edu.school %>\u003C\u002Fspan>\n\u003C\u002Fdiv>\n\u003C% }) %>\n","ejs",[28,506,507,512,516,521,526,531,536,541],{"__ignoreMap":30},[172,508,509],{"class":174,"line":175},[172,510,511],{},"\u003Ch1>\u003C%= theme.profile.name %>\u003C\u002Fh1>\n",[172,513,514],{"class":174,"line":182},[172,515,421],{"emptyLinePlaceholder":420},[172,517,518],{"class":174,"line":197},[172,519,520],{},"\u003C% theme.education.forEach(edu => { %>\n",[172,522,523],{"class":174,"line":278},[172,524,525],{},"\u003Cdiv class=\"ac-edu-item\">\n",[172,527,528],{"class":174,"line":284},[172,529,530],{},"  \u003Cimg src=\"\u003C%- url_for(edu.school_logo) %>\" alt=\"\u003C%= edu.school %>\">\n",[172,532,533],{"class":174,"line":292},[172,534,535],{},"  \u003Cspan>\u003C%= edu.school %>\u003C\u002Fspan>\n",[172,537,538],{"class":174,"line":305},[172,539,540],{},"\u003C\u002Fdiv>\n",[172,542,543],{"class":174,"line":343},[172,544,545],{},"\u003C% }) %>\n",[10,547,548,549,552],{},"文章数据来自 ",[28,550,551],{},"site.posts","（Hexo 全局对象），不需要手动配置。",[14,554,556],{"id":555},"暗色模式无闪烁","暗色模式（无闪烁）",[10,558,559],{},"这是个经典的 FOUC（Flash of Unstyled Content）问题。如果在 JS 里切换主题，页面加载时会先白屏再变暗，体验很差。",[10,561,562,563,566,567,570],{},"解决方案是在 ",[28,564,565],{},"\u003Chead>"," 里放一段",[143,568,569],{},"同步执行","的内联脚本，在浏览器渲染任何内容之前就把主题设好：",[21,572,576],{"className":573,"code":574,"language":575,"meta":30,"style":30},"language-html shiki shiki-themes github-dark-dimmed github-light","\u003Cscript>\n(function(){\n  var t = localStorage.getItem('nova-theme');\n  if (t) document.documentElement.setAttribute('data-theme', t);\n  \u002F\u002F 禁用所有 transition，防止初始化时的动画闪烁\n  document.documentElement.classList.add('no-transition');\n  window.addEventListener('DOMContentLoaded', function(){\n    requestAnimationFrame(function(){\n      requestAnimationFrame(function(){\n        document.documentElement.classList.remove('no-transition');\n      });\n    });\n  });\n})();\n\u003C\u002Fscript>\n\u003Cstyle>.no-transition, .no-transition * { transition: none !important; }\u003C\u002Fstyle>\n","html",[28,577,578,589,599,624,643,649,664,684,695,706,720,725,730,735,741,751],{"__ignoreMap":30},[172,579,580,583,586],{"class":174,"line":175},[172,581,582],{"class":189},"\u003C",[172,584,585],{"class":185},"script",[172,587,588],{"class":189},">\n",[172,590,591,593,596],{"class":174,"line":182},[172,592,316],{"class":189},[172,594,595],{"class":266},"function",[172,597,598],{"class":189},"(){\n",[172,600,601,604,607,610,613,617,619,622],{"class":174,"line":197},[172,602,603],{"class":266},"  var",[172,605,606],{"class":189}," t ",[172,608,609],{"class":266},"=",[172,611,612],{"class":189}," localStorage.",[172,614,616],{"class":615},"saVmf","getItem",[172,618,316],{"class":189},[172,620,621],{"class":193},"'nova-theme'",[172,623,340],{"class":189},[172,625,626,629,632,635,637,640],{"class":174,"line":278},[172,627,628],{"class":266},"  if",[172,630,631],{"class":189}," (t) document.documentElement.",[172,633,634],{"class":615},"setAttribute",[172,636,316],{"class":189},[172,638,639],{"class":193},"'data-theme'",[172,641,642],{"class":189},", t);\n",[172,644,645],{"class":174,"line":284},[172,646,648],{"class":647},"sgHix","  \u002F\u002F 禁用所有 transition，防止初始化时的动画闪烁\n",[172,650,651,654,657,659,662],{"class":174,"line":292},[172,652,653],{"class":189},"  document.documentElement.classList.",[172,655,656],{"class":615},"add",[172,658,316],{"class":189},[172,660,661],{"class":193},"'no-transition'",[172,663,340],{"class":189},[172,665,666,669,672,674,677,680,682],{"class":174,"line":305},[172,667,668],{"class":189},"  window.",[172,670,671],{"class":615},"addEventListener",[172,673,316],{"class":189},[172,675,676],{"class":193},"'DOMContentLoaded'",[172,678,679],{"class":189},", ",[172,681,595],{"class":266},[172,683,598],{"class":189},[172,685,686,689,691,693],{"class":174,"line":343},[172,687,688],{"class":615},"    requestAnimationFrame",[172,690,316],{"class":189},[172,692,595],{"class":266},[172,694,598],{"class":189},[172,696,697,700,702,704],{"class":174,"line":444},[172,698,699],{"class":615},"      requestAnimationFrame",[172,701,316],{"class":189},[172,703,595],{"class":266},[172,705,598],{"class":189},[172,707,708,711,714,716,718],{"class":174,"line":455},[172,709,710],{"class":189},"        document.documentElement.classList.",[172,712,713],{"class":615},"remove",[172,715,316],{"class":189},[172,717,661],{"class":193},[172,719,340],{"class":189},[172,721,722],{"class":174,"line":466},[172,723,724],{"class":189},"      });\n",[172,726,727],{"class":174,"line":477},[172,728,729],{"class":189},"    });\n",[172,731,732],{"class":174,"line":488},[172,733,734],{"class":189},"  });\n",[172,736,738],{"class":174,"line":737},14,[172,739,740],{"class":189},"})();\n",[172,742,744,747,749],{"class":174,"line":743},15,[172,745,746],{"class":189},"\u003C\u002F",[172,748,585],{"class":185},[172,750,588],{"class":189},[172,752,754,756,759,762,765,767,769,772,775,778,780,783,786,789,791],{"class":174,"line":753},16,[172,755,582],{"class":189},[172,757,758],{"class":185},"style",[172,760,761],{"class":189},">",[172,763,764],{"class":178},".no-transition",[172,766,679],{"class":189},[172,768,764],{"class":178},[172,770,771],{"class":185}," *",[172,773,774],{"class":189}," { ",[172,776,777],{"class":244},"transition",[172,779,190],{"class":189},[172,781,782],{"class":244},"none",[172,784,785],{"class":266}," !important",[172,787,788],{"class":189},"; }\u003C\u002F",[172,790,758],{"class":185},[172,792,588],{"class":189},[10,794,795,798],{},[28,796,797],{},"requestAnimationFrame"," 嵌套两层是为了确保浏览器完成一帧渲染后再恢复 transition，避免初始化动画。",[10,800,801,802,805],{},"CSS 主题切换靠 ",[28,803,804],{},"[data-theme]"," 属性选择器 + CSS 变量：",[21,807,809],{"className":227,"code":808,"language":229,"meta":30,"style":30},":root {\n  --bg: #ffffff;\n  --text: #1a1a1a;\n  --accent: #3b82f6;\n}\n[data-theme=\"dark\"] {\n  --bg: #121212;\n  --text: #e5e5e5;\n}\n",[28,810,811,818,830,842,854,858,874,885,896],{"__ignoreMap":30},[172,812,813,816],{"class":174,"line":175},[172,814,815],{"class":178},":root",[172,817,239],{"class":189},[172,819,820,823,825,828],{"class":174,"line":182},[172,821,822],{"class":324},"  --bg",[172,824,190],{"class":189},[172,826,827],{"class":244},"#ffffff",[172,829,253],{"class":189},[172,831,832,835,837,840],{"class":174,"line":197},[172,833,834],{"class":324},"  --text",[172,836,190],{"class":189},[172,838,839],{"class":244},"#1a1a1a",[172,841,253],{"class":189},[172,843,844,847,849,852],{"class":174,"line":278},[172,845,846],{"class":324},"  --accent",[172,848,190],{"class":189},[172,850,851],{"class":244},"#3b82f6",[172,853,253],{"class":189},[172,855,856],{"class":174,"line":284},[172,857,281],{"class":189},[172,859,860,863,866,868,871],{"class":174,"line":292},[172,861,862],{"class":189},"[",[172,864,865],{"class":178},"data-theme",[172,867,609],{"class":266},[172,869,870],{"class":193},"\"dark\"",[172,872,873],{"class":189},"] {\n",[172,875,876,878,880,883],{"class":174,"line":305},[172,877,822],{"class":324},[172,879,190],{"class":189},[172,881,882],{"class":244},"#121212",[172,884,253],{"class":189},[172,886,887,889,891,894],{"class":174,"line":343},[172,888,834],{"class":324},[172,890,190],{"class":189},[172,892,893],{"class":244},"#e5e5e5",[172,895,253],{"class":189},[172,897,898],{"class":174,"line":444},[172,899,281],{"class":189},[14,901,903],{"id":902},"toc-目录的坑","TOC 目录的坑",[10,905,906,907,910,911,914],{},"Hexo 内置的 ",[28,908,909],{},"toc()"," helper 对中文标题生成的锚点有问题，",[28,912,913],{},"href"," 经常是空的，点了没反应。",[10,916,917,918,921],{},"所以 TOC 完全用 JS 动态构建，读取页面里实际存在的标题元素的 ",[28,919,920],{},"id","：",[21,923,927],{"className":924,"code":925,"language":926,"meta":30,"style":30},"language-js shiki shiki-themes github-dark-dimmed github-light","function buildToc() {\n  const tocList = document.getElementById('tocList');\n  const headings = document.querySelectorAll('.post-content h1, .post-content h2, .post-content h3');\n\n  headings.forEach(h => {\n    \u002F\u002F Hexo 把 id 放在 \u003Ch2> 内部的 \u003Cspan> 上，不是 \u003Ch2> 本身\n    const idEl = h.id ? h : h.querySelector('[id]');\n    if (!idEl) return;\n\n    const a = document.createElement('a');\n    a.href = '#' + idEl.id;\n    a.textContent = h.textContent.trim();\n    tocList.appendChild(a);\n  });\n}\n","js",[28,928,929,939,963,984,988,1006,1011,1046,1065,1069,1090,1106,1122,1133,1137],{"__ignoreMap":30},[172,930,931,933,936],{"class":174,"line":175},[172,932,595],{"class":266},[172,934,935],{"class":615}," buildToc",[172,937,938],{"class":189},"() {\n",[172,940,941,944,947,950,953,956,958,961],{"class":174,"line":182},[172,942,943],{"class":266},"  const",[172,945,946],{"class":244}," tocList",[172,948,949],{"class":266}," =",[172,951,952],{"class":189}," document.",[172,954,955],{"class":615},"getElementById",[172,957,316],{"class":189},[172,959,960],{"class":193},"'tocList'",[172,962,340],{"class":189},[172,964,965,967,970,972,974,977,979,982],{"class":174,"line":197},[172,966,943],{"class":266},[172,968,969],{"class":244}," headings",[172,971,949],{"class":266},[172,973,952],{"class":189},[172,975,976],{"class":615},"querySelectorAll",[172,978,316],{"class":189},[172,980,981],{"class":193},"'.post-content h1, .post-content h2, .post-content h3'",[172,983,340],{"class":189},[172,985,986],{"class":174,"line":278},[172,987,421],{"emptyLinePlaceholder":420},[172,989,990,993,996,998,1001,1004],{"class":174,"line":284},[172,991,992],{"class":189},"  headings.",[172,994,995],{"class":615},"forEach",[172,997,316],{"class":189},[172,999,1000],{"class":324},"h",[172,1002,1003],{"class":266}," =>",[172,1005,239],{"class":189},[172,1007,1008],{"class":174,"line":292},[172,1009,1010],{"class":647},"    \u002F\u002F Hexo 把 id 放在 \u003Ch2> 内部的 \u003Cspan> 上，不是 \u003Ch2> 本身\n",[172,1012,1013,1016,1019,1021,1024,1027,1030,1033,1036,1039,1041,1044],{"class":174,"line":305},[172,1014,1015],{"class":266},"    const",[172,1017,1018],{"class":244}," idEl",[172,1020,949],{"class":266},[172,1022,1023],{"class":189}," h.id ",[172,1025,1026],{"class":266},"?",[172,1028,1029],{"class":189}," h ",[172,1031,1032],{"class":266},":",[172,1034,1035],{"class":189}," h.",[172,1037,1038],{"class":615},"querySelector",[172,1040,316],{"class":189},[172,1042,1043],{"class":193},"'[id]'",[172,1045,340],{"class":189},[172,1047,1048,1051,1054,1057,1060,1063],{"class":174,"line":343},[172,1049,1050],{"class":266},"    if",[172,1052,1053],{"class":189}," (",[172,1055,1056],{"class":266},"!",[172,1058,1059],{"class":189},"idEl) ",[172,1061,1062],{"class":266},"return",[172,1064,253],{"class":189},[172,1066,1067],{"class":174,"line":444},[172,1068,421],{"emptyLinePlaceholder":420},[172,1070,1071,1073,1076,1078,1080,1083,1085,1088],{"class":174,"line":455},[172,1072,1015],{"class":266},[172,1074,1075],{"class":244}," a",[172,1077,949],{"class":266},[172,1079,952],{"class":189},[172,1081,1082],{"class":615},"createElement",[172,1084,316],{"class":189},[172,1086,1087],{"class":193},"'a'",[172,1089,340],{"class":189},[172,1091,1092,1095,1097,1100,1103],{"class":174,"line":466},[172,1093,1094],{"class":189},"    a.href ",[172,1096,609],{"class":266},[172,1098,1099],{"class":193}," '#'",[172,1101,1102],{"class":266}," +",[172,1104,1105],{"class":189}," idEl.id;\n",[172,1107,1108,1111,1113,1116,1119],{"class":174,"line":477},[172,1109,1110],{"class":189},"    a.textContent ",[172,1112,609],{"class":266},[172,1114,1115],{"class":189}," h.textContent.",[172,1117,1118],{"class":615},"trim",[172,1120,1121],{"class":189},"();\n",[172,1123,1124,1127,1130],{"class":174,"line":488},[172,1125,1126],{"class":189},"    tocList.",[172,1128,1129],{"class":615},"appendChild",[172,1131,1132],{"class":189},"(a);\n",[172,1134,1135],{"class":174,"line":737},[172,1136,734],{"class":189},[172,1138,1139],{"class":174,"line":743},[172,1140,281],{"class":189},[10,1142,1143,1144,1147,1148,1151],{},"滚动高亮也是 JS 实现的，监听 ",[28,1145,1146],{},"scroll"," 事件，找到当前视口内最近的标题，给对应的 TOC 链接加 ",[28,1149,1150],{},"active"," class。",[14,1153,1154],{"id":1154},"博客卡片的渐变色条",[10,1156,1157,1158,1161,1162,1165],{},"每张卡片顶部有一条 4px 的彩色渐变，6 张一循环，用 ",[28,1159,1160],{},"nth-child"," + ",[28,1163,1164],{},"::before"," 伪元素实现：",[21,1167,1169],{"className":227,"code":1168,"language":229,"meta":30,"style":30},".post-card::before {\n  content: '';\n  display: block;\n  height: 4px;\n}\n.post-card:nth-child(6n+1)::before { background: linear-gradient(90deg, #3b82f6, #06b6d4); }\n.post-card:nth-child(6n+2)::before { background: linear-gradient(90deg, #8b5cf6, #ec4899); }\n.post-card:nth-child(6n+3)::before { background: linear-gradient(90deg, #10b981, #3b82f6); }\n\u002F* ... *\u002F\n",[28,1170,1171,1178,1190,1201,1215,1219,1264,1303,1341],{"__ignoreMap":30},[172,1172,1173,1176],{"class":174,"line":175},[172,1174,1175],{"class":178},".post-card::before",[172,1177,239],{"class":189},[172,1179,1180,1183,1185,1188],{"class":174,"line":182},[172,1181,1182],{"class":244},"  content",[172,1184,190],{"class":189},[172,1186,1187],{"class":193},"''",[172,1189,253],{"class":189},[172,1191,1192,1194,1196,1199],{"class":174,"line":197},[172,1193,245],{"class":244},[172,1195,190],{"class":189},[172,1197,1198],{"class":244},"block",[172,1200,253],{"class":189},[172,1202,1203,1206,1208,1211,1213],{"class":174,"line":278},[172,1204,1205],{"class":244},"  height",[172,1207,190],{"class":189},[172,1209,1210],{"class":244},"4",[172,1212,267],{"class":266},[172,1214,253],{"class":189},[172,1216,1217],{"class":174,"line":284},[172,1218,281],{"class":189},[172,1220,1221,1224,1226,1229,1232,1234,1236,1239,1241,1244,1246,1249,1252,1254,1256,1258,1261],{"class":174,"line":292},[172,1222,1223],{"class":178},".post-card:nth-child",[172,1225,316],{"class":189},[172,1227,1228],{"class":244},"6n+1",[172,1230,1231],{"class":189},")",[172,1233,1164],{"class":178},[172,1235,774],{"class":189},[172,1237,1238],{"class":244},"background",[172,1240,190],{"class":189},[172,1242,1243],{"class":244},"linear-gradient",[172,1245,316],{"class":189},[172,1247,1248],{"class":244},"90",[172,1250,1251],{"class":266},"deg",[172,1253,679],{"class":189},[172,1255,851],{"class":244},[172,1257,679],{"class":189},[172,1259,1260],{"class":244},"#06b6d4",[172,1262,1263],{"class":189},"); }\n",[172,1265,1266,1268,1270,1273,1275,1277,1279,1281,1283,1285,1287,1289,1291,1293,1296,1298,1301],{"class":174,"line":305},[172,1267,1223],{"class":178},[172,1269,316],{"class":189},[172,1271,1272],{"class":244},"6n+2",[172,1274,1231],{"class":189},[172,1276,1164],{"class":178},[172,1278,774],{"class":189},[172,1280,1238],{"class":244},[172,1282,190],{"class":189},[172,1284,1243],{"class":244},[172,1286,316],{"class":189},[172,1288,1248],{"class":244},[172,1290,1251],{"class":266},[172,1292,679],{"class":189},[172,1294,1295],{"class":244},"#8b5cf6",[172,1297,679],{"class":189},[172,1299,1300],{"class":244},"#ec4899",[172,1302,1263],{"class":189},[172,1304,1305,1307,1309,1312,1314,1316,1318,1320,1322,1324,1326,1328,1330,1332,1335,1337,1339],{"class":174,"line":343},[172,1306,1223],{"class":178},[172,1308,316],{"class":189},[172,1310,1311],{"class":244},"6n+3",[172,1313,1231],{"class":189},[172,1315,1164],{"class":178},[172,1317,774],{"class":189},[172,1319,1238],{"class":244},[172,1321,190],{"class":189},[172,1323,1243],{"class":244},[172,1325,316],{"class":189},[172,1327,1248],{"class":244},[172,1329,1251],{"class":266},[172,1331,679],{"class":189},[172,1333,1334],{"class":244},"#10b981",[172,1336,679],{"class":189},[172,1338,851],{"class":244},[172,1340,1263],{"class":189},[172,1342,1343],{"class":174,"line":444},[172,1344,1345],{"class":647},"\u002F* ... *\u002F\n",[10,1347,1348],{},"不需要额外 HTML，纯 CSS 搞定。",[14,1350,1351],{"id":1351},"总结",[10,1353,1354],{},"整个主题的核心思路：",[1356,1357,1358,1368,1380,1389],"ul",{},[1359,1360,1361,1364,1365,1367],"li",{},[143,1362,1363],{},"路由","：用 Hexo 的 layout 系统做页面分发，",[28,1366,82],{}," 劫持首页渲染学术主页",[1359,1369,1370,1373,1374,1377,1378],{},[143,1371,1372],{},"数据","：个人信息放 ",[28,1375,1376],{},"_config.yml","，文章数据用 Hexo 全局 ",[28,1379,551],{},[1359,1381,1382,1385,1386,1388],{},[143,1383,1384],{},"主题切换","：CSS 变量 + ",[28,1387,804],{}," 属性，内联脚本防 FOUC",[1359,1390,1391,1394],{},[143,1392,1393],{},"交互","：JS 只处理 TOC、暗色模式切换、导航滚动阴影，没有引入任何框架",[10,1396,1397,1398,1405],{},"主题已开源在 ",[1399,1400,1404],"a",{"href":1401,"rel":1402},"https:\u002F\u002Fgithub.com\u002Fliangqianxing\u002Fhexo-theme-nova",[1403],"nofollow","hexo-theme-nova","，欢迎 star。",[758,1407,1408],{},"html pre.shiki code .sr-dd, html code.shiki .sr-dd{--shiki-default:#6CB6FF;--shiki-light:#6F42C1}html pre.shiki code .shb1k, html code.shiki .shb1k{--shiki-default:#8DDB8C;--shiki-light:#22863A}html pre.shiki code .ssh_m, html code.shiki .ssh_m{--shiki-default:#ADBAC7;--shiki-light:#24292E}html pre.shiki code .sXfbr, html code.shiki .sXfbr{--shiki-default:#96D0FF;--shiki-light:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html pre.shiki code .s6PUj, html code.shiki .s6PUj{--shiki-default:#F47067;--shiki-light:#D73A49}html pre.shiki code .saVmf, html code.shiki .saVmf{--shiki-default:#DCBDFB;--shiki-light:#6F42C1}html pre.shiki code .swcJU, html code.shiki .swcJU{--shiki-default:#6CB6FF;--shiki-light:#005CC5}html pre.shiki code .sNjOc, html code.shiki .sNjOc{--shiki-default:#F69D50;--shiki-light:#E36209}html pre.shiki code .sgHix, html code.shiki .sgHix{--shiki-default:#768390;--shiki-light:#6A737D}",{"title":30,"searchDepth":182,"depth":197,"links":1410},[1411,1412,1413,1414,1415,1416,1417,1418],{"id":16,"depth":182,"text":16},{"id":121,"depth":182,"text":121},{"id":210,"depth":182,"text":210},{"id":351,"depth":182,"text":351},{"id":555,"depth":182,"text":556},{"id":902,"depth":182,"text":903},{"id":1154,"depth":182,"text":1154},{"id":1351,"depth":182,"text":1351},[1420],"技术","2026-04-06",false,"md",{},"\u002Fposts\u002Fnova-theme-implementation",{"title":5,"description":12},"posts\u002Fnova-theme-implementation",[1429,1430,1431],"Hexo","前端","开源","8MSCYBVQwQZdUIBkcBOPGTVXQeV6jeZoTPRjDq6IJDY",[1434,1447,1459,1465,1478,1487,1496,1506,1516,1525,1535,1547,1560,1572,1581,1591,1603,1614,1624,1632,1643,1649,1655,1661,1669,1678,1685,1691,1694,1702],{"slug":1435,"path":1436,"title":1437,"date":1438,"tags":1439,"description":30,"draft":1422,"hidden":1422,"published":420,"readingTime":444},"multimodal-rag-from-scratch","\u002Fposts\u002Fmultimodal-rag-from-scratch","从零实现多模态 RAG：BM25、Dense 检索、RRF 融合、MMR 重排全部手写","2026-06-30 18:00:00",[1440,1441,1442,1443,1444,1445,1446],"RAG","多模态","AI Infra","BM25","向量检索","混合检索","实习求职",{"slug":1448,"path":1449,"title":1450,"date":1451,"tags":1452,"description":30,"draft":1422,"hidden":1422,"published":420,"readingTime":466},"mini-llm-engine-deep-dive","\u002Fposts\u002Fmini-llm-engine-deep-dive","讲透 mini-llm-engine：从显存碎片到六大推理优化","2026-06-30 14:00:00",[1453,1442,1454,1455,1456,1457,1458,1446],"LLM","vLLM","PagedAttention","KV Cache","推理优化","投机解码",{"slug":1460,"path":1461,"title":1462,"date":1463,"tags":1464,"description":30,"draft":1422,"hidden":1422,"published":420,"readingTime":455},"mini-llm-engine-from-scratch","\u002Fposts\u002Fmini-llm-engine-from-scratch","从零实现 LLM 推理引擎：深挖 vLLM 的六大核心优化","2026-06-30 10:00:00",[1453,1442,1454,1455,1457,1458,1446],{"slug":1466,"path":1467,"title":1468,"date":1469,"tags":1470,"description":1476,"draft":1422,"hidden":1422,"published":420,"readingTime":1477},"bytedance-recommendation-architecture-intern-interview","\u002Fposts\u002Fbytedance-recommendation-architecture-intern-interview","字节推荐架构实习生 Data 面试准备：推荐系统、实时特征与高并发八股","2026-06-09",[1471,1472,1473,1474,1475],"面试","推荐系统","后端架构","实时计算","字节跳动","面向字节跳动推荐架构团队实习岗位的八股准备清单，覆盖推荐系统链路、实时数据、特征服务、高并发后端、分布式系统与项目包装。",18,{"slug":1479,"path":1480,"title":1481,"date":1469,"tags":1482,"description":1486,"draft":1422,"hidden":1422,"published":420,"readingTime":477},"high-concurrency-rate-limiting-algorithms","\u002Fposts\u002Fhigh-concurrency-rate-limiting-algorithms","高并发限流算法：固定窗口、滑动窗口、漏桶与令牌桶",[1483,1484,1473,1485,1471],"高并发","限流","系统设计","面试和工程都常见的限流算法总结，讲清楚固定窗口、滑动窗口、漏桶、令牌桶、并发数限制以及分布式限流如何落地。",{"slug":1488,"path":1489,"title":1490,"date":1469,"tags":1491,"description":1495,"draft":1422,"hidden":1422,"published":420,"readingTime":343},"kafka-producer-broker-consumer","\u002Fposts\u002Fkafka-producer-broker-consumer","Kafka 入门：生产者、Broker、消费者和“消费”到底是什么意思",[1492,1493,1494,1471,1472],"Kafka","消息队列","后端","用推荐系统里的用户行为日志为例，讲清楚 Kafka 的作用、Producer、Broker、Consumer、Topic、Partition、Offset 和消费语义。",{"slug":1497,"path":1498,"title":1499,"date":1469,"tags":1500,"description":1505,"draft":1422,"hidden":1422,"published":420,"readingTime":292},"leetcode-lru-merge-k-reverse-list","\u002Fposts\u002Fleetcode-lru-merge-k-reverse-list","链表与缓存高频题：LRU Cache、合并 K 个有序链表、反转链表",[1501,1502,1503,1504,1471],"算法","链表","LRU","LeetCode","面试高频算法题速记，整理 LRU Cache、合并 K 个有序链表、反转链表的核心思路、复杂度和 C++ 代码。",{"slug":1507,"path":1508,"title":1509,"date":1510,"tags":1511,"description":30,"draft":1422,"hidden":1422,"published":420,"readingTime":753},"meddococr-interpreter-source-analysis","\u002Fposts\u002Fmeddococr-interpreter-source-analysis","MedDocOCR-Interpreter 源码导读：医疗文档 OCR、结构化抽取与报告解读原型","2026-05-22 10:30:00",[1512,1441,1513,1440,1514,1515],"OCR","医疗 AI","Python","源码分析",{"slug":1517,"path":1518,"title":1519,"date":1520,"tags":1521,"description":30,"draft":1422,"hidden":1422,"published":420,"readingTime":455},"cet6-writing-model-essays","\u002Fposts\u002Fcet6-writing-model-essays","六级写作范文背诵包：10 个高频话题","2026-05-20 09:00:00",[1522,1523,1524],"English","CET6","Writing",{"slug":1526,"path":1527,"title":1528,"date":1529,"tags":1530,"description":30,"draft":1422,"hidden":1422,"published":420,"readingTime":1534},"claude-code-context-management","\u002Fposts\u002Fclaude-code-context-management","Claude Code 上下文管理机制：从 Microcompact 到 Auto Compact","2026-05-19 10:00:00",[1531,1532,1453,1442,1533],"Claude Code","Agent","上下文工程",27,{"slug":1536,"path":1537,"title":1538,"date":1539,"tags":1540,"description":30,"draft":1422,"hidden":1422,"published":420,"readingTime":1546},"nanotron-llm-pretraining-framework-analysis","\u002Fposts\u002Fnanotron-llm-pretraining-framework-analysis","Nanotron 项目详解：Hugging Face 的大模型预训练框架怎么做分布式训练","2026-05-10 12:10:00",[1453,1541,1542,1543,1544,1442,1545],"大模型训练","分布式训练","Nanotron","Hugging Face","PyTorch",19,{"slug":1548,"path":1549,"title":1550,"date":1551,"tags":1552,"description":30,"draft":1422,"hidden":1422,"published":420,"readingTime":1559},"gofoundry-go-backend-foundation-framework","\u002Fposts\u002Fgofoundry-go-backend-foundation-framework","GoFoundry 项目详解：基于 Go 的后端基础框架套件设计","2026-05-10 11:20:00",[1553,1554,1555,1556,1557,1493,1558],"Go","后端框架","ORM","分布式缓存","分布式锁","项目架构",25,{"slug":1561,"path":1562,"title":1563,"date":1564,"tags":1565,"description":30,"draft":1422,"hidden":1422,"published":420,"readingTime":1534},"cloudvault-go-cloud-storage-system","\u002Fposts\u002Fcloudvault-go-cloud-storage-system","CloudVault 项目详解：基于 Go 的云端存储与网盘系统架构设计","2026-05-10 10:30:00",[1553,1566,1567,1568,1558,1569,1570,1571],"云存储","网盘系统","分布式系统","Redis","RabbitMQ","Elasticsearch",{"slug":1573,"path":1574,"title":1575,"date":1576,"tags":1577,"description":30,"draft":1422,"hidden":1422,"published":420,"readingTime":1580},"openclaw-source-code-analysis","\u002Fposts\u002Fopenclaw-source-code-analysis","OpenClaw 源码导读：个人 AI 助手的网关、通道、插件与运行时架构","2026-05-08 16:30:00",[1578,1532,1442,1579,1515],"OpenClaw","TypeScript",20,{"slug":1582,"path":1583,"title":1584,"date":1585,"tags":1586,"description":30,"draft":1422,"hidden":1422,"published":420,"readingTime":455},"flow-matching-generative-models","\u002Fposts\u002Fflow-matching-generative-models","Flow Matching：从噪声到数据的连续流生成模型","2026-05-07 00:00:00",[1587,1588,1589,1590],"生成模型","Diffusion","Flow Matching","深度学习",{"slug":1592,"path":1593,"title":1594,"date":1595,"tags":1596,"description":30,"draft":1422,"hidden":1422,"published":420,"readingTime":743},"database-ai-week4","\u002Fposts\u002Fdatabase-ai-week4","Week 4：数据库速成——从 Storage、Index、Query Optimization 到 Vector DB 与 RAG","2026-05-05 12:00:00",[1597,1598,1599,1440,1600,1601,1602],"数据库","CMU 15-445","Vector DB","LLM Memory","Query Optimization","Caching",{"slug":1604,"path":1605,"title":1606,"date":1607,"tags":1608,"description":30,"draft":1422,"hidden":1422,"published":420,"readingTime":753},"distributed-systems-week3","\u002Fposts\u002Fdistributed-systems-week3","Week 3：分布式系统速成——MapReduce、Raft、容错与 Distributed KV Store","2026-05-05 11:00:00",[1568,1609,1610,1611,1612,1613,1532],"MIT 6.824","MapReduce","Raft","KV Store","Ray",{"slug":1615,"path":1616,"title":1617,"date":1618,"tags":1619,"description":30,"draft":1422,"hidden":1422,"published":420,"readingTime":743},"gpu-inference-acceleration-week2","\u002Fposts\u002Fgpu-inference-acceleration-week2","Week 2：GPU 与推理加速——从 Kernel、算子融合到 LLM Serving","2026-05-05 10:00:00",[1590,1620,1621,1453,1622,1454,1623],"GPU","推理加速","CMU 10-414","TensorRT",{"slug":1625,"path":1626,"title":1627,"date":1628,"tags":1629,"description":30,"draft":1422,"hidden":1422,"published":420,"readingTime":1477},"dl-framework-autograd-mini","\u002Fposts\u002Fdl-framework-autograd-mini","Week 1：DL 框架与 Autograd——从计算图、反向传播到 Mini Autograd 实现","2026-05-05 09:00:00",[1590,1630,1545,1622,1631],"Autograd","Mini Framework",{"slug":1633,"path":1634,"title":1635,"date":1636,"tags":1637,"description":30,"draft":1422,"hidden":1422,"published":420,"readingTime":477},"lock-free-concurrency-notes","\u002Fposts\u002Flock-free-concurrency-notes","无锁并发入门：从 CAS 到 Atomic Ring Buffer","2026-04-25",[1638,1639,1640,1641,1642],"C++","并发","无锁编程","性能优化","量化开发",{"slug":1644,"path":1645,"title":1646,"date":1647,"tags":1648,"description":30,"draft":1422,"hidden":1422,"published":420,"readingTime":284},"agent-memory","\u002Fposts\u002Fagent-memory","Agent 对话记忆化：从原理到实现","2026-04-24",[1453,1532,1440,1471],{"slug":1650,"path":1651,"title":1652,"date":1647,"tags":1653,"description":30,"draft":1422,"hidden":1422,"published":420,"readingTime":292},"llm-context-compression","\u002Fposts\u002Fllm-context-compression","LLM 上下文五层压缩机制详解",[1453,1532,1654,1471],"上下文压缩",{"slug":1656,"path":1657,"title":1658,"date":1659,"tags":1660,"description":30,"draft":1422,"hidden":1422,"published":420,"readingTime":444},"cpp-concurrency-basics","\u002Fposts\u002Fcpp-concurrency-basics","C++ 并发编程入门：从数据竞争到线程池","2026-04-15",[1638,1639,1471,1642],{"slug":1662,"path":1663,"title":1664,"date":1665,"tags":1666,"description":30,"draft":1422,"hidden":1422,"published":420,"readingTime":175},"travel-in-shenzhen","\u002Fposts\u002Ftravel-in-shenzhen","XCPC 深圳游记","2026-04-13",[1667,1638,1668],"XCPC","比赛",{"slug":1670,"path":1671,"title":1672,"date":1673,"tags":1674,"description":30,"draft":1422,"hidden":1422,"published":420,"readingTime":305},"backend-stack-deep-dive","\u002Fposts\u002Fbackend-stack-deep-dive","后端五件套：FastAPI \u002F Node.js \u002F SQLAlchemy async \u002F PostgreSQL \u002F Docker 面试速通","2026-04-07",[1494,1675,1676,1677,1471],"FastAPI","PostgreSQL","Docker",{"slug":1679,"path":1680,"title":1681,"date":1421,"tags":1682,"description":30,"draft":1422,"hidden":1422,"published":420,"readingTime":292},"deepscientist-tech-stack","\u002Fposts\u002Fdeepscientist-tech-stack","DeepScientist 技术栈全解析：一个 AI 科研平台的架构设计",[1683,1675,1684,1676,1471],"全栈","Next.js",{"slug":1686,"path":1687,"title":1688,"date":1421,"tags":1689,"description":30,"draft":1422,"hidden":1422,"published":420,"readingTime":343},"minicode-source-analysis","\u002Fposts\u002Fminicode-source-analysis","MiniCode 源码解析：用 5000 行 TypeScript 实现一个 AI 编程助手",[1579,1690,1453,1515,1471],"CLI",{"slug":1692,"path":1425,"title":5,"date":1421,"tags":1693,"description":30,"draft":1422,"hidden":1422,"published":420,"readingTime":284},"nova-theme-implementation",[1429,1430,1431],{"slug":1695,"path":1696,"title":1697,"date":1698,"tags":1699,"description":30,"draft":1422,"hidden":1422,"published":420,"readingTime":182},"git-cheatsheet","\u002Fposts\u002Fgit-cheatsheet","Git 常用操作备忘","2026-04-05 14:00:00",[1700,1701],"Git","工具",{"slug":1703,"path":1704,"title":1705,"date":1706,"tags":1707,"description":30,"draft":1422,"hidden":1422,"published":420,"readingTime":197},"github-actions-intro","\u002Fposts\u002Fgithub-actions-intro","GitHub Actions 入门：自动化你的工作流","2026-04-04 09:00:00",[1708,1709,1710],"GitHub Actions","CI\u002FCD","自动化",1782796012510]