Personal Site Redesign Implementation Plan

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.


File Structure

Task 1: Add Tests for the New Homepage Contract

Files:

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;
    }
});

Change 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:

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.

Task 3: Replace Homepage Markup

Files:

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.

Task 4: Rebuild Homepage and Library Styles

Files:

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.

Task 5: Build and Manual Verification

Files:

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:

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.

Self-Review