mirror of
https://github.com/ClovertaTheTrilobita/SanYeCao-blog.git
synced 2026-10-25 13:33:53 +00:00
refactor overall style
This commit is contained in:
parent
0c39b1c120
commit
5ecebe00ec
19 changed files with 626 additions and 161 deletions
|
|
@ -5,6 +5,7 @@ import svelte from "@astrojs/svelte";
|
|||
import rehypeMermaid from "rehype-mermaid";
|
||||
import remarkMath from "remark-math";
|
||||
import rehypeKatex from "rehype-katex";
|
||||
import rehypeImageCaptions from "./src/plugins/rehype-image-captions.mjs";
|
||||
// https://astro.build/config
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
|
|
@ -27,6 +28,7 @@ export default defineConfig({
|
|||
excludeLangs: ['mermaid', 'math'],
|
||||
},
|
||||
rehypePlugins: [
|
||||
rehypeImageCaptions,
|
||||
rehypeMermaid,
|
||||
rehypeKatex,
|
||||
],
|
||||
|
|
|
|||
|
|
@ -62,14 +62,13 @@
|
|||
border-right: var(--deep-blue) 2.5px solid;
|
||||
border-bottom: var(--deep-blue) 2.5px solid;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
background: color-mix(in srgb, var(--background-color) 94%, transparent);
|
||||
color: #222;
|
||||
/* box-shadow: 0 4px 14px rgba(0, 0, 0, 0.16); */
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
backdrop-filter: blur(6px);
|
||||
}
|
||||
|
||||
#theme-toggle-fab {
|
||||
|
|
@ -141,10 +140,16 @@
|
|||
|
||||
:global(.dark) #theme-toggle-fab,
|
||||
:global(.dark) #back-to-top {
|
||||
background: rgba(34, 34, 34, 0.92);
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--background-color-dark) 94%,
|
||||
transparent
|
||||
);
|
||||
color: #f5f5f5;
|
||||
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.32);
|
||||
border: #515151 2px solid;
|
||||
border-top-color: #515151;
|
||||
border-left-color: #515151;
|
||||
border-right-color: #515151;
|
||||
border-bottom-color: #515151;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ const t = getTranslations(lang);
|
|||
{t.friends.content.map((line: string) => <p set:html={line} />)}
|
||||
</div>
|
||||
<div class="friend-code-block">
|
||||
<pre><code>---
|
||||
<pre data-language="yaml"><code class="language-yaml">---
|
||||
name: 'CLoverta的博客'
|
||||
description: "欢迎光临,请进门左转"
|
||||
url: "https://blog.cloverta.top"
|
||||
|
|
@ -59,27 +59,35 @@ avatar: 'https://s2.loli.net/2025/11/22/tiDKuzdqycx1v9B.png'
|
|||
}
|
||||
|
||||
.friend-code-block {
|
||||
background: #fbf5f2;
|
||||
border: 1px solid #8fa1ad;
|
||||
border-radius: 3px;
|
||||
padding: 0rem;
|
||||
overflow-x: auto;
|
||||
/* color: var(--text-color); */
|
||||
min-width: 0;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
:global(.dark) .friend-code-block {
|
||||
background: rgba(127, 127, 127, 0.12);
|
||||
.friend-code-block :global(.code-block-wrapper) {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
border: 1px solid #8fa1ad;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.friend-code-block pre {
|
||||
margin: 0;
|
||||
background: var(--comment-background);
|
||||
}
|
||||
|
||||
.friend-code-block code {
|
||||
font-family: monospace;
|
||||
font-family: "Maple Mono", monospace;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
:global(.dark) .friend-code-block :global(.code-block-wrapper) {
|
||||
border-color: rgba(170, 183, 196, 0.45);
|
||||
}
|
||||
|
||||
:global(.dark) .friend-code-block pre {
|
||||
background: rgba(127, 127, 127, 0.12);
|
||||
}
|
||||
|
||||
.image-wrap {
|
||||
width: fit-content;
|
||||
margin: 0 auto;
|
||||
|
|
|
|||
|
|
@ -65,6 +65,13 @@ const heading = lang === "zh" ? "最新评论" : "Latest comments";
|
|||
}
|
||||
}
|
||||
|
||||
function getDiscussionUrl(url, lang) {
|
||||
const localizedUrl = rewritePostUrl(url, lang);
|
||||
if (!localizedUrl || localizedUrl === "#") return "#";
|
||||
|
||||
return `${localizedUrl.split("#")[0]}#comments`;
|
||||
}
|
||||
|
||||
function getAvatar(item) {
|
||||
return (
|
||||
item?.user?.picture || item?.user?.photo || item?.user?.avatar || ""
|
||||
|
|
@ -226,6 +233,9 @@ const heading = lang === "zh" ? "最新评论" : "Latest comments";
|
|||
const href = escapeHtml(
|
||||
rewritePostUrl(item?.locator?.url || "", lang),
|
||||
);
|
||||
const discussionHref = escapeHtml(
|
||||
getDiscussionUrl(item?.locator?.url || "", lang),
|
||||
);
|
||||
|
||||
const preview = getCommentPreview(item?.text, lang);
|
||||
const time = formatTime(item?.time, lang);
|
||||
|
|
@ -260,8 +270,13 @@ const heading = lang === "zh" ? "最新评论" : "Latest comments";
|
|||
<div class="comment-card-text">${preview}</div>
|
||||
|
||||
<footer class="comment-source">
|
||||
<div class="comment-source-context">
|
||||
<span>${lang === "zh" ? "评论于" : "On"}</span>
|
||||
<a class="comment-title-link" href="${href}">${title}</a>
|
||||
</div>
|
||||
<a class="comment-discussion-link" href="${discussionHref}">${
|
||||
lang === "zh" ? "查看完整讨论" : "View discussion"
|
||||
}</a>
|
||||
</footer>
|
||||
</div>
|
||||
</article>
|
||||
|
|
@ -269,6 +284,18 @@ const heading = lang === "zh" ? "最新评论" : "Latest comments";
|
|||
})
|
||||
.join("");
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
list.querySelectorAll(".comment-card").forEach((card) => {
|
||||
const preview = card.querySelector(".comment-card-text");
|
||||
if (!preview) return;
|
||||
|
||||
card.classList.toggle(
|
||||
"is-truncated",
|
||||
preview.scrollHeight > preview.clientHeight + 1,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
loading.style.display = "none";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -83,6 +83,9 @@ const tocHeadings = headings.filter((h) => h.depth === 2 || h.depth === 3);
|
|||
<style>
|
||||
.post-menu {
|
||||
box-sizing: border-box;
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.post-menu-title {
|
||||
|
|
@ -125,6 +128,7 @@ const tocHeadings = headings.filter((h) => h.depth === 2 || h.depth === 3);
|
|||
|
||||
.post-menu-item {
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
margin: 0.42rem 0;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
|
@ -136,27 +140,41 @@ const tocHeadings = headings.filter((h) => h.depth === 2 || h.depth === 3);
|
|||
}
|
||||
|
||||
.post-menu a {
|
||||
display: block;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
padding: 0.08rem 0.35rem 0.08rem 0.55rem;
|
||||
border-left: 2px solid transparent;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
transition:
|
||||
border-color 0.16s ease,
|
||||
color 0.16s ease;
|
||||
}
|
||||
|
||||
.post-menu a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.post-menu a.is-current {
|
||||
border-left-color: var(--deep-red);
|
||||
color: var(--url-color);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.post-menu-mobile-toggle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.post-menu {
|
||||
--content-width: 90ch;
|
||||
--content-width: var(--page-content-width);
|
||||
--menu-width: clamp(
|
||||
140px,
|
||||
calc((100vw - 1200px) * 0.42 + 140px),
|
||||
260px
|
||||
);
|
||||
--menu-gap: 0rem;
|
||||
--menu-gap: clamp(1.5rem, 2vw, 2rem);
|
||||
|
||||
position: fixed;
|
||||
top: 8.5rem;
|
||||
|
|
@ -168,7 +186,9 @@ const tocHeadings = headings.filter((h) => h.depth === 2 || h.depth === 3);
|
|||
);
|
||||
|
||||
max-height: calc(100vh - 10rem);
|
||||
overflow: auto;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
padding: 0.2rem 0.2rem 0.2rem 0;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
|
@ -187,8 +207,11 @@ const tocHeadings = headings.filter((h) => h.depth === 2 || h.depth === 3);
|
|||
padding: 0.6rem 0.75rem;
|
||||
border: 1.5px solid var(--deep-blue);
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
backdrop-filter: blur(6px);
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--background-color) 94%,
|
||||
transparent
|
||||
);
|
||||
color: inherit;
|
||||
font-size: 0.85rem;
|
||||
cursor: pointer;
|
||||
|
|
@ -232,11 +255,16 @@ const tocHeadings = headings.filter((h) => h.depth === 2 || h.depth === 3);
|
|||
z-index: 29;
|
||||
width: min(88vw, 24rem);
|
||||
max-height: min(65vh, 32rem);
|
||||
overflow: auto;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
padding: 0.9rem 1rem;
|
||||
border: 1.5px solid var(--deep-blue);
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
backdrop-filter: blur(8px);
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--background-color) 97%,
|
||||
transparent
|
||||
);
|
||||
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
|
|
@ -329,8 +357,6 @@ const tocHeadings = headings.filter((h) => h.depth === 2 || h.depth === 3);
|
|||
};
|
||||
|
||||
const updateCurrentHeading = () => {
|
||||
if (window.innerWidth >= 1200) return;
|
||||
|
||||
headingItems = getHeadingElements();
|
||||
|
||||
let current = null;
|
||||
|
|
@ -347,10 +373,16 @@ const tocHeadings = headings.filter((h) => h.depth === 2 || h.depth === 3);
|
|||
|
||||
preview.textContent = current ? current.text : defaultText;
|
||||
|
||||
headingLinks.forEach((link) =>
|
||||
link.classList.remove("is-current"),
|
||||
);
|
||||
if (current) current.link.classList.add("is-current");
|
||||
headingLinks.forEach((link) => {
|
||||
const isCurrent = link === current?.link;
|
||||
link.classList.toggle("is-current", isCurrent);
|
||||
|
||||
if (isCurrent) {
|
||||
link.setAttribute("aria-current", "location");
|
||||
} else {
|
||||
link.removeAttribute("aria-current");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const toggleMenu = () => {
|
||||
|
|
@ -377,7 +409,11 @@ const tocHeadings = headings.filter((h) => h.depth === 2 || h.depth === 3);
|
|||
e.preventDefault();
|
||||
|
||||
el.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
behavior: window.matchMedia(
|
||||
"(prefers-reduced-motion: reduce)",
|
||||
).matches
|
||||
? "auto"
|
||||
: "smooth",
|
||||
block: "start",
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,33 @@ const {
|
|||
<FloatingActions />
|
||||
<script is:inline>
|
||||
function addCodeCopyButtons() {
|
||||
const languageNames = {
|
||||
js: "JavaScript",
|
||||
javascript: "JavaScript",
|
||||
ts: "TypeScript",
|
||||
typescript: "TypeScript",
|
||||
jsx: "JSX",
|
||||
tsx: "TSX",
|
||||
sh: "Shell",
|
||||
bash: "Shell",
|
||||
zsh: "Shell",
|
||||
py: "Python",
|
||||
python: "Python",
|
||||
yml: "YAML",
|
||||
yaml: "YAML",
|
||||
json: "JSON",
|
||||
astro: "Astro",
|
||||
html: "HTML",
|
||||
css: "CSS",
|
||||
scss: "SCSS",
|
||||
sql: "SQL",
|
||||
rust: "Rust",
|
||||
go: "Go",
|
||||
java: "Java",
|
||||
cpp: "C++",
|
||||
csharp: "C#",
|
||||
};
|
||||
|
||||
document.querySelectorAll("pre").forEach((pre) => {
|
||||
const code = pre.querySelector("code");
|
||||
if (!code) return;
|
||||
|
|
@ -63,6 +90,29 @@ const {
|
|||
pre.parentNode?.insertBefore(wrapper, pre);
|
||||
wrapper.appendChild(pre);
|
||||
|
||||
const toolbar = document.createElement("div");
|
||||
toolbar.className = "code-block-toolbar";
|
||||
wrapper.insertBefore(toolbar, pre);
|
||||
|
||||
const languageClass = [...code.classList, ...pre.classList].find((name) =>
|
||||
name.startsWith("language-"),
|
||||
);
|
||||
const language = (
|
||||
pre.dataset.language ||
|
||||
code.dataset.language ||
|
||||
languageClass?.slice("language-".length) ||
|
||||
""
|
||||
).trim().toLowerCase();
|
||||
|
||||
if (language) {
|
||||
const label = document.createElement("span");
|
||||
label.className = "code-language-label";
|
||||
label.textContent =
|
||||
languageNames[language.toLowerCase()] ||
|
||||
language.toUpperCase();
|
||||
toolbar.appendChild(label);
|
||||
}
|
||||
|
||||
/* 创建复制按钮 */
|
||||
const button = document.createElement("button");
|
||||
button.className = "code-copy-button";
|
||||
|
|
@ -77,13 +127,15 @@ const {
|
|||
icon.setAttribute("aria-hidden", "true");
|
||||
|
||||
button.appendChild(icon);
|
||||
wrapper.appendChild(button);
|
||||
toolbar.appendChild(button);
|
||||
|
||||
button.addEventListener("click", async () => {
|
||||
const text = code.textContent ?? "";
|
||||
let copied = false;
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
copied = true;
|
||||
} catch {
|
||||
const textarea = document.createElement("textarea");
|
||||
textarea.value = text;
|
||||
|
|
@ -91,16 +143,25 @@ const {
|
|||
textarea.style.opacity = "0";
|
||||
textarea.style.pointerEvents = "none";
|
||||
|
||||
try {
|
||||
document.body.appendChild(textarea);
|
||||
textarea.select();
|
||||
document.execCommand("copy");
|
||||
copied = document.execCommand("copy");
|
||||
} catch {
|
||||
copied = false;
|
||||
} finally {
|
||||
textarea.remove();
|
||||
button.focus({ preventScroll: true });
|
||||
}
|
||||
}
|
||||
|
||||
icon.src = "/images/correct-signal-svgrepo-com.svg";
|
||||
button.setAttribute("aria-label", "已复制");
|
||||
button.title = "已复制";
|
||||
button.classList.add("copied");
|
||||
icon.src = copied
|
||||
? "/images/correct-signal-svgrepo-com.svg"
|
||||
: "/images/copy-svgrepo-com.svg";
|
||||
const message = copied ? "已复制" : "复制失败,请手动选择代码复制";
|
||||
button.setAttribute("aria-label", message);
|
||||
button.title = message;
|
||||
button.classList.toggle("copied", copied);
|
||||
|
||||
window.setTimeout(() => {
|
||||
icon.src = "/images/copy-svgrepo-com.svg";
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ const publishedDate = new Intl.DateTimeFormat(
|
|||
description={frontmatter.description}
|
||||
image={frontmatter.image?.url}
|
||||
>
|
||||
<main class="post-layout">
|
||||
<div class="post-layout page-shell">
|
||||
<div id="reading-progress" aria-hidden="true"></div>
|
||||
<a
|
||||
href="/"
|
||||
|
|
@ -60,7 +60,7 @@ const publishedDate = new Intl.DateTimeFormat(
|
|||
{frontmatter.title}
|
||||
</h1>
|
||||
<p class="description">
|
||||
<em>{frontmatter.description}</em>
|
||||
{frontmatter.description}
|
||||
</p>
|
||||
<p class="post-byline">
|
||||
<span>{t.post.writtenBy} {frontmatter.author}</span>
|
||||
|
|
@ -97,11 +97,17 @@ const publishedDate = new Intl.DateTimeFormat(
|
|||
|
||||
<PostNav post={post} lang={lang} />
|
||||
|
||||
<h2>{comments}</h2>
|
||||
<section
|
||||
id="comments"
|
||||
class="comments-section"
|
||||
aria-labelledby="comments-heading"
|
||||
>
|
||||
<h2 id="comments-heading">{comments}</h2>
|
||||
{t.post.commentsDesc.map((line: string) => <p set:html={line} />)}
|
||||
<Remark42Embed slug={slug} />
|
||||
</section>
|
||||
</article>
|
||||
</main>
|
||||
</div>
|
||||
</BaseLayout>
|
||||
|
||||
<script
|
||||
|
|
@ -160,30 +166,55 @@ const publishedDate = new Intl.DateTimeFormat(
|
|||
backButton.dataset.bound = "true";
|
||||
}
|
||||
};
|
||||
const updateProgress = () => {
|
||||
const doc = document.documentElement;
|
||||
const scrollTop = window.scrollY || doc.scrollTop;
|
||||
const scrollHeight = doc.scrollHeight - window.innerHeight;
|
||||
|
||||
const progress = scrollHeight > 0 ? (scrollTop / scrollHeight) * 100 : 0;
|
||||
|
||||
const bar = document.getElementById("reading-progress");
|
||||
if (bar) {
|
||||
bar.style.width = `${Math.min(progress, 100)}%`;
|
||||
}
|
||||
const initReadingProgress = () => {
|
||||
const readingWindow = window as typeof window & {
|
||||
__postReadingProgressHandler?: () => void;
|
||||
};
|
||||
|
||||
updateProgress();
|
||||
if (readingWindow.__postReadingProgressHandler) {
|
||||
window.removeEventListener(
|
||||
"scroll",
|
||||
readingWindow.__postReadingProgressHandler,
|
||||
);
|
||||
window.removeEventListener(
|
||||
"resize",
|
||||
readingWindow.__postReadingProgressHandler,
|
||||
);
|
||||
}
|
||||
|
||||
const bar = document.getElementById("reading-progress");
|
||||
const content = document.querySelector<HTMLElement>(".post-content");
|
||||
if (!bar || !content) return;
|
||||
|
||||
const updateProgress = () => {
|
||||
const contentTop = content.getBoundingClientRect().top + window.scrollY;
|
||||
const readableDistance = Math.max(
|
||||
content.scrollHeight - window.innerHeight,
|
||||
1,
|
||||
);
|
||||
const progress = Math.min(
|
||||
Math.max((window.scrollY - contentTop) / readableDistance, 0),
|
||||
1,
|
||||
);
|
||||
|
||||
bar.style.transform = `scaleX(${progress})`;
|
||||
};
|
||||
|
||||
readingWindow.__postReadingProgressHandler = updateProgress;
|
||||
window.addEventListener("scroll", updateProgress, { passive: true });
|
||||
window.addEventListener("resize", updateProgress);
|
||||
updateProgress();
|
||||
};
|
||||
|
||||
initReadingProgress();
|
||||
initFloatingActions();
|
||||
document.addEventListener("astro:page-load", initReadingProgress);
|
||||
document.addEventListener("astro:page-load", initFloatingActions);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.post-layout {
|
||||
max-width: 850px;
|
||||
margin: 0 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
#reading-progress {
|
||||
|
|
@ -191,10 +222,12 @@ const publishedDate = new Intl.DateTimeFormat(
|
|||
top: 0;
|
||||
left: 0;
|
||||
z-index: 9999;
|
||||
width: 0;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
background: #5a89ff;
|
||||
transition: width 0.08s linear;
|
||||
transform: scaleX(0);
|
||||
transform-origin: left center;
|
||||
transition: transform 0.08s linear;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
|
@ -209,15 +242,12 @@ const publishedDate = new Intl.DateTimeFormat(
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
text-decoration: none;
|
||||
border: #536493 1.5px solid;
|
||||
border-top: #536493 2.5px dotted;
|
||||
border-left: #536493 2.5px dotted;
|
||||
border-top: #536493 1.5px dotted;
|
||||
border-left: #536493 1.5px dotted;
|
||||
border-right: #536493 2.5px solid;
|
||||
border-bottom: #536493 2.5px solid;
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
background: color-mix(in srgb, var(--background-color) 94%, transparent);
|
||||
color: #222;
|
||||
/* box-shadow: 0 4px 14px rgba(0, 0, 0, 0.16); */
|
||||
backdrop-filter: blur(6px);
|
||||
z-index: 1000;
|
||||
visibility: hidden;
|
||||
transform: translateY(-80px);
|
||||
|
|
@ -247,10 +277,16 @@ const publishedDate = new Intl.DateTimeFormat(
|
|||
}
|
||||
|
||||
:global(.dark) .back-button {
|
||||
background: rgba(34, 34, 34, 0.92);
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--background-color-dark) 94%,
|
||||
transparent
|
||||
);
|
||||
color: #f5f5f5;
|
||||
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.32);
|
||||
border: #2f406d 2px solid;
|
||||
border-top-color: #515151;
|
||||
border-left-color: #515151;
|
||||
border-right-color: #515151;
|
||||
border-bottom-color: #515151;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
|
|
@ -262,6 +298,9 @@ const publishedDate = new Intl.DateTimeFormat(
|
|||
}
|
||||
}
|
||||
.post-content {
|
||||
--article-muted: #596672;
|
||||
--article-rule: rgba(83, 100, 147, 0.25);
|
||||
--article-tint: rgba(83, 100, 147, 0.045);
|
||||
font-family:
|
||||
system-ui,
|
||||
-apple-system,
|
||||
|
|
@ -274,6 +313,130 @@ const publishedDate = new Intl.DateTimeFormat(
|
|||
"Source Han Sans SC",
|
||||
sans-serif;
|
||||
font-weight: 400;
|
||||
line-height: 1.85;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.post-content :global(> :first-child) {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.post-content :global(p) {
|
||||
margin-block: 1.05em;
|
||||
}
|
||||
|
||||
.post-content :global(h2) {
|
||||
margin: 3.2rem 0 1rem;
|
||||
font-family: "Noto Sans SC", system-ui, sans-serif;
|
||||
font-size: clamp(1.35rem, 2vw, 1.55rem);
|
||||
line-height: 1.4;
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.post-content :global(h3) {
|
||||
margin: 2.25rem 0 0.8rem;
|
||||
font-family: "Noto Sans SC", system-ui, sans-serif;
|
||||
font-size: clamp(1.12rem, 1.6vw, 1.28rem);
|
||||
line-height: 1.45;
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.post-content :global(h4) {
|
||||
margin: 1.8rem 0 0.65rem;
|
||||
font-family: "Noto Sans SC", system-ui, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-size: 1.05em;
|
||||
}
|
||||
|
||||
.post-content :global(:is(h2, h3, h4) + :is(h3, h4, p, ul, ol)) {
|
||||
margin-top: 0.7rem;
|
||||
}
|
||||
|
||||
.post-content :global(ul),
|
||||
.post-content :global(ol) {
|
||||
margin-block: 1.1em;
|
||||
padding-inline-start: 1.6em;
|
||||
}
|
||||
|
||||
.post-content :global(li::marker) {
|
||||
color: var(--article-muted);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.post-content :global(li > p) {
|
||||
margin-block: 0.45em;
|
||||
}
|
||||
|
||||
.post-content :global(li > :is(ul, ol)) {
|
||||
margin-block: 0.5em;
|
||||
}
|
||||
|
||||
.post-content :global(li + li) {
|
||||
margin-top: 0.35rem;
|
||||
}
|
||||
|
||||
.post-content :global(img:not(.code-copy-icon)) {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.post-content :global(figure) {
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.post-content :global(figcaption) {
|
||||
margin-top: 0.55rem;
|
||||
color: var(--article-muted);
|
||||
font-family: "Noto Sans SC", system-ui, sans-serif;
|
||||
font-size: 0.78rem;
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.post-content :global(table) {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
margin-block: 1.75rem;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.9em;
|
||||
line-height: 1.65;
|
||||
}
|
||||
|
||||
.post-content :global(th),
|
||||
.post-content :global(td) {
|
||||
padding: 0.65rem 0.85rem;
|
||||
border-bottom: 1px solid var(--article-rule);
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.post-content :global(th) {
|
||||
background: var(--article-tint);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.post-content :global(a) {
|
||||
text-decoration: underline;
|
||||
text-decoration-thickness: 1px;
|
||||
text-underline-offset: 0.2em;
|
||||
text-decoration-color: color-mix(in srgb, currentColor 45%, transparent);
|
||||
}
|
||||
|
||||
.post-content :global(a:hover) {
|
||||
text-decoration-color: currentColor;
|
||||
}
|
||||
|
||||
.post-content :global(:is(a, summary):focus-visible) {
|
||||
outline: 2px solid var(--deep-blue);
|
||||
outline-offset: 3px;
|
||||
}
|
||||
|
||||
.post-content :global(hr) {
|
||||
margin-block: 2.5rem;
|
||||
border: 0;
|
||||
border-top: 1px dashed var(--article-rule);
|
||||
}
|
||||
|
||||
.post-content :global(code),
|
||||
|
|
@ -284,7 +447,7 @@ const publishedDate = new Intl.DateTimeFormat(
|
|||
}
|
||||
|
||||
a {
|
||||
color: var(--post-link-color);
|
||||
color: var(--url-color);
|
||||
}
|
||||
|
||||
.post-header {
|
||||
|
|
@ -304,11 +467,13 @@ const publishedDate = new Intl.DateTimeFormat(
|
|||
line-height: 1.25;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-word;
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 0 0 0.7rem 0;
|
||||
opacity: 0.9;
|
||||
color: #596672;
|
||||
font-weight: 400;
|
||||
line-height: 1.6;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-word;
|
||||
|
|
@ -337,6 +502,23 @@ const publishedDate = new Intl.DateTimeFormat(
|
|||
color: #aab7c4;
|
||||
}
|
||||
|
||||
:global(html.dark) .description {
|
||||
color: #b5bec7;
|
||||
}
|
||||
|
||||
@media (min-width: 701px) {
|
||||
.post-title {
|
||||
margin-bottom: 0.85rem;
|
||||
font-size: clamp(1.7rem, 2.2vw, 2rem);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.post-cover {
|
||||
width: 300px;
|
||||
height: auto;
|
||||
|
|
@ -376,15 +558,16 @@ const publishedDate = new Intl.DateTimeFormat(
|
|||
}
|
||||
}
|
||||
|
||||
details {
|
||||
.post-content :global(details) {
|
||||
margin: 1.5rem 0;
|
||||
overflow: hidden;
|
||||
border-top: 2px solid #1e3a5f;
|
||||
border-radius: 0 0 0.5rem 0.5rem;
|
||||
background: rgba(128, 128, 128, 0.14);
|
||||
border-top: 1px solid var(--article-rule);
|
||||
border-bottom: 1px solid var(--article-rule);
|
||||
border-radius: 0;
|
||||
background: var(--article-tint);
|
||||
}
|
||||
|
||||
details summary {
|
||||
.post-content :global(details summary) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
|
|
@ -395,11 +578,11 @@ const publishedDate = new Intl.DateTimeFormat(
|
|||
list-style: none;
|
||||
}
|
||||
|
||||
details summary::-webkit-details-marker {
|
||||
.post-content :global(details summary::-webkit-details-marker) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
details summary::before {
|
||||
.post-content :global(details summary::before) {
|
||||
content: "›";
|
||||
display: inline-block;
|
||||
font-size: 1.25rem;
|
||||
|
|
@ -407,46 +590,46 @@ const publishedDate = new Intl.DateTimeFormat(
|
|||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
details[open] summary::before {
|
||||
.post-content :global(details[open] summary::before) {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
/* 展开内容 */
|
||||
details > :not(summary) {
|
||||
.post-content :global(details > :not(summary)) {
|
||||
box-sizing: border-box;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
padding-right: 1rem;
|
||||
margin: 0.8rem 1rem;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 为列表序号预留空间 */
|
||||
details > ol,
|
||||
details > ul {
|
||||
padding: 0.6rem 1rem 1rem 3rem;
|
||||
.post-content :global(details > ol),
|
||||
.post-content :global(details > ul) {
|
||||
padding-inline-start: 1.6em;
|
||||
}
|
||||
|
||||
details li {
|
||||
.post-content :global(details li) {
|
||||
margin: 0.8rem 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 1.25rem 0;
|
||||
padding: 0.75rem 1rem;
|
||||
border-left: 4px solid var(--deep-red);
|
||||
background: rgba(128, 128, 128, 0.1);
|
||||
border-radius: 0 0.4rem 0.4rem 0;
|
||||
.post-content :global(blockquote) {
|
||||
margin: 1.5rem 0;
|
||||
padding: 0.15rem 0 0.15rem 1rem;
|
||||
border-left: 3px solid var(--deep-blue);
|
||||
color: var(--article-muted);
|
||||
background: transparent;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
blockquote p {
|
||||
.post-content :global(blockquote p) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
blockquote p + p {
|
||||
.post-content :global(blockquote p + p) {
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
/* 块级公式:超出屏幕时只滚动公式,不撑宽整篇文章 */
|
||||
.katex-display {
|
||||
.post-content :global(.katex-display) {
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
|
|
@ -455,7 +638,27 @@ const publishedDate = new Intl.DateTimeFormat(
|
|||
}
|
||||
|
||||
/* 避免 KaTeX 内部元素强行把父容器撑宽 */
|
||||
.katex-display > .katex {
|
||||
.post-content :global(.katex-display > .katex) {
|
||||
min-width: max-content;
|
||||
}
|
||||
|
||||
.comments-section {
|
||||
scroll-margin-top: 5rem;
|
||||
}
|
||||
|
||||
:global(html.dark) .post-content {
|
||||
--article-muted: #aab7c4;
|
||||
--article-rule: rgba(170, 183, 196, 0.28);
|
||||
--article-tint: rgba(170, 183, 196, 0.055);
|
||||
}
|
||||
|
||||
:global(html.dark) .post-content :global(blockquote) {
|
||||
border-left-color: #9ecbff;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
#reading-progress {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import "@/styles/global.css";
|
|||
pageTitle="404 Not Found"
|
||||
description="Sorry, the page doesn't exists"
|
||||
>
|
||||
<div class="not-found">
|
||||
<div class="not-found page-shell">
|
||||
<img src="/images/marisa.png" alt="Marisa" class="marisa-404" />
|
||||
<div class="hint">
|
||||
<h3>对不起,您请求的页面不存在哦。</h3>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ const pageTitle = t.about.title;
|
|||
pageTitle=`${pageTitle} - ${t.banner.title}`
|
||||
image={t.about.profilePicture}
|
||||
>
|
||||
<main class="about">
|
||||
<div class="about page-shell">
|
||||
<h1>{t.about.title}</h1>
|
||||
|
||||
<div class="intro">
|
||||
|
|
@ -36,14 +36,12 @@ const pageTitle = t.about.title;
|
|||
<div class="content">
|
||||
{t.about.content.map((line: string) => <p set:html={line} />)}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</BaseLayout>
|
||||
|
||||
<style>
|
||||
.about {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem 1rem;
|
||||
padding-block: 2rem;
|
||||
}
|
||||
|
||||
.about h1 {
|
||||
|
|
|
|||
|
|
@ -15,9 +15,11 @@ const headerTitle = lang === "zh" ? "友情链接" : "Friends";
|
|||
---
|
||||
|
||||
<BaseLayout pageTitle=`${headerTitle} - ${t.banner.title}`>
|
||||
<div class="page-shell">
|
||||
<FriendsDescription />
|
||||
<div class="section-divider"></div>
|
||||
<FriendlyLinkList />
|
||||
</div>
|
||||
</BaseLayout>
|
||||
|
||||
<style>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ const rssLink = lang === "zh" ? "/rss.xml" : "/en/rss.xml";
|
|||
---
|
||||
|
||||
<BaseLayout pageTitle=`${headerTitle} - ${t.banner.subtitle}`>
|
||||
<main class="index">
|
||||
<div class="index page-shell">
|
||||
<h1 class="page-title">
|
||||
<span>{pageTitle}</span>
|
||||
<a
|
||||
|
|
@ -57,13 +57,12 @@ const rssLink = lang === "zh" ? "/rss.xml" : "/en/rss.xml";
|
|||
|
||||
<!-- <Remark42LatestComments lang={lang} client:load /> -->
|
||||
<LatestComments lang={lang} />
|
||||
</main>
|
||||
</div>
|
||||
</BaseLayout>
|
||||
|
||||
<style>
|
||||
.index {
|
||||
max-width: 850px;
|
||||
margin: 0 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.section-divider {
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ const t = getTranslations(lang);
|
|||
---
|
||||
|
||||
<BaseLayout pageTitle={String(tag)}>
|
||||
<div class="page-shell">
|
||||
<p class="tag-heading">
|
||||
{lang === "zh" ? "带有标签" : "Posts tagged with"}{" "}
|
||||
<span class="tag-chip" transition:name={`post-tags-${tag}`}>{tag}</span>
|
||||
|
|
@ -60,6 +61,7 @@ const t = getTranslations(lang);
|
|||
})
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</BaseLayout>
|
||||
|
||||
<style>
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ const pageTitle = lang === "zh" ? "标签索引" : "Tag Index";
|
|||
---
|
||||
|
||||
<BaseLayout pageTitle=`${pageTitle} - ${t.banner.title}`>
|
||||
<main class="tags-layout">
|
||||
<div class="tags-layout page-shell">
|
||||
<h1>{t.tags.title}</h1>
|
||||
<p>{t.tags.description}</p>
|
||||
|
||||
|
|
@ -30,14 +30,10 @@ const pageTitle = lang === "zh" ? "标签索引" : "Tag Index";
|
|||
))
|
||||
}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</BaseLayout>
|
||||
|
||||
<style>
|
||||
.tags-layout {
|
||||
max-width: 850px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ const headerTitle = lang === "zh" ? "时间轴" : "Timeline";
|
|||
---
|
||||
|
||||
<BaseLayout pageTitle=`${headerTitle} - ${t.banner.title}` `>
|
||||
<div class="page-shell">
|
||||
<h1>{t.nav.timeline}</h1>
|
||||
<PostTimeline />
|
||||
</div>
|
||||
</BaseLayout>
|
||||
|
|
|
|||
48
src/plugins/rehype-image-captions.mjs
Normal file
48
src/plugins/rehype-image-captions.mjs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
// Standalone Markdown images may use their optional title as a visible caption.
|
||||
// Keep alt text unchanged: alternative text and captions serve different purposes.
|
||||
export default function rehypeImageCaptions() {
|
||||
return (tree) => {
|
||||
function visit(parent) {
|
||||
if (!Array.isArray(parent.children)) return;
|
||||
|
||||
parent.children = parent.children.map((node) => {
|
||||
if (node.type === "element" && node.tagName === "p") {
|
||||
const content = node.children.filter(
|
||||
(child) => child.type !== "text" || child.value.trim() !== "",
|
||||
);
|
||||
const onlyChild = content.length === 1 ? content[0] : null;
|
||||
const image = onlyChild?.tagName === "img"
|
||||
? onlyChild
|
||||
: onlyChild?.tagName === "a" && onlyChild.children?.length === 1
|
||||
&& onlyChild.children[0].tagName === "img"
|
||||
? onlyChild.children[0]
|
||||
: null;
|
||||
const caption = image?.properties?.title;
|
||||
|
||||
if (typeof caption === "string" && caption.trim()) {
|
||||
return {
|
||||
type: "element",
|
||||
tagName: "figure",
|
||||
properties: {},
|
||||
children: [
|
||||
onlyChild,
|
||||
{
|
||||
type: "element",
|
||||
tagName: "figcaption",
|
||||
properties: {},
|
||||
children: [{ type: "text", value: caption.trim() }],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Respect captions and markup authored explicitly in HTML.
|
||||
if (node.tagName !== "figure") visit(node);
|
||||
return node;
|
||||
});
|
||||
}
|
||||
|
||||
visit(tree);
|
||||
};
|
||||
}
|
||||
|
|
@ -1,21 +1,31 @@
|
|||
.code-block-wrapper {
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
|
||||
.code-block-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
padding: 0.4rem 0.5rem 0.4rem 1rem;
|
||||
border-bottom: 1px solid rgba(127, 127, 127, 0.2);
|
||||
background: rgba(127, 127, 127, 0.08);
|
||||
border-radius: 3px 3px 0 0;
|
||||
}
|
||||
|
||||
.code-block-wrapper pre {
|
||||
margin: 0;
|
||||
padding: 1rem;
|
||||
border-radius: 3px;
|
||||
max-width: 100%;
|
||||
border-radius: 0 0 3px 3px;
|
||||
overflow-x: auto;
|
||||
line-height: 1.6;
|
||||
margin: 1rem 0;
|
||||
position: relative;
|
||||
|
||||
}
|
||||
|
||||
/* pre:has(> .code-copy-button) {
|
||||
padding-top: 2.75rem;
|
||||
} */
|
||||
|
||||
code {
|
||||
font-family: "Maple Mono", monospace;
|
||||
font-size: 0.96rem;
|
||||
|
|
@ -31,12 +41,15 @@ td code {
|
|||
background: rgba(127, 127, 127, 0.12);
|
||||
}
|
||||
|
||||
.code-copy-button {
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
right: 0.5rem;
|
||||
z-index: 2;
|
||||
.code-block-wrapper pre > code {
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.code-copy-button {
|
||||
flex: 0 0 auto;
|
||||
margin-left: auto;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 2rem;
|
||||
|
|
@ -58,6 +71,18 @@ td code {
|
|||
box-shadow 0.15s ease;
|
||||
}
|
||||
|
||||
.code-language-label {
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
color: #687583;
|
||||
font-family: "Maple Mono", monospace;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 700;
|
||||
line-height: 1.5;
|
||||
letter-spacing: 0.04em;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.code-copy-icon {
|
||||
display: block;
|
||||
width: 100%;
|
||||
|
|
@ -81,6 +106,11 @@ td code {
|
|||
background: #dcebd5;
|
||||
}
|
||||
|
||||
html.dark .code-language-label,
|
||||
.dark .code-language-label {
|
||||
color: #aeb9c4;
|
||||
}
|
||||
|
||||
/* .dark .code-copy-button {
|
||||
border-color: #9ecbff;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,17 @@ body {
|
|||
font-size: 1.15rem;
|
||||
}
|
||||
|
||||
.page-content,
|
||||
.page-shell {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.page-shell {
|
||||
max-width: var(--page-content-width);
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
body::after {
|
||||
content: "";
|
||||
position: fixed;
|
||||
|
|
|
|||
|
|
@ -139,7 +139,8 @@
|
|||
.comment-source {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 0.4rem;
|
||||
justify-content: space-between;
|
||||
gap: 0.55rem 1rem;
|
||||
min-width: 0;
|
||||
color: #89939c;
|
||||
font-family: system-ui, sans-serif;
|
||||
|
|
@ -147,6 +148,13 @@
|
|||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.comment-source-context {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 0.4rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.comment-title-link {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
|
|
@ -163,6 +171,22 @@
|
|||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.comment-discussion-link {
|
||||
display: none;
|
||||
flex: 0 0 auto;
|
||||
color: var(--url-color);
|
||||
font-size: 0.76rem;
|
||||
font-weight: 500;
|
||||
text-decoration-line: underline;
|
||||
text-decoration-style: dashed;
|
||||
text-underline-offset: 0.22em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.comment-card.is-truncated .comment-discussion-link {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.latest-comments-empty {
|
||||
color: #777;
|
||||
font-style: italic;
|
||||
|
|
@ -201,6 +225,11 @@ html.dark .comment-title-link,
|
|||
color: #aabbd0;
|
||||
}
|
||||
|
||||
html.dark .comment-discussion-link,
|
||||
.dark .comment-discussion-link {
|
||||
color: #9ecbff;
|
||||
}
|
||||
|
||||
html.dark .comment-avatar-fallback,
|
||||
.dark .comment-avatar-fallback {
|
||||
background: rgba(180, 190, 200, 0.14);
|
||||
|
|
@ -215,6 +244,11 @@ html.dark .comment-avatar-fallback,
|
|||
.comment-card-text {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.comment-source {
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
/* 默认配色 */
|
||||
:root {
|
||||
--page-content-width: 850px;
|
||||
--deep-blue: #536493;
|
||||
--deep-red: #ef5a6f;
|
||||
--background-color: #f9f2ed;
|
||||
|
|
|
|||
Loading…
Reference in a new issue