更换到Astro #9
Generated
+269
-5534
File diff suppressed because it is too large
Load Diff
+3
-38
@@ -9,44 +9,9 @@
|
|||||||
"preview": "astro preview",
|
"preview": "astro preview",
|
||||||
"server": "astro dev"
|
"server": "astro dev"
|
||||||
},
|
},
|
||||||
"hexo": {
|
|
||||||
"version": "8.1.1"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/rss": "^4.0.12",
|
"@astrojs/rss": "^4.0.18",
|
||||||
"@astrojs/sitemap": "^3.6.0",
|
"@astrojs/sitemap": "^3.7.3",
|
||||||
"astro": "^5.13.0",
|
"astro": "^6.4.8"
|
||||||
"axios": "^1.15.0",
|
|
||||||
"cheerio": "^1.2.0",
|
|
||||||
"hexo": "^8.1.1",
|
|
||||||
"hexo-abbrlink": "^2.2.1",
|
|
||||||
"hexo-ai-summary-liushen": "^2.0.0",
|
|
||||||
"hexo-butterfly-swiper-lyx": "^1.0.2",
|
|
||||||
"hexo-deployer-git": "^4.0.0",
|
|
||||||
"hexo-douban": "^2.3.6",
|
|
||||||
"hexo-filter-mathjax": "^0.9.1",
|
|
||||||
"hexo-generator-archive": "^2.0.0",
|
|
||||||
"hexo-generator-category": "^2.0.0",
|
|
||||||
"hexo-generator-index": "^4.0.0",
|
|
||||||
"hexo-generator-search": "^2.4.3",
|
|
||||||
"hexo-generator-searchdb": "^1.5.0",
|
|
||||||
"hexo-generator-sitemap": "^3.0.1",
|
|
||||||
"hexo-generator-tag": "^2.0.0",
|
|
||||||
"hexo-indexnow": "^1.2.0",
|
|
||||||
"hexo-pretty-feed": "^2.1.0",
|
|
||||||
"hexo-renderer-ejs": "^2.0.0",
|
|
||||||
"hexo-renderer-marked": "^7.0.1",
|
|
||||||
"hexo-renderer-pug": "^3.0.0",
|
|
||||||
"hexo-renderer-stylus": "^3.0.1",
|
|
||||||
"hexo-safego": "^2.3.0",
|
|
||||||
"hexo-server": "^3.0.0",
|
|
||||||
"hexo-spoiler": "^1.7.4",
|
|
||||||
"hexo-wordcount": "^6.0.1",
|
|
||||||
"moment": "^2.30.1",
|
|
||||||
"node-fetch": "^3.3.2",
|
|
||||||
"p-limit": "^7.3.0",
|
|
||||||
"typesense": "^3.0.5",
|
|
||||||
"vite-plugin-require-transform": "^1.0.21",
|
|
||||||
"xml2js": "^0.6.2"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
---
|
||||||
|
import type { BlogPost } from '@/lib/posts';
|
||||||
|
|
||||||
|
type TocItem = {
|
||||||
|
depth: number;
|
||||||
|
text: string;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
post: BlogPost;
|
||||||
|
toc: TocItem[];
|
||||||
|
relatedPosts: BlogPost[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const { post, toc, relatedPosts } = Astro.props;
|
||||||
|
const avatar = 'https://free.picui.cn/free/2025/08/10/689845496a283.png';
|
||||||
|
---
|
||||||
|
|
||||||
|
<aside class="article-sidebar" aria-label="文章侧边栏">
|
||||||
|
<section class="sidebar-card article-author-card">
|
||||||
|
<img class="profile-avatar" src={avatar} alt="biss" loading="lazy" />
|
||||||
|
<div class="profile-name">biss</div>
|
||||||
|
<div class="article-author-meta">
|
||||||
|
<span>{post.dateText}</span>
|
||||||
|
{post.categories[0] && <a href={`/categories/${encodeURIComponent(post.categories[0])}/`}>{post.categories[0]}</a>}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{toc.length > 0 && (
|
||||||
|
<section class="sidebar-card toc-card">
|
||||||
|
<h2>
|
||||||
|
<span aria-hidden="true">☰</span>
|
||||||
|
文章目录
|
||||||
|
<em>{toc.length}</em>
|
||||||
|
</h2>
|
||||||
|
<nav class="toc-list" aria-label="文章目录">
|
||||||
|
{toc.map((item) => (
|
||||||
|
<a class={`toc-depth-${item.depth}`} href={`#${item.id}`}>{item.text}</a>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<section class="sidebar-card same-category-card">
|
||||||
|
<h2>
|
||||||
|
<span aria-hidden="true">▣</span>
|
||||||
|
同类文章
|
||||||
|
<em>{relatedPosts.length}</em>
|
||||||
|
</h2>
|
||||||
|
{relatedPosts.length > 0 ? (
|
||||||
|
<div class="same-category-list">
|
||||||
|
{relatedPosts.map((item) => (
|
||||||
|
<a href={item.href}>
|
||||||
|
<strong>{item.title}</strong>
|
||||||
|
<time datetime={item.date.toISOString()}>{item.dateText}</time>
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<p class="sidebar-empty">这个分类下暂时没有其他文章。</p>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
</aside>
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
---
|
||||||
|
import type { BlogPost } from '@/lib/posts';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
posts: BlogPost[];
|
||||||
|
tags: string[];
|
||||||
|
categories: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const { posts, tags, categories } = Astro.props;
|
||||||
|
const avatar = 'https://free.picui.cn/free/2025/08/10/689845496a283.png';
|
||||||
|
const recentPosts = posts.slice(0, 5);
|
||||||
|
---
|
||||||
|
|
||||||
|
<aside class="home-sidebar" aria-label="首页侧边栏">
|
||||||
|
<section class="sidebar-card profile-card">
|
||||||
|
<img class="profile-avatar" src={avatar} alt="biss" loading="lazy" />
|
||||||
|
<div class="profile-name">biss</div>
|
||||||
|
<div class="profile-stats">
|
||||||
|
<a href="/archives/">
|
||||||
|
<span>文章</span>
|
||||||
|
<strong>{posts.length}</strong>
|
||||||
|
</a>
|
||||||
|
<a href="/tags/">
|
||||||
|
<span>标签</span>
|
||||||
|
<strong>{tags.length}</strong>
|
||||||
|
</a>
|
||||||
|
<a href="/categories/">
|
||||||
|
<span>分类</span>
|
||||||
|
<strong>{categories.length}</strong>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<a class="follow-button" href="https://github.com/bishshi" target="_blank" rel="noreferrer">
|
||||||
|
<span class="github-mark" aria-hidden="true">GitHub</span>
|
||||||
|
<span>Follow Me</span>
|
||||||
|
</a>
|
||||||
|
<div class="profile-links" aria-label="联系方式">
|
||||||
|
<a href="https://github.com/bishshi" target="_blank" rel="noreferrer" aria-label="GitHub">GitHub</a>
|
||||||
|
<a href="mailto:bishsh2006@gmail.com" aria-label="Email">Email</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="sidebar-card notice-card">
|
||||||
|
<h2><span aria-hidden="true">!</span> 公告</h2>
|
||||||
|
<p>欢迎来自 <strong id="visitor-location">远方</strong> 的小友。</p>
|
||||||
|
<p>烧烤三件套,灵魂蘸料。</p>
|
||||||
|
<p id="visitor-distance">当前位置距离主约 <strong>473</strong> 公里!</p>
|
||||||
|
<p id="aside-weather">当前天气加载中...</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="sticky-sidebar">
|
||||||
|
<section class="sidebar-card calendar-card">
|
||||||
|
<h2>日历</h2>
|
||||||
|
<div class="calendar-widget">
|
||||||
|
<div class="calendar-summary">
|
||||||
|
<strong id="calendar-week">第--周 周--</strong>
|
||||||
|
<span id="calendar-date">--</span>
|
||||||
|
<small id="calendar-solar">----年--月 第--天</small>
|
||||||
|
<small id="calendar-lunar">今天也很好</small>
|
||||||
|
</div>
|
||||||
|
<div class="calendar-grid" id="calendar-grid" aria-label="本月日历"></div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="sidebar-card schedule-card">
|
||||||
|
<h2>剩余时间</h2>
|
||||||
|
<div class="schedule-widget">
|
||||||
|
<div class="schedule-left">
|
||||||
|
<strong>距离除夕</strong>
|
||||||
|
<span id="new-year-days">--</span>
|
||||||
|
<small id="new-year-date">---- -- --</small>
|
||||||
|
</div>
|
||||||
|
<div class="schedule-bars">
|
||||||
|
<div class="schedule-row">
|
||||||
|
<span>本年</span>
|
||||||
|
<progress id="year-progress" max="100" value="0"></progress>
|
||||||
|
<em id="year-left">--天</em>
|
||||||
|
</div>
|
||||||
|
<div class="schedule-row">
|
||||||
|
<span>本月</span>
|
||||||
|
<progress id="month-progress" max="100" value="0"></progress>
|
||||||
|
<em id="month-left">--天</em>
|
||||||
|
</div>
|
||||||
|
<div class="schedule-row">
|
||||||
|
<span>本周</span>
|
||||||
|
<progress id="week-progress" max="100" value="0"></progress>
|
||||||
|
<em id="week-left">--天</em>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="sidebar-card recent-card">
|
||||||
|
<h2>最新文章</h2>
|
||||||
|
<div class="sidebar-posts">
|
||||||
|
{recentPosts.map((post) => (
|
||||||
|
<a class="sidebar-post" href={post.href}>
|
||||||
|
{post.cover ? (
|
||||||
|
<img src={post.cover} alt="" loading="lazy" />
|
||||||
|
) : (
|
||||||
|
<span class="post-thumb-fallback" aria-hidden="true"></span>
|
||||||
|
)}
|
||||||
|
<span>
|
||||||
|
<strong>{post.title}</strong>
|
||||||
|
<time datetime={post.date.toISOString()}>{post.dateText}</time>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<script is:inline>
|
||||||
|
(() => {
|
||||||
|
const dayNames = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
|
||||||
|
const pad = (value) => String(value).padStart(2, '0');
|
||||||
|
const percent = (used, total) => Math.min(100, Math.max(0, (used / total) * 100));
|
||||||
|
const setText = (id, value) => {
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
if (el) el.textContent = value;
|
||||||
|
};
|
||||||
|
|
||||||
|
function dayOfYear(date) {
|
||||||
|
const start = new Date(date.getFullYear(), 0, 1);
|
||||||
|
return Math.floor((date - start) / 86400000) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function weekOfYear(date) {
|
||||||
|
const firstDay = new Date(date.getFullYear(), 0, 1);
|
||||||
|
const pastDays = Math.floor((date - firstDay) / 86400000);
|
||||||
|
return Math.ceil((pastDays + firstDay.getDay() + 1) / 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderCalendar(now) {
|
||||||
|
setText('calendar-week', `第${weekOfYear(now)}周 ${dayNames[now.getDay()]}`);
|
||||||
|
setText('calendar-date', pad(now.getDate()));
|
||||||
|
setText('calendar-solar', `${now.getFullYear()}年${now.getMonth() + 1}月 第${dayOfYear(now)}天`);
|
||||||
|
setText('calendar-lunar', '万千风景,五历如一');
|
||||||
|
|
||||||
|
const grid = document.getElementById('calendar-grid');
|
||||||
|
if (!grid) return;
|
||||||
|
|
||||||
|
const year = now.getFullYear();
|
||||||
|
const month = now.getMonth();
|
||||||
|
const first = new Date(year, month, 1).getDay();
|
||||||
|
const days = new Date(year, month + 1, 0).getDate();
|
||||||
|
const previousDays = new Date(year, month, 0).getDate();
|
||||||
|
const cells = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < 42; i += 1) {
|
||||||
|
const dayNumber = i - first + 1;
|
||||||
|
const isCurrent = dayNumber >= 1 && dayNumber <= days;
|
||||||
|
const displayDay = isCurrent
|
||||||
|
? dayNumber
|
||||||
|
: dayNumber < 1
|
||||||
|
? previousDays + dayNumber
|
||||||
|
: dayNumber - days;
|
||||||
|
const isToday = isCurrent && displayDay === now.getDate();
|
||||||
|
cells.push(`<span class="${isCurrent ? '' : 'muted'} ${isToday ? 'today' : ''}">${displayDay}</span>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
grid.innerHTML = cells.join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSchedule(now) {
|
||||||
|
const year = now.getFullYear();
|
||||||
|
const nextEve = new Date(year + 1, 1, 16);
|
||||||
|
const daysToEve = Math.max(0, Math.ceil((nextEve - now) / 86400000));
|
||||||
|
const yearStart = new Date(year, 0, 1);
|
||||||
|
const yearEnd = new Date(year + 1, 0, 1);
|
||||||
|
const monthStart = new Date(year, now.getMonth(), 1);
|
||||||
|
const monthEnd = new Date(year, now.getMonth() + 1, 1);
|
||||||
|
const weekStart = new Date(year, now.getMonth(), now.getDate() - ((now.getDay() + 6) % 7));
|
||||||
|
const weekEnd = new Date(weekStart.getFullYear(), weekStart.getMonth(), weekStart.getDate() + 7);
|
||||||
|
|
||||||
|
setText('new-year-days', String(daysToEve));
|
||||||
|
setText('new-year-date', `${nextEve.getFullYear()}-${pad(nextEve.getMonth() + 1)}-${pad(nextEve.getDate())}`);
|
||||||
|
|
||||||
|
const rows = [
|
||||||
|
['year', yearStart, yearEnd],
|
||||||
|
['month', monthStart, monthEnd],
|
||||||
|
['week', weekStart, weekEnd],
|
||||||
|
];
|
||||||
|
|
||||||
|
rows.forEach(([name, start, end]) => {
|
||||||
|
const total = end - start;
|
||||||
|
const used = now - start;
|
||||||
|
const left = Math.max(0, Math.ceil((end - now) / 86400000));
|
||||||
|
const bar = document.getElementById(`${name}-progress`);
|
||||||
|
if (bar) bar.value = percent(used, total);
|
||||||
|
setText(`${name}-left`, `还剩${left}天`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function initSidebar() {
|
||||||
|
const now = new Date();
|
||||||
|
renderCalendar(now);
|
||||||
|
renderSchedule(now);
|
||||||
|
}
|
||||||
|
|
||||||
|
initSidebar();
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
<script is:inline src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
|
||||||
|
<script is:inline src="https://cdn.jsdmirror.com/gh/bishshi/welcomemessage/txmap.js"></script>
|
||||||
|
<script is:inline src="/js/weather.js"></script>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
interface Props {
|
||||||
|
title: string;
|
||||||
|
subtitle?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { title, subtitle } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<header class="page-header-card">
|
||||||
|
<p class="page-header-eyebrow">Bi's Blog</p>
|
||||||
|
<h1>{title}</h1>
|
||||||
|
{subtitle && <p class="page-header-subtitle">{subtitle}</p>}
|
||||||
|
</header>
|
||||||
@@ -13,6 +13,7 @@ export type BlogPost = MarkdownModule & {
|
|||||||
tags: string[];
|
tags: string[];
|
||||||
categories: string[];
|
categories: string[];
|
||||||
summary: string;
|
summary: string;
|
||||||
|
cover?: string;
|
||||||
href: string;
|
href: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -79,6 +80,7 @@ export function getAllPosts(): BlogPost[] {
|
|||||||
tags: asArray(module.frontmatter.tags),
|
tags: asArray(module.frontmatter.tags),
|
||||||
categories: asArray(module.frontmatter.categories),
|
categories: asArray(module.frontmatter.categories),
|
||||||
summary: makeSummary(module),
|
summary: makeSummary(module),
|
||||||
|
cover: typeof module.frontmatter.cover === 'string' ? module.frontmatter.cover : undefined,
|
||||||
href: `/posts/${slug}/`,
|
href: `/posts/${slug}/`,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
import BaseLayout from '@/layouts/BaseLayout.astro';
|
import BaseLayout from '@/layouts/BaseLayout.astro';
|
||||||
|
import PageHeaderCard from '@/components/PageHeaderCard.astro';
|
||||||
import { getSitePages } from '@/lib/pages';
|
import { getSitePages } from '@/lib/pages';
|
||||||
|
|
||||||
export function getStaticPaths() {
|
export function getStaticPaths() {
|
||||||
@@ -14,10 +15,10 @@ const { Content } = page;
|
|||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout title={page.title}>
|
<BaseLayout title={page.title}>
|
||||||
<article class="article">
|
<section class="content-wrap narrow page-with-card">
|
||||||
<header class="article-header">
|
<PageHeaderCard title={page.title} />
|
||||||
<h1>{page.title}</h1>
|
</section>
|
||||||
</header>
|
<article class="article page-article">
|
||||||
<div class="prose">
|
<div class="prose">
|
||||||
<Content />
|
<Content />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
import BaseLayout from '@/layouts/BaseLayout.astro';
|
import BaseLayout from '@/layouts/BaseLayout.astro';
|
||||||
|
import PageHeaderCard from '@/components/PageHeaderCard.astro';
|
||||||
import { getAllPosts } from '@/lib/posts';
|
import { getAllPosts } from '@/lib/posts';
|
||||||
|
|
||||||
const posts = getAllPosts();
|
const posts = getAllPosts();
|
||||||
@@ -7,7 +8,7 @@ const posts = getAllPosts();
|
|||||||
|
|
||||||
<BaseLayout title="归档">
|
<BaseLayout title="归档">
|
||||||
<section class="content-wrap narrow">
|
<section class="content-wrap narrow">
|
||||||
<h1>归档</h1>
|
<PageHeaderCard title="归档" subtitle={`共 ${posts.length} 篇文章`} />
|
||||||
<div class="archive-list">
|
<div class="archive-list">
|
||||||
{posts.map((post) => (
|
{posts.map((post) => (
|
||||||
<a href={post.href}>
|
<a href={post.href}>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
import BaseLayout from '@/layouts/BaseLayout.astro';
|
import BaseLayout from '@/layouts/BaseLayout.astro';
|
||||||
|
import PageHeaderCard from '@/components/PageHeaderCard.astro';
|
||||||
import PostCard from '@/components/PostCard.astro';
|
import PostCard from '@/components/PostCard.astro';
|
||||||
import { getAllCategories, getAllPosts } from '@/lib/posts';
|
import { getAllCategories, getAllPosts } from '@/lib/posts';
|
||||||
|
|
||||||
@@ -16,7 +17,7 @@ const posts = getAllPosts().filter((post) => post.categories.includes(category))
|
|||||||
|
|
||||||
<BaseLayout title={category}>
|
<BaseLayout title={category}>
|
||||||
<section class="content-wrap">
|
<section class="content-wrap">
|
||||||
<h1>{category}</h1>
|
<PageHeaderCard title={category} subtitle={`共 ${posts.length} 篇文章`} />
|
||||||
<div class="post-list">
|
<div class="post-list">
|
||||||
{posts.map((post) => <PostCard post={post} />)}
|
{posts.map((post) => <PostCard post={post} />)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
import BaseLayout from '@/layouts/BaseLayout.astro';
|
import BaseLayout from '@/layouts/BaseLayout.astro';
|
||||||
|
import PageHeaderCard from '@/components/PageHeaderCard.astro';
|
||||||
import { getAllCategories, getAllPosts } from '@/lib/posts';
|
import { getAllCategories, getAllPosts } from '@/lib/posts';
|
||||||
|
|
||||||
const posts = getAllPosts();
|
const posts = getAllPosts();
|
||||||
@@ -8,7 +9,7 @@ const categories = getAllCategories();
|
|||||||
|
|
||||||
<BaseLayout title="分类">
|
<BaseLayout title="分类">
|
||||||
<section class="content-wrap narrow">
|
<section class="content-wrap narrow">
|
||||||
<h1>分类</h1>
|
<PageHeaderCard title="分类" subtitle={`共 ${categories.length} 个分类`} />
|
||||||
<div class="term-grid">
|
<div class="term-grid">
|
||||||
{categories.map((category) => (
|
{categories.map((category) => (
|
||||||
<a href={`/categories/${encodeURIComponent(category)}/`}>
|
<a href={`/categories/${encodeURIComponent(category)}/`}>
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
---
|
---
|
||||||
import BaseLayout from '@/layouts/BaseLayout.astro';
|
import BaseLayout from '@/layouts/BaseLayout.astro';
|
||||||
|
import HomeSidebar from '@/components/HomeSidebar.astro';
|
||||||
import PostCard from '@/components/PostCard.astro';
|
import PostCard from '@/components/PostCard.astro';
|
||||||
import { getAllPosts } from '@/lib/posts';
|
import { getAllCategories, getAllPosts, getAllTags } from '@/lib/posts';
|
||||||
|
|
||||||
const posts = getAllPosts();
|
const posts = getAllPosts();
|
||||||
|
const tags = getAllTags();
|
||||||
|
const categories = getAllCategories();
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout>
|
<BaseLayout>
|
||||||
@@ -14,7 +17,8 @@ const posts = getAllPosts();
|
|||||||
<p>把折腾、学习和生活片段安静地收在这里。</p>
|
<p>把折腾、学习和生活片段安静地收在这里。</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section class="content-wrap">
|
<section class="content-wrap home-layout">
|
||||||
|
<div class="home-main">
|
||||||
<div class="section-heading">
|
<div class="section-heading">
|
||||||
<h2>最新文章</h2>
|
<h2>最新文章</h2>
|
||||||
<a href="/archives/">查看全部</a>
|
<a href="/archives/">查看全部</a>
|
||||||
@@ -22,5 +26,7 @@ const posts = getAllPosts();
|
|||||||
<div class="post-list">
|
<div class="post-list">
|
||||||
{posts.slice(0, 16).map((post) => <PostCard post={post} />)}
|
{posts.slice(0, 16).map((post) => <PostCard post={post} />)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<HomeSidebar posts={posts} tags={tags} categories={categories} />
|
||||||
</section>
|
</section>
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
import ArticleSidebar from '@/components/ArticleSidebar.astro';
|
||||||
import BaseLayout from '@/layouts/BaseLayout.astro';
|
import BaseLayout from '@/layouts/BaseLayout.astro';
|
||||||
import { getAllPosts } from '@/lib/posts';
|
import { getAllPosts } from '@/lib/posts';
|
||||||
|
|
||||||
@@ -11,10 +12,62 @@ export function getStaticPaths() {
|
|||||||
|
|
||||||
const { post } = Astro.props;
|
const { post } = Astro.props;
|
||||||
const { Content } = post;
|
const { Content } = post;
|
||||||
|
const allPosts = getAllPosts();
|
||||||
|
|
||||||
|
function slugifyHeading(value: string): string {
|
||||||
|
return value
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/<[^>]+>/g, '')
|
||||||
|
.replace(/[`*_~[\]()]/g, '')
|
||||||
|
.replace(/[^\p{Letter}\p{Number}\s-]/gu, '')
|
||||||
|
.replace(/\s+/g, '-')
|
||||||
|
.replace(/-+/g, '-')
|
||||||
|
.replace(/^-|-$/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeToc(raw: string) {
|
||||||
|
const used = new Map<string, number>();
|
||||||
|
let inFence = false;
|
||||||
|
|
||||||
|
return raw
|
||||||
|
.split(/\r?\n/)
|
||||||
|
.flatMap((line) => {
|
||||||
|
if (/^\s*```/.test(line)) {
|
||||||
|
inFence = !inFence;
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inFence) return [];
|
||||||
|
|
||||||
|
const match = /^(#{1,3})\s+(.+?)\s*#*$/.exec(line);
|
||||||
|
if (!match) return [];
|
||||||
|
|
||||||
|
const text = match[2].replace(/<[^>]+>/g, '').trim();
|
||||||
|
if (!text) return [];
|
||||||
|
|
||||||
|
const baseId = slugifyHeading(text) || 'section';
|
||||||
|
const count = used.get(baseId) ?? 0;
|
||||||
|
used.set(baseId, count + 1);
|
||||||
|
|
||||||
|
return [{
|
||||||
|
depth: match[1].length,
|
||||||
|
text,
|
||||||
|
id: count === 0 ? baseId : `${baseId}-${count}`,
|
||||||
|
}];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const toc = makeToc(post.rawContent?.() ?? '');
|
||||||
|
const relatedPosts = allPosts
|
||||||
|
.filter((item) => item.slug !== post.slug)
|
||||||
|
.filter((item) => item.categories.some((category) => post.categories.includes(category)))
|
||||||
|
.slice(0, 8);
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout title={post.title} description={post.summary}>
|
<BaseLayout title={post.title} description={post.summary}>
|
||||||
<article class="article">
|
<section class="post-layout">
|
||||||
|
<article class="article post-article">
|
||||||
<header class="article-header">
|
<header class="article-header">
|
||||||
<time datetime={post.date.toISOString()}>{post.dateText}</time>
|
<time datetime={post.date.toISOString()}>{post.dateText}</time>
|
||||||
<h1>{post.title}</h1>
|
<h1>{post.title}</h1>
|
||||||
@@ -23,8 +76,29 @@ const { Content } = post;
|
|||||||
{post.tags.map((tag) => <a href={`/tags/${encodeURIComponent(tag)}/`}>#{tag}</a>)}
|
{post.tags.map((tag) => <a href={`/tags/${encodeURIComponent(tag)}/`}>#{tag}</a>)}
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div class="prose">
|
<div class="prose" data-toc={JSON.stringify(toc)}>
|
||||||
<Content />
|
<Content />
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
<ArticleSidebar post={post} toc={toc} relatedPosts={relatedPosts} />
|
||||||
|
</section>
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
|
||||||
|
<script is:inline>
|
||||||
|
(() => {
|
||||||
|
const prose = document.querySelector('.prose[data-toc]');
|
||||||
|
if (!prose) return;
|
||||||
|
|
||||||
|
let toc = [];
|
||||||
|
try {
|
||||||
|
toc = JSON.parse(prose.dataset.toc || '[]');
|
||||||
|
} catch {
|
||||||
|
toc = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
prose.querySelectorAll('h1, h2, h3').forEach((heading, index) => {
|
||||||
|
const item = toc[index];
|
||||||
|
if (item?.id && !heading.id) heading.id = item.id;
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
import BaseLayout from '@/layouts/BaseLayout.astro';
|
import BaseLayout from '@/layouts/BaseLayout.astro';
|
||||||
|
import PageHeaderCard from '@/components/PageHeaderCard.astro';
|
||||||
import PostCard from '@/components/PostCard.astro';
|
import PostCard from '@/components/PostCard.astro';
|
||||||
import { getAllPosts, getAllTags } from '@/lib/posts';
|
import { getAllPosts, getAllTags } from '@/lib/posts';
|
||||||
|
|
||||||
@@ -16,7 +17,7 @@ const posts = getAllPosts().filter((post) => post.tags.includes(tag));
|
|||||||
|
|
||||||
<BaseLayout title={tag}>
|
<BaseLayout title={tag}>
|
||||||
<section class="content-wrap">
|
<section class="content-wrap">
|
||||||
<h1>#{tag}</h1>
|
<PageHeaderCard title={`#${tag}`} subtitle={`共 ${posts.length} 篇文章`} />
|
||||||
<div class="post-list">
|
<div class="post-list">
|
||||||
{posts.map((post) => <PostCard post={post} />)}
|
{posts.map((post) => <PostCard post={post} />)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
import BaseLayout from '@/layouts/BaseLayout.astro';
|
import BaseLayout from '@/layouts/BaseLayout.astro';
|
||||||
|
import PageHeaderCard from '@/components/PageHeaderCard.astro';
|
||||||
import { getAllPosts, getAllTags } from '@/lib/posts';
|
import { getAllPosts, getAllTags } from '@/lib/posts';
|
||||||
|
|
||||||
const posts = getAllPosts();
|
const posts = getAllPosts();
|
||||||
@@ -8,7 +9,7 @@ const tags = getAllTags();
|
|||||||
|
|
||||||
<BaseLayout title="标签">
|
<BaseLayout title="标签">
|
||||||
<section class="content-wrap narrow">
|
<section class="content-wrap narrow">
|
||||||
<h1>标签</h1>
|
<PageHeaderCard title="标签" subtitle={`共 ${tags.length} 个标签`} />
|
||||||
<div class="term-grid">
|
<div class="term-grid">
|
||||||
{tags.map((tag) => (
|
{tags.map((tag) => (
|
||||||
<a href={`/tags/${encodeURIComponent(tag)}/`}>
|
<a href={`/tags/${encodeURIComponent(tag)}/`}>
|
||||||
|
|||||||
+620
-17
@@ -8,6 +8,11 @@
|
|||||||
--accent: #0f766e;
|
--accent: #0f766e;
|
||||||
--accent-strong: #14532d;
|
--accent-strong: #14532d;
|
||||||
--shadow: 0 14px 45px rgba(58, 51, 38, 0.1);
|
--shadow: 0 14px 45px rgba(58, 51, 38, 0.1);
|
||||||
|
--page-gutter: clamp(20px, 4vw, 64px);
|
||||||
|
--wide-width: 1440px;
|
||||||
|
--content-width: 1280px;
|
||||||
|
--article-width: 1080px;
|
||||||
|
--narrow-width: 1000px;
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
@@ -33,45 +38,106 @@ a {
|
|||||||
|
|
||||||
.site-header {
|
.site-header {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 12px;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
border-bottom: 1px solid rgba(222, 216, 204, 0.8);
|
pointer-events: none;
|
||||||
background: rgba(255, 253, 248, 0.88);
|
|
||||||
backdrop-filter: blur(14px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav {
|
.nav {
|
||||||
width: min(1120px, calc(100% - 32px));
|
position: relative;
|
||||||
min-height: 64px;
|
width: min(var(--wide-width), calc(100% - (var(--page-gutter) * 2)));
|
||||||
|
min-height: 58px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
padding: 0 18px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 24px;
|
gap: 24px;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.56);
|
||||||
|
border-radius: 999px;
|
||||||
|
background:
|
||||||
|
linear-gradient(135deg, rgba(255, 255, 255, 0.52), rgba(255, 255, 255, 0.2)),
|
||||||
|
rgba(255, 255, 255, 0.28);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.82),
|
||||||
|
inset 0 -18px 38px rgba(255, 255, 255, 0.16),
|
||||||
|
0 18px 54px rgba(44, 55, 63, 0.18);
|
||||||
|
backdrop-filter: blur(22px) saturate(180%);
|
||||||
|
-webkit-backdrop-filter: blur(22px) saturate(180%);
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 18% 0%, rgba(255, 255, 255, 0.72), transparent 30%),
|
||||||
|
linear-gradient(90deg, rgba(255, 255, 255, 0.42), transparent 38%, rgba(255, 255, 255, 0.22));
|
||||||
|
opacity: 0.74;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 7px;
|
||||||
|
right: 16px;
|
||||||
|
left: 16px;
|
||||||
|
height: 1px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(255, 255, 255, 0.78);
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.brand {
|
.brand {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
font-size: 1.05rem;
|
font-size: 1.05rem;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-links {
|
.nav-links {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 18px;
|
gap: 6px;
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
font-size: 0.94rem;
|
font-size: 0.94rem;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nav-links a {
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 7px 12px;
|
||||||
|
transition:
|
||||||
|
color 0.2s ease,
|
||||||
|
background-color 0.2s ease,
|
||||||
|
border-color 0.2s ease,
|
||||||
|
box-shadow 0.2s ease,
|
||||||
|
transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
.nav-links a:hover,
|
.nav-links a:hover,
|
||||||
.site-footer a:hover,
|
.site-footer a:hover,
|
||||||
.section-heading a:hover {
|
.section-heading a:hover {
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nav-links a:hover {
|
||||||
|
border-color: rgba(255, 255, 255, 0.62);
|
||||||
|
background: rgba(255, 255, 255, 0.38);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.72),
|
||||||
|
0 8px 18px rgba(15, 118, 110, 0.1);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
.hero {
|
.hero {
|
||||||
width: min(1120px, calc(100% - 32px));
|
width: min(var(--wide-width), calc(100% - (var(--page-gutter) * 2)));
|
||||||
min-height: 360px;
|
min-height: 360px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -99,12 +165,96 @@ a {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.content-wrap {
|
.content-wrap {
|
||||||
width: min(980px, calc(100% - 32px));
|
width: min(var(--content-width), calc(100% - (var(--page-gutter) * 2)));
|
||||||
margin: 0 auto 72px;
|
margin: 0 auto 72px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-wrap.narrow {
|
.content-wrap.narrow {
|
||||||
width: min(780px, calc(100% - 32px));
|
width: min(var(--narrow-width), calc(100% - (var(--page-gutter) * 2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header-card {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 40px 0 26px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.58);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: clamp(26px, 5vw, 44px);
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 18% 0%, rgba(255, 255, 255, 0.78), transparent 36%),
|
||||||
|
linear-gradient(135deg, rgba(255, 255, 255, 0.58), rgba(238, 250, 243, 0.36)),
|
||||||
|
rgba(255, 253, 248, 0.72);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.82),
|
||||||
|
0 18px 48px rgba(58, 51, 38, 0.12);
|
||||||
|
backdrop-filter: blur(18px) saturate(165%);
|
||||||
|
-webkit-backdrop-filter: blur(18px) saturate(165%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header-card::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 18px;
|
||||||
|
left: 18px;
|
||||||
|
height: 1px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(255, 255, 255, 0.76);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header-eyebrow {
|
||||||
|
margin: 0 0 8px;
|
||||||
|
color: var(--accent);
|
||||||
|
font-size: 0.86rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header-card h1,
|
||||||
|
.content-wrap .page-header-card h1 {
|
||||||
|
margin: 0;
|
||||||
|
color: #20242a;
|
||||||
|
font-size: clamp(2rem, 5vw, 3.6rem);
|
||||||
|
line-height: 1.08;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header-subtitle {
|
||||||
|
max-width: 620px;
|
||||||
|
margin: 12px 0 0;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-with-card {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-article {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-layout {
|
||||||
|
width: min(var(--wide-width), calc(100% - (var(--page-gutter) * 2)));
|
||||||
|
margin: 40px auto 84px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) 320px;
|
||||||
|
align-items: start;
|
||||||
|
gap: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-layout .article {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-layout {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) 320px;
|
||||||
|
align-items: start;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-main {
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-heading {
|
.section-heading {
|
||||||
@@ -150,6 +300,396 @@ a {
|
|||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.home-sidebar {
|
||||||
|
display: grid;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sticky-sidebar {
|
||||||
|
position: sticky;
|
||||||
|
top: 84px;
|
||||||
|
display: grid;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-card {
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid rgba(222, 216, 204, 0.72);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 18px;
|
||||||
|
background:
|
||||||
|
linear-gradient(135deg, rgba(255, 255, 255, 0.72), rgba(242, 251, 237, 0.78)),
|
||||||
|
rgba(255, 253, 248, 0.76);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-card h2 {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin: 0 0 14px;
|
||||||
|
color: #4d5258;
|
||||||
|
font-size: 1.08rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-card h2 em {
|
||||||
|
margin-left: auto;
|
||||||
|
min-width: 28px;
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
color: #6d9cc2;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-style: normal;
|
||||||
|
text-align: center;
|
||||||
|
background: rgba(102, 200, 245, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-card {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-avatar {
|
||||||
|
width: 112px;
|
||||||
|
height: 112px;
|
||||||
|
margin: 0 auto 14px;
|
||||||
|
border-radius: 999px;
|
||||||
|
object-fit: cover;
|
||||||
|
box-shadow: 0 12px 30px rgba(15, 118, 110, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-name {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
font-family: Georgia, "Times New Roman", serif;
|
||||||
|
color: #585b5e;
|
||||||
|
font-size: 1.9rem;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-stats {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-stats a {
|
||||||
|
display: grid;
|
||||||
|
gap: 3px;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-stats strong {
|
||||||
|
color: #2f4358;
|
||||||
|
font-size: 1.22rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-stats a:first-child strong,
|
||||||
|
.notice-card strong,
|
||||||
|
.schedule-left span,
|
||||||
|
.calendar-summary span {
|
||||||
|
color: #66c8f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.follow-button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 10px;
|
||||||
|
min-height: 44px;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 700;
|
||||||
|
background: #3eb8be;
|
||||||
|
transition:
|
||||||
|
transform 0.2s ease,
|
||||||
|
background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.follow-button:hover {
|
||||||
|
color: #fff;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
background: #279fa5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.github-mark {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-links {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 18px;
|
||||||
|
margin-top: 16px;
|
||||||
|
color: #3f5368;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-card p {
|
||||||
|
margin: 0.65em 0;
|
||||||
|
color: #596068;
|
||||||
|
line-height: 1.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-widget,
|
||||||
|
.schedule-widget {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 0.9fr 1.1fr;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-summary,
|
||||||
|
.schedule-left {
|
||||||
|
display: grid;
|
||||||
|
gap: 4px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-summary strong,
|
||||||
|
.schedule-left strong {
|
||||||
|
color: #4b5560;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-summary span,
|
||||||
|
.schedule-left span {
|
||||||
|
font-size: 2.25rem;
|
||||||
|
line-height: 1.1;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-summary small,
|
||||||
|
.schedule-left small {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 0.78rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(7, minmax(0, 1fr));
|
||||||
|
gap: 5px 7px;
|
||||||
|
color: #8aa6be;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-grid span {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
min-height: 20px;
|
||||||
|
border-radius: 999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-grid .muted {
|
||||||
|
opacity: 0.45;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-grid .today {
|
||||||
|
color: #fff;
|
||||||
|
background: #66c8f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.schedule-bars {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.schedule-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 34px 1fr 64px;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 0.78rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.schedule-row progress {
|
||||||
|
width: 100%;
|
||||||
|
height: 18px;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: rgba(102, 200, 245, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.schedule-row progress::-webkit-progress-bar {
|
||||||
|
background: rgba(102, 200, 245, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.schedule-row progress::-webkit-progress-value {
|
||||||
|
background: rgba(102, 200, 245, 0.58);
|
||||||
|
}
|
||||||
|
|
||||||
|
.schedule-row progress::-moz-progress-bar {
|
||||||
|
background: rgba(102, 200, 245, 0.58);
|
||||||
|
}
|
||||||
|
|
||||||
|
.schedule-row em {
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-posts {
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-post {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 70px minmax(0, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12px 0;
|
||||||
|
border-top: 1px dashed rgba(104, 111, 120, 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-post:first-child {
|
||||||
|
border-top: 0;
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-post img,
|
||||||
|
.post-thumb-fallback {
|
||||||
|
width: 70px;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
border-radius: 6px;
|
||||||
|
object-fit: cover;
|
||||||
|
background: linear-gradient(135deg, rgba(62, 184, 190, 0.32), rgba(102, 200, 245, 0.34));
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-post strong {
|
||||||
|
display: -webkit-box;
|
||||||
|
overflow: hidden;
|
||||||
|
color: #4d5258;
|
||||||
|
font-size: 0.94rem;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.45;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-post time {
|
||||||
|
display: block;
|
||||||
|
margin-top: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-sidebar {
|
||||||
|
position: sticky;
|
||||||
|
top: 90px;
|
||||||
|
display: grid;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-author-card {
|
||||||
|
text-align: center;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 50% 0%, rgba(102, 200, 245, 0.32), transparent 42%),
|
||||||
|
linear-gradient(135deg, rgba(255, 255, 255, 0.58), rgba(214, 238, 255, 0.3)),
|
||||||
|
rgba(255, 253, 248, 0.62);
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-author-card .profile-avatar {
|
||||||
|
width: 88px;
|
||||||
|
height: 88px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-author-card .profile-name {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||||
|
font-size: 1.45rem;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-author-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 0.86rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-author-meta a {
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toc-list,
|
||||||
|
.same-category-list {
|
||||||
|
display: grid;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toc-list {
|
||||||
|
position: relative;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toc-list::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 5px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background-image: radial-gradient(circle, rgba(102, 200, 245, 0.72) 2px, transparent 2px);
|
||||||
|
background-size: 14px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toc-list a,
|
||||||
|
.same-category-list a {
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 9px 11px;
|
||||||
|
color: #50637a;
|
||||||
|
line-height: 1.45;
|
||||||
|
transition:
|
||||||
|
color 0.2s ease,
|
||||||
|
background-color 0.2s ease,
|
||||||
|
transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toc-list a:hover,
|
||||||
|
.same-category-list a:hover {
|
||||||
|
color: #1f618d;
|
||||||
|
background: rgba(102, 200, 245, 0.16);
|
||||||
|
transform: translateX(2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toc-depth-2 {
|
||||||
|
padding-left: 22px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toc-depth-3 {
|
||||||
|
padding-left: 34px !important;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.same-category-list a {
|
||||||
|
display: grid;
|
||||||
|
gap: 3px;
|
||||||
|
border-top: 1px dashed rgba(104, 111, 120, 0.22);
|
||||||
|
}
|
||||||
|
|
||||||
|
.same-category-list a:first-child {
|
||||||
|
border-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.same-category-list strong {
|
||||||
|
display: -webkit-box;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 0.94rem;
|
||||||
|
font-weight: 600;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-empty {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 0.92rem;
|
||||||
|
}
|
||||||
|
|
||||||
time {
|
time {
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
@@ -172,13 +712,20 @@ time {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.article {
|
.article {
|
||||||
width: min(820px, calc(100% - 32px));
|
width: min(var(--article-width), calc(100% - (var(--page-gutter) * 2)));
|
||||||
margin: 64px auto 84px;
|
margin: 64px auto 84px;
|
||||||
border: 1px solid var(--line);
|
border: 1px solid rgba(255, 255, 255, 0.58);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: clamp(24px, 5vw, 48px);
|
padding: clamp(24px, 5vw, 48px);
|
||||||
background: rgba(255, 253, 248, 0.93);
|
background:
|
||||||
box-shadow: var(--shadow);
|
radial-gradient(circle at 18% 0%, rgba(255, 255, 255, 0.72), transparent 34%),
|
||||||
|
linear-gradient(145deg, rgba(255, 255, 255, 0.62), rgba(226, 248, 241, 0.35)),
|
||||||
|
rgba(255, 253, 248, 0.68);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.82),
|
||||||
|
0 24px 64px rgba(44, 55, 63, 0.16);
|
||||||
|
backdrop-filter: blur(18px) saturate(165%);
|
||||||
|
-webkit-backdrop-filter: blur(18px) saturate(165%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.article-header {
|
.article-header {
|
||||||
@@ -194,6 +741,7 @@ time {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.prose :where(h2, h3, h4) {
|
.prose :where(h2, h3, h4) {
|
||||||
|
scroll-margin-top: 110px;
|
||||||
margin-top: 2em;
|
margin-top: 2em;
|
||||||
line-height: 1.35;
|
line-height: 1.35;
|
||||||
}
|
}
|
||||||
@@ -256,7 +804,7 @@ time {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.site-footer {
|
.site-footer {
|
||||||
width: min(1120px, calc(100% - 32px));
|
width: min(var(--wide-width), calc(100% - (var(--page-gutter) * 2)));
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 32px 0 44px;
|
padding: 32px 0 44px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -265,25 +813,80 @@ time {
|
|||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (min-width: 960px) {
|
||||||
|
.post-list {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1100px) {
|
||||||
|
.post-layout {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-sidebar {
|
||||||
|
position: static;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.toc-card {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-layout {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-sidebar {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.sticky-sidebar {
|
||||||
|
position: static;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 720px) {
|
@media (max-width: 720px) {
|
||||||
|
:root {
|
||||||
|
--page-gutter: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.nav {
|
.nav {
|
||||||
min-height: auto;
|
min-height: auto;
|
||||||
padding: 14px 0;
|
padding: 12px 14px;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
border-radius: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-links {
|
.nav-links {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
gap: 14px;
|
gap: 6px;
|
||||||
padding-bottom: 2px;
|
padding-bottom: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nav-links a {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
.hero {
|
.hero {
|
||||||
min-height: 300px;
|
min-height: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.home-sidebar {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-widget,
|
||||||
|
.schedule-widget {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-sidebar {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
.archive-list a {
|
.archive-list a {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
|
|||||||
Reference in New Issue
Block a user