For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Build a mature, concise integrated personal homepage for 宵宵 while preserving the existing writing library, reader pages, playlist, and contact interactions.
Architecture: Keep the current static Jekyll architecture. index.html owns homepage structure, assets/css/site.css owns the shared visual system, assets/js/home.js owns small local interactions and dynamic work rendering, and assets/data/works.js remains the content source of truth.
Tech Stack: Jekyll/GitHub Pages, Liquid templates, vanilla JavaScript ES modules, CSS, Node test runner.
assets/js/home.test.mjs: add homepage structure and featured-card rendering tests that describe the new mature profile requirements.assets/js/home.js: expose tags in dynamically rendered featured cards and keep existing library/player/contact behavior stable.index.html: replace the current card-heavy homepage with the integrated profile layout.assets/css/site.css: rebuild homepage, library, featured-card, about, contact, and playlist styles around the new calmer visual language while preserving reader styles._site/ may change during local Jekyll builds but should not be committed because it is ignored.Files:
Modify: assets/js/home.test.mjs
Step 1: Add structure tests for the redesigned homepage
Append these tests near the existing homepage tests in assets/js/home.test.mjs:
test('homepage uses the mature integrated profile structure', async () => {
const home = await readFile(new URL('../../index.html', import.meta.url), 'utf8');
assert.match(home, /class="site-nav"/);
assert.match(home, /class="hero-profile"/);
assert.match(home, /id="works"/);
assert.match(home, /id="about"/);
assert.match(home, /id="music"/);
assert.match(home, /id="contact"/);
assert.match(home, /综合个人档案|个人主页|个人知识库/);
});
test('homepage navigation points to the primary content sections', async () => {
const home = await readFile(new URL('../../index.html', import.meta.url), 'utf8');
assert.match(home, /href="#works"/);
assert.match(home, /href="#about"/);
assert.match(home, /href="#music"/);
assert.match(home, /href="#contact"/);
assert.match(home, /href="https:\/\/github.com\/SxLin0"/);
assert.match(home, /href="mailto:2125808970@qq.com"/);
});
test('homepage avoids the previous decorative profile card shell', async () => {
const home = await readFile(new URL('../../index.html', import.meta.url), 'utf8');
assert.doesNotMatch(home, /class="profile-card"/);
assert.doesNotMatch(home, /class="hero-panel"/);
});
Append this DOM-oriented test after the featured works include quiet card metadata test:
test('featured work cards render metadata, summary, tags, and action text', () => {
const appended = [];
const card = {
className: '',
href: '',
attributes: {},
children: appended,
setAttribute(name, value) {
this.attributes[name] = value;
},
append(...nodes) {
appended.push(...nodes);
}
};
const originalDocument = globalThis.document;
globalThis.document = {
...originalDocument,
createElement(tagName) {
return {
tagName,
className: '',
textContent: '',
children: [],
append(...nodes) {
this.children.push(...nodes);
}
};
}
};
try {
const rendered = createFeaturedWorkCard({
id: 'demo',
section: 'blog',
title: 'Demo Work',
href: 'content/blog/demo.html',
date: '课程笔记',
summary: 'A short summary.',
tags: ['JavaScript', 'Writing']
}, card);
assert.equal(rendered.className, 'featured-work-card');
assert.equal(rendered.href, '/content/blog/demo.html');
assert.equal(rendered.attributes['aria-label'], '阅读Demo Work');
assert.ok(appended.some((node) => node.className === 'featured-work-meta' && node.textContent === '博客 · 课程笔记'));
assert.ok(appended.some((node) => node.tagName === 'strong' && node.textContent === 'Demo Work'));
assert.ok(appended.some((node) => node.tagName === 'p' && node.textContent === 'A short summary.'));
assert.ok(appended.some((node) => node.className === 'featured-work-tags' && node.children.map((child) => child.textContent).join(',') === 'JavaScript,Writing'));
assert.ok(appended.some((node) => node.className === 'featured-work-action' && node.textContent === '打开阅读'));
} finally {
globalThis.document = originalDocument;
}
});
createFeaturedWorkCard for testingChange this function signature in assets/js/home.js during Task 2:
export function createFeaturedWorkCard(work) {
Run:
node --test assets/js/home.test.mjs
Expected: FAIL because createFeaturedWorkCard is not exported yet and index.html still uses the old profile-card and hero-panel structure.
Files:
Modify: assets/js/home.js
Step 1: Export and extend createFeaturedWorkCard
Replace the existing createFeaturedWorkCard function with:
export function createFeaturedWorkCard(work) {
const link = document.createElement('a');
const meta = document.createElement('span');
const title = document.createElement('strong');
const summary = document.createElement('p');
const tags = document.createElement('span');
const action = document.createElement('span');
link.className = 'featured-work-card';
link.href = resolveWorkHref(work);
link.setAttribute('aria-label', `阅读${work.title}`);
meta.className = 'featured-work-meta';
meta.textContent = [getSectionTitle(work.section), work.date].filter(Boolean).join(' · ');
title.textContent = work.title;
summary.textContent = work.summary || '打开作品继续阅读。';
tags.className = 'featured-work-tags';
(work.tags || []).forEach((tag) => {
const tagElement = document.createElement('span');
tagElement.textContent = tag;
tags.append(tagElement);
});
action.className = 'featured-work-action';
action.textContent = '打开阅读';
link.append(meta, title, summary, tags, action);
return link;
}
Run:
node --test assets/js/home.test.mjs
Expected: homepage structure tests still FAIL, featured card test PASS.
Run:
git add assets/js/home.js assets/js/home.test.mjs
git commit -m "test: define homepage redesign contract"
Expected: a commit containing only tests and the featured-card export/rendering adjustment.
Files:
Modify: index.html
Step 1: Replace the body content inside <div class="home-body">
Keep the front matter and final module script. Replace the current <main class="page-shell">...</main> content with this structure:
<main class="page-shell">
<aside class="library-panel" aria-label="作品书架">
<div class="library-title-row">
<div class="library-heading">
<p class="eyebrow">作品目录</p>
<h2>宵宵的书架</h2>
</div>
<button class="library-toggle" type="button" aria-controls="library-panel-content" aria-expanded="false">
<span class="library-toggle-text">书架</span>
<span class="library-toggle-icon" aria-hidden="true"></span>
</button>
</div>
<div class="library-panel-content" id="library-panel-content">
<a class="library-home-link" href="/">首页</a>
<label class="library-search" for="library-search">
<span>搜索文章或诗词</span>
<span class="library-search-control">
<input id="library-search" type="search" placeholder="输入标题..." autocomplete="off">
<button class="library-search-clear" id="library-search-clear" type="button" aria-label="清空搜索" title="清空搜索" hidden>×</button>
</span>
<span class="library-search-status" id="library-search-status" aria-live="polite"></span>
</label>
<div id="library-sections">
<details class="library-section">
<summary><span>Blog</span><span class="section-count">4</span></summary>
<ul class="work-list">
<li><a class="work-card" href="/content/blog/operating-system.html"><span class="work-title">操作系统</span></a></li>
<li><a class="work-card" href="/content/blog/database.html"><span class="work-title">数据库</span></a></li>
<li><a class="work-card" href="/content/blog/internet-computing.html"><span class="work-title">互联网计算</span></a></li>
<li><a class="work-card" href="/content/blog/compiler-principles.html"><span class="work-title">编译原理</span></a></li>
</ul>
</details>
<details class="library-section">
<summary><span>Poem</span><span class="section-count">21</span></summary>
<ul class="work-list">
<li><a class="work-card" href="/reader.html?work=shang-yuan"><span class="work-title">上元</span></a></li>
<li><a class="work-card" href="/reader.html?work=first-arrival-jiangning"><span class="work-title">初到江宁</span></a></li>
<li><a class="work-card" href="/reader.html?work=three-kingdoms"><span class="work-title">叹三国</span></a></li>
<li><a class="work-card" href="/reader.html?work=summer-thoughts"><span class="work-title">夏日咏怀</span></a></li>
<li><a class="work-card" href="/reader.html?work=night-boat-home"><span class="work-title">夜行舟归家</span></a></li>
<li><a class="work-card" href="/reader.html?work=rumengling-mid-autumn"><span class="work-title">如梦令·中秋</span></a></li>
<li><a class="work-card" href="/reader.html?work=cold-night-family"><span class="work-title">寒夜怀亲</span></a></li>
<li><a class="work-card" href="/reader.html?work=untitled-mid-autumn"><span class="work-title">无题</span></a></li>
<li><a class="work-card" href="/reader.html?work=spring-tea"><span class="work-title">春茶</span></a></li>
<li><a class="work-card" href="/reader.html?work=moonlit-hometown"><span class="work-title">月夜怀乡</span></a></li>
<li><a class="work-card" href="/reader.html?work=wutong-shadow"><span class="work-title">梧桐影</span></a></li>
<li><a class="work-card" href="/reader.html?work=taihang-north"><span class="work-title">登临太行北望</span></a></li>
<li><a class="work-card" href="/reader.html?work=yellow-river"><span class="work-title">登黄河古迹怀古</span></a></li>
<li><a class="work-card" href="/reader.html?work=kitchen-god-snow"><span class="work-title">祭灶遇风雪</span></a></li>
<li><a class="work-card" href="/reader.html?work=red-beans"><span class="work-title">红豆</span></a></li>
<li><a class="work-card" href="/reader.html?work=tiger-year"><span class="work-title">虎年</span></a></li>
<li><a class="work-card" href="/reader.html?work=after-exam"><span class="work-title">试后述志</span></a></li>
<li><a class="work-card" href="/reader.html?work=caisangzi-snow"><span class="work-title">采桑子·咏雪</span></a></li>
<li><a class="work-card" href="/reader.html?work=qingyuan-birthday"><span class="work-title">青玉案·添岁有感</span></a></li>
<li><a class="work-card" href="/reader.html?work=zhegutian-dongge-dream"><span class="work-title">鹧鸪天·东阁记梦</span></a></li>
<li><a class="work-card" href="/reader.html?work=zhegutian-gray-hair"><span class="work-title">鹧鸪天·华发叹</span></a></li>
</ul>
</details>
<details class="library-section">
<summary><span>Articles</span><span class="section-count">2</span></summary>
<ul class="work-list">
<li><a class="work-card" href="/reader.html?work=software-major"><span class="work-title">关于填报软件工程专业的一些看法</span></a></li>
<li><a class="work-card" href="/reader.html?work=spring-essay"><span class="work-title">浅谈“春”</span></a></li>
</ul>
</details>
</div>
<p class="library-empty" id="library-search-empty" hidden>没有找到匹配的作品</p>
<div class="library-side-note" aria-label="联系入口">
<div class="library-side-actions">
<a href="https://github.com/SxLin0" target="_blank" rel="noreferrer">GitHub</a>
<a href="mailto:2125808970@qq.com">Email</a>
</div>
</div>
</div>
</aside>
<div class="home-stack">
<nav class="site-nav" aria-label="首页导航">
<a class="site-brand" href="/" aria-label="回到首页">宵宵</a>
<div class="site-nav-links">
<a href="#works">作品</a>
<a href="#about">关于</a>
<a href="#music">音乐</a>
<a href="#contact">联系</a>
</div>
</nav>
<section class="hero-profile" aria-labelledby="hero-title">
<div class="hero-copy">
<p class="hero-kicker">南京大学软件工程在读 / 写作者 / 个人知识库维护者</p>
<h1 id="hero-title">宵宵</h1>
<p class="hero-subtitle">Software Engineering, Writing, Poems and Notes.</p>
<p class="hero-lead">这里是我的综合个人档案:整理课程笔记、技术文章、长文思考、诗词练习、音乐与生活碎片。希望它像一间清楚的书房,既能快速找到内容,也能留下真实的个人气息。</p>
<div class="hero-actions" aria-label="首页快捷入口">
<a class="hero-action primary" href="#works">阅读精选</a>
<a class="hero-action" href="#library-panel-content">打开书架</a>
<a class="hero-action" href="https://github.com/SxLin0" target="_blank" rel="noreferrer">GitHub</a>
<a class="hero-action" href="mailto:2125808970@qq.com">Email</a>
</div>
</div>
<aside class="hero-card" aria-label="个人摘要">
<img src="/src/photo.jpg" alt="宵宵的头像">
<div>
<p class="hero-card-label">Current Focus</p>
<strong>软件工程 / 后端开发 / 知识整理</strong>
<span>C++ · Java · Python · JavaScript · Spring Boot</span>
</div>
<dl class="hero-facts" aria-label="网站内容概览">
<div><dt>Blog</dt><dd>课程笔记</dd></div>
<div><dt>Poem</dt><dd>诗词创作</dd></div>
<div><dt>Articles</dt><dd>长文观点</dd></div>
</dl>
</aside>
</section>
Continue the same file with sections for works, content map, about, music, contact, and footer. Reuse the existing playlist <li> entries exactly so audio paths and cover paths stay stable. Move contact markup into a section with id="contact" and keep .copy-contact buttons and data-copy-value unchanged.
Run:
node --test assets/js/home.test.mjs
Expected: all JS tests PASS or only CSS-independent assertions PASS. If any test fails because of a missing anchor or class, update index.html to match the plan names.
Run:
git add index.html
git commit -m "feat: restructure homepage profile layout"
Expected: a commit containing only index.html.
Files:
Modify: assets/css/site.css
Step 1: Replace the color tokens
Update the :root block to:
:root {
--ink: #172b36;
--muted: #657780;
--primary: #176b87;
--primary-dark: #0f4055;
--accent: #4b9db6;
--seal: #a24b3d;
--paper: #fbfaf7;
--surface: rgba(255, 255, 255, 0.86);
--surface-strong: rgba(255, 255, 255, 0.96);
--line: rgba(23, 107, 135, 0.14);
--shadow: 0 18px 48px rgba(27, 49, 61, 0.1);
--soft-shadow: 0 10px 28px rgba(27, 49, 61, 0.08);
--radius: 12px;
--reading-width: 1060px;
}
Remove the old .profile-card, .hero-panel, .section-map-card, .about-panel, .playlist, and contact homepage blocks. Add styles for .home-stack, .site-nav, .hero-profile, .hero-card, .featured-panel, .content-map, .about-layout, .profile-list, .skill-clusters, .music-panel, and .contact-panel.
Use these required layout rules:
.home-body {
padding: 28px 28px 38px 340px;
}
.home-stack {
display: grid;
gap: 20px;
}
.site-nav {
position: sticky;
top: 18px;
z-index: 10;
display: flex;
align-items: center;
justify-content: space-between;
gap: 18px;
padding: 12px 16px;
border: 1px solid var(--line);
border-radius: var(--radius);
background: rgba(255, 255, 255, 0.82);
box-shadow: var(--soft-shadow);
backdrop-filter: blur(18px);
}
.hero-profile {
display: grid;
grid-template-columns: minmax(0, 1.35fr) minmax(280px, 0.65fr);
gap: 24px;
align-items: stretch;
min-height: 430px;
padding: clamp(28px, 5vw, 54px);
border: 1px solid var(--line);
border-radius: var(--radius);
background: linear-gradient(135deg, rgba(255, 255, 255, 0.96), rgba(251, 250, 247, 0.92));
box-shadow: var(--shadow);
}
Add these breakpoints without removing reader-page rules:
@media (max-width: 1180px) {
.home-body,
.reader-body {
padding-left: 28px;
}
.library-panel {
position: sticky;
top: 14px;
width: 100%;
max-height: none;
margin-bottom: 18px;
}
}
@media (max-width: 820px) {
.home-body {
padding: 14px;
}
.site-nav,
.hero-profile,
.about-layout,
.mini-player {
grid-template-columns: 1fr;
}
.site-nav {
position: static;
align-items: flex-start;
}
.hero-profile {
min-height: 0;
padding: 24px;
}
.playlist ul,
.featured-works,
.content-map {
grid-template-columns: 1fr;
}
}
Run:
node --test assets/js/home.test.mjs
Expected: PASS.
Run:
git add assets/css/site.css
git commit -m "style: polish personal homepage"
Expected: a commit containing only assets/css/site.css.
Files:
README.mdRun only: node --test assets/js/home.test.mjs, bundle exec jekyll build
Run:
node --test assets/js/home.test.mjs
Expected: all tests PASS.
Run:
bundle exec jekyll build
Expected: build succeeds and writes the site to _site/.
Run:
bundle exec jekyll serve --host 127.0.0.1 --port 4000
Expected: server prints a local URL such as http://127.0.0.1:4000/.
Open the served homepage and check:
Click featured works, content map links, GitHub, email, QQ copy, playlist item, play/pause button, and library search.
If visual regressions appear, edit only index.html, assets/css/site.css, or assets/js/home.js as needed. Then rerun:
node --test assets/js/home.test.mjs
bundle exec jekyll build
Expected: tests and build PASS.
Run:
git add index.html assets/css/site.css assets/js/home.js assets/js/home.test.mjs
git commit -m "fix: verify redesigned homepage"
Expected: commit only if there were post-QA fixes. If no fixes were needed, skip this commit.
index.html.