mirror of
https://github.com/ClovertaTheTrilobita/SanYeCao-blog.git
synced 2026-10-25 21:43:53 +00:00
Merge pull request #39 from ClovertaTheTrilobita/dev
refactor overall design
This commit is contained in:
commit
62a1917790
25 changed files with 1512 additions and 628 deletions
|
|
@ -5,6 +5,7 @@ import svelte from "@astrojs/svelte";
|
||||||
import rehypeMermaid from "rehype-mermaid";
|
import rehypeMermaid from "rehype-mermaid";
|
||||||
import remarkMath from "remark-math";
|
import remarkMath from "remark-math";
|
||||||
import rehypeKatex from "rehype-katex";
|
import rehypeKatex from "rehype-katex";
|
||||||
|
import rehypeImageCaptions from "./src/plugins/rehype-image-captions.mjs";
|
||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
import { fileURLToPath } from 'node:url'
|
import { fileURLToPath } from 'node:url'
|
||||||
|
|
||||||
|
|
@ -27,6 +28,7 @@ export default defineConfig({
|
||||||
excludeLangs: ['mermaid', 'math'],
|
excludeLangs: ['mermaid', 'math'],
|
||||||
},
|
},
|
||||||
rehypePlugins: [
|
rehypePlugins: [
|
||||||
|
rehypeImageCaptions,
|
||||||
rehypeMermaid,
|
rehypeMermaid,
|
||||||
rehypeKatex,
|
rehypeKatex,
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -62,14 +62,13 @@
|
||||||
border-right: var(--deep-blue) 2.5px solid;
|
border-right: var(--deep-blue) 2.5px solid;
|
||||||
border-bottom: var(--deep-blue) 2.5px solid;
|
border-bottom: var(--deep-blue) 2.5px solid;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
background: rgba(255, 255, 255, 0.92);
|
background: color-mix(in srgb, var(--background-color) 94%, transparent);
|
||||||
color: #222;
|
color: #222;
|
||||||
/* box-shadow: 0 4px 14px rgba(0, 0, 0, 0.16); */
|
/* box-shadow: 0 4px 14px rgba(0, 0, 0, 0.16); */
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
backdrop-filter: blur(6px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#theme-toggle-fab {
|
#theme-toggle-fab {
|
||||||
|
|
@ -141,10 +140,16 @@
|
||||||
|
|
||||||
:global(.dark) #theme-toggle-fab,
|
:global(.dark) #theme-toggle-fab,
|
||||||
:global(.dark) #back-to-top {
|
: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;
|
color: #f5f5f5;
|
||||||
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.32);
|
border-top-color: #515151;
|
||||||
border: #515151 2px solid;
|
border-left-color: #515151;
|
||||||
|
border-right-color: #515151;
|
||||||
|
border-bottom-color: #515151;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,9 @@ const t = getTranslations(lang);
|
||||||
---
|
---
|
||||||
|
|
||||||
<footer class="footer">
|
<footer class="footer">
|
||||||
|
<div class="footer-divider" data-footer-divider>
|
||||||
<Divider />
|
<Divider />
|
||||||
|
</div>
|
||||||
<a
|
<a
|
||||||
href="https://github.com/ClovertaTheTrilobita/SanYeCao-blog"
|
href="https://github.com/ClovertaTheTrilobita/SanYeCao-blog"
|
||||||
class="github"
|
class="github"
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ const t = getTranslations(lang);
|
||||||
{t.friends.content.map((line: string) => <p set:html={line} />)}
|
{t.friends.content.map((line: string) => <p set:html={line} />)}
|
||||||
</div>
|
</div>
|
||||||
<div class="friend-code-block">
|
<div class="friend-code-block">
|
||||||
<pre><code>---
|
<pre data-language="yaml"><code class="language-yaml">---
|
||||||
name: 'CLoverta的博客'
|
name: 'CLoverta的博客'
|
||||||
description: "欢迎光临,请进门左转"
|
description: "欢迎光临,请进门左转"
|
||||||
url: "https://blog.cloverta.top"
|
url: "https://blog.cloverta.top"
|
||||||
|
|
@ -59,27 +59,35 @@ avatar: 'https://s2.loli.net/2025/11/22/tiDKuzdqycx1v9B.png'
|
||||||
}
|
}
|
||||||
|
|
||||||
.friend-code-block {
|
.friend-code-block {
|
||||||
background: #fbf5f2;
|
min-width: 0;
|
||||||
border: 1px solid #8fa1ad;
|
margin-top: 1rem;
|
||||||
border-radius: 3px;
|
|
||||||
padding: 0rem;
|
|
||||||
overflow-x: auto;
|
|
||||||
/* color: var(--text-color); */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.dark) .friend-code-block {
|
.friend-code-block :global(.code-block-wrapper) {
|
||||||
background: rgba(127, 127, 127, 0.12);
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid #8fa1ad;
|
||||||
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.friend-code-block pre {
|
.friend-code-block pre {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
background: var(--comment-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
.friend-code-block code {
|
.friend-code-block code {
|
||||||
font-family: monospace;
|
font-family: "Maple Mono", monospace;
|
||||||
white-space: pre;
|
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 {
|
.image-wrap {
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,9 @@ const lang = getLangFromUrl(Astro.url);
|
||||||
const t = getTranslations(lang);
|
const t = getTranslations(lang);
|
||||||
---
|
---
|
||||||
|
|
||||||
<header class="site-header">
|
<div class="header-slot">
|
||||||
|
<header class="site-header">
|
||||||
|
<div class="header-content">
|
||||||
<div class="theme-icon-wrap">
|
<div class="theme-icon-wrap">
|
||||||
<ThemeIcon />
|
<ThemeIcon />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -26,23 +28,35 @@ const t = getTranslations(lang);
|
||||||
</div>
|
</div>
|
||||||
<Navigation />
|
<Navigation />
|
||||||
</nav>
|
</nav>
|
||||||
|
</div>
|
||||||
<div class="divider">
|
<div class="divider">
|
||||||
<Divider />
|
<Divider />
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.site-header {
|
||||||
|
position: relative;
|
||||||
|
z-index: 30;
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
.banner-link {
|
.banner-link {
|
||||||
display: inline-block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.banner-logo {
|
.banner-logo {
|
||||||
display: block;
|
display: block;
|
||||||
height: 55px; /* 按你的 banner 比例改 */
|
height: 55px;
|
||||||
background-color: #1f2328;
|
background-color: #1f2328;
|
||||||
|
|
||||||
-webkit-mask: url("/images/banner-black.svg") no-repeat left / contain;
|
-webkit-mask: url("/images/banner-black.svg") no-repeat left / contain;
|
||||||
mask: url("/images/banner-black.svg") no-repeat left / contain;
|
mask: url("/images/banner-black.svg") no-repeat left / contain;
|
||||||
|
transform-origin: left center;
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(html.dark .banner-logo) {
|
:global(html.dark .banner-logo) {
|
||||||
|
|
@ -55,11 +69,6 @@ const t = getTranslations(lang);
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.site-header {
|
|
||||||
position: relative;
|
|
||||||
padding-top: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-icon-wrap {
|
.theme-icon-wrap {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
@ -71,17 +80,14 @@ const t = getTranslations(lang);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
|
max-height: 12rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-nav .banner {
|
.header-nav .banner {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
line-height: 1.1;
|
line-height: 1.1;
|
||||||
|
|
||||||
/* 给右上角 ThemeIcon 留空间 */
|
|
||||||
max-width: calc(100% - 7rem);
|
max-width: calc(100% - 7rem);
|
||||||
|
|
||||||
/* 桌面正常,手机自动缩小 */
|
|
||||||
font-size: clamp(1.6rem, 6vw, 2.5rem);
|
font-size: clamp(1.6rem, 6vw, 2.5rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -90,8 +96,106 @@ const t = getTranslations(lang);
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (min-width: 769px) {
|
||||||
|
.header-slot {
|
||||||
|
/* Keep document height independent of scroll-driven header height. */
|
||||||
|
display: flow-root;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 30;
|
||||||
|
height: var(--header-slot-height, auto);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.site-header {
|
.site-header {
|
||||||
|
pointer-events: auto;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 90ch;
|
||||||
|
margin-inline: auto;
|
||||||
|
padding: 0.5rem 0 0;
|
||||||
|
background-color: color-mix(
|
||||||
|
in srgb,
|
||||||
|
var(--background-color) var(--header-surface-weight, 0%),
|
||||||
|
transparent
|
||||||
|
);
|
||||||
|
transform: translateY(0);
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-slot.is-scroll-hidden .site-header,
|
||||||
|
.header-slot.is-footer-hidden .site-header {
|
||||||
|
transform: translateY(calc(-100% - 0.5rem));
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(html.dark) .site-header {
|
||||||
|
background-color: color-mix(
|
||||||
|
in srgb,
|
||||||
|
#1e1e1e var(--header-surface-weight, 0%),
|
||||||
|
transparent
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-icon-wrap {
|
||||||
|
top: var(--theme-top, 0);
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-nav {
|
||||||
|
gap: var(--header-gap, 1rem);
|
||||||
|
height: var(--header-nav-height, auto);
|
||||||
|
max-height: var(--header-nav-height, 12rem);
|
||||||
|
transform: translateY(var(--header-nav-y, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-nav .banner {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: var(--banner-width, auto);
|
||||||
|
max-width: var(--banner-width, calc(100% - 7rem));
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner-logo {
|
||||||
|
height: var(--logo-height, 55px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-header :global(.site-nav) {
|
||||||
|
width: var(--site-nav-width, auto);
|
||||||
|
margin: var(--site-nav-margin, 1rem) 0;
|
||||||
|
padding-bottom: var(--site-nav-padding, 1rem);
|
||||||
|
transform: translate(var(--site-nav-x, 0), var(--site-nav-y, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-header :global(.site-nav-desktop) {
|
||||||
|
gap: var(--site-nav-gap, 1.5rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-header :global(.site-nav a) {
|
||||||
|
font-size: var(--site-nav-font-size, 1.05rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-header > .divider {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: var(--divider-margin-top, 0.2rem);
|
||||||
|
margin-bottom: var(--divider-margin-bottom, 2rem);
|
||||||
|
transform: translateY(var(--divider-y, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-header > .divider :global(.divider) {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: var(--divider-inner-margin-bottom, 1rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.header-slot {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-header {
|
||||||
|
position: relative;
|
||||||
padding-top: 0.25rem;
|
padding-top: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -101,3 +205,268 @@ const t = getTranslations(lang);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const desktopHeaderQuery = window.matchMedia("(min-width: 769px)");
|
||||||
|
const HEADER_SCROLL_DISTANCE = 120;
|
||||||
|
const DIRECTION_TRIGGER_DISTANCE = 6;
|
||||||
|
const headerStyleProperties = [
|
||||||
|
"--header-surface-weight",
|
||||||
|
"--theme-top",
|
||||||
|
"--header-gap",
|
||||||
|
"--header-nav-height",
|
||||||
|
"--header-nav-y",
|
||||||
|
"--banner-width",
|
||||||
|
"--logo-height",
|
||||||
|
"--site-nav-width",
|
||||||
|
"--site-nav-margin",
|
||||||
|
"--site-nav-padding",
|
||||||
|
"--site-nav-x",
|
||||||
|
"--site-nav-y",
|
||||||
|
"--site-nav-gap",
|
||||||
|
"--site-nav-font-size",
|
||||||
|
"--divider-margin-top",
|
||||||
|
"--divider-margin-bottom",
|
||||||
|
"--divider-inner-margin-bottom",
|
||||||
|
"--divider-y",
|
||||||
|
];
|
||||||
|
|
||||||
|
type HeaderMetrics = {
|
||||||
|
header: HTMLElement;
|
||||||
|
expandedWidth: number;
|
||||||
|
expandedNavHeight: number;
|
||||||
|
expandedBannerWidth: number;
|
||||||
|
expandedSiteNavWidth: number;
|
||||||
|
rootFontSize: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
let headerFrame = 0;
|
||||||
|
let headerMetrics: HeaderMetrics | null = null;
|
||||||
|
let footerDividerObserver: IntersectionObserver | null = null;
|
||||||
|
let isFooterDividerVisible = false;
|
||||||
|
let lastScrollY = window.scrollY;
|
||||||
|
let scrollDirection = 0;
|
||||||
|
let directionDistance = 0;
|
||||||
|
|
||||||
|
const lerp = (start: number, end: number, progress: number) =>
|
||||||
|
start + (end - start) * progress;
|
||||||
|
|
||||||
|
function clearHeaderStyles(header: HTMLElement) {
|
||||||
|
headerStyleProperties.forEach((property) => {
|
||||||
|
header.style.removeProperty(property);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function measureHeader(header: HTMLElement): HeaderMetrics | null {
|
||||||
|
clearHeaderStyles(header);
|
||||||
|
const slot = header.parentElement;
|
||||||
|
if (!slot) return null;
|
||||||
|
slot.style.removeProperty("--header-slot-height");
|
||||||
|
|
||||||
|
const headerNav = header.querySelector<HTMLElement>(".header-nav");
|
||||||
|
const banner = header.querySelector<HTMLElement>(".banner");
|
||||||
|
const siteNav = header.querySelector<HTMLElement>(".site-nav");
|
||||||
|
|
||||||
|
if (!headerNav || !banner || !siteNav) return null;
|
||||||
|
|
||||||
|
// Measure the expanded footprint, including its bottom spacing, once.
|
||||||
|
// Only the contents animate; the following page content never reflows.
|
||||||
|
slot.style.setProperty(
|
||||||
|
"--header-slot-height",
|
||||||
|
`${slot.getBoundingClientRect().height}px`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
header,
|
||||||
|
expandedWidth: header.getBoundingClientRect().width,
|
||||||
|
expandedNavHeight: headerNav.getBoundingClientRect().height,
|
||||||
|
expandedBannerWidth: banner.getBoundingClientRect().width,
|
||||||
|
expandedSiteNavWidth: siteNav.getBoundingClientRect().width,
|
||||||
|
rootFontSize: Number.parseFloat(
|
||||||
|
getComputedStyle(document.documentElement).fontSize,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateHeaderState() {
|
||||||
|
const header = document.querySelector<HTMLElement>(".site-header");
|
||||||
|
if (!header) return;
|
||||||
|
|
||||||
|
updateScrollVisibility(header.parentElement, window.scrollY);
|
||||||
|
updateFooterVisibility(header.parentElement, window.scrollY);
|
||||||
|
|
||||||
|
if (!desktopHeaderQuery.matches) {
|
||||||
|
clearHeaderStyles(header);
|
||||||
|
header.parentElement?.style.removeProperty("--header-slot-height");
|
||||||
|
headerMetrics = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!headerMetrics || headerMetrics.header !== header) {
|
||||||
|
headerMetrics = measureHeader(header);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!headerMetrics) return;
|
||||||
|
|
||||||
|
const {
|
||||||
|
expandedWidth,
|
||||||
|
expandedNavHeight,
|
||||||
|
expandedBannerWidth,
|
||||||
|
expandedSiteNavWidth,
|
||||||
|
rootFontSize,
|
||||||
|
} = headerMetrics;
|
||||||
|
const rawProgress = Math.min(
|
||||||
|
Math.max(window.scrollY / HEADER_SCROLL_DISTANCE, 0),
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
const progress = rawProgress;
|
||||||
|
const viewportWidth = document.documentElement.clientWidth;
|
||||||
|
const compactLogoWidth = Math.min(
|
||||||
|
Math.max(9 * rootFontSize, viewportWidth * 0.22),
|
||||||
|
12 * rootFontSize,
|
||||||
|
);
|
||||||
|
const compactSiteNavWidth = Math.max(
|
||||||
|
expandedWidth - compactLogoWidth - 7.5 * rootFontSize,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
const compactNavGap = Math.min(
|
||||||
|
Math.max(0.75 * rootFontSize, viewportWidth * 0.02),
|
||||||
|
1.5 * rootFontSize,
|
||||||
|
);
|
||||||
|
|
||||||
|
const values: Record<string, string> = {
|
||||||
|
"--header-surface-weight": `${lerp(0, 98, progress)}%`,
|
||||||
|
"--theme-top": `${lerp(0, 0.85 * rootFontSize, progress)}px`,
|
||||||
|
"--header-gap": `${lerp(rootFontSize, 0, progress)}px`,
|
||||||
|
"--header-nav-height": `${lerp(expandedNavHeight, 3 * rootFontSize, progress)}px`,
|
||||||
|
"--header-nav-y": `${lerp(0, 0.5 * rootFontSize, progress)}px`,
|
||||||
|
"--banner-width": `${lerp(expandedBannerWidth, compactLogoWidth, progress)}px`,
|
||||||
|
"--logo-height": `${lerp(55, 36, progress)}px`,
|
||||||
|
"--site-nav-width": `${lerp(expandedSiteNavWidth, compactSiteNavWidth, progress)}px`,
|
||||||
|
"--site-nav-margin": `${lerp(rootFontSize, 0, progress)}px`,
|
||||||
|
"--site-nav-padding": `${lerp(rootFontSize, 0, progress)}px`,
|
||||||
|
"--site-nav-x": `${lerp(0, compactLogoWidth + 1.5 * rootFontSize, progress)}px`,
|
||||||
|
"--site-nav-y": `${lerp(0, -36, progress)}px`,
|
||||||
|
"--site-nav-gap": `${lerp(1.5 * rootFontSize, compactNavGap, progress)}px`,
|
||||||
|
"--site-nav-font-size": `${lerp(1.05 * rootFontSize, 0.95 * rootFontSize, progress)}px`,
|
||||||
|
"--divider-margin-top": `${lerp(0.2 * rootFontSize, 0.45 * rootFontSize, progress)}px`,
|
||||||
|
"--divider-margin-bottom": `${lerp(2 * rootFontSize, 0, progress)}px`,
|
||||||
|
"--divider-inner-margin-bottom": `${lerp(rootFontSize, 0, progress)}px`,
|
||||||
|
"--divider-y": `${lerp(0, -0.1 * rootFontSize, progress)}px`,
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.entries(values).forEach(([property, value]) => {
|
||||||
|
header.style.setProperty(property, value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateScrollVisibility(
|
||||||
|
slot: HTMLElement | null,
|
||||||
|
currentScrollY: number,
|
||||||
|
) {
|
||||||
|
if (!slot) return;
|
||||||
|
|
||||||
|
const delta = currentScrollY - lastScrollY;
|
||||||
|
lastScrollY = currentScrollY;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!desktopHeaderQuery.matches ||
|
||||||
|
currentScrollY <= HEADER_SCROLL_DISTANCE
|
||||||
|
) {
|
||||||
|
slot.classList.remove("is-scroll-hidden");
|
||||||
|
scrollDirection = 0;
|
||||||
|
directionDistance = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Math.abs(delta) < 0.5) return;
|
||||||
|
|
||||||
|
const nextDirection = Math.sign(delta);
|
||||||
|
if (nextDirection !== scrollDirection) {
|
||||||
|
scrollDirection = nextDirection;
|
||||||
|
directionDistance = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
directionDistance += Math.abs(delta);
|
||||||
|
if (directionDistance < DIRECTION_TRIGGER_DISTANCE) return;
|
||||||
|
|
||||||
|
slot.classList.toggle("is-scroll-hidden", scrollDirection > 0);
|
||||||
|
directionDistance = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFooterVisibility(
|
||||||
|
slot: HTMLElement | null,
|
||||||
|
currentScrollY: number,
|
||||||
|
) {
|
||||||
|
if (!slot) return;
|
||||||
|
|
||||||
|
const headerIsFullyExpanded = currentScrollY >= HEADER_SCROLL_DISTANCE;
|
||||||
|
|
||||||
|
slot.classList.toggle(
|
||||||
|
"is-footer-hidden",
|
||||||
|
desktopHeaderQuery.matches &&
|
||||||
|
headerIsFullyExpanded &&
|
||||||
|
isFooterDividerVisible,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestHeaderUpdate() {
|
||||||
|
if (headerFrame) return;
|
||||||
|
|
||||||
|
headerFrame = window.requestAnimationFrame(() => {
|
||||||
|
headerFrame = 0;
|
||||||
|
updateHeaderState();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindFooterDividerObserver() {
|
||||||
|
footerDividerObserver?.disconnect();
|
||||||
|
footerDividerObserver = null;
|
||||||
|
isFooterDividerVisible = false;
|
||||||
|
|
||||||
|
const slot = document.querySelector<HTMLElement>(".header-slot");
|
||||||
|
const footerDivider = document.querySelector<HTMLElement>(
|
||||||
|
"[data-footer-divider]",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!slot) return;
|
||||||
|
|
||||||
|
slot.classList.remove("is-footer-hidden");
|
||||||
|
|
||||||
|
if (!desktopHeaderQuery.matches || !footerDivider) return;
|
||||||
|
|
||||||
|
footerDividerObserver = new IntersectionObserver(([entry]) => {
|
||||||
|
isFooterDividerVisible = entry.isIntersecting;
|
||||||
|
updateFooterVisibility(slot, window.scrollY);
|
||||||
|
});
|
||||||
|
footerDividerObserver.observe(footerDivider);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("scroll", requestHeaderUpdate, { passive: true });
|
||||||
|
window.addEventListener("resize", () => {
|
||||||
|
headerMetrics = null;
|
||||||
|
requestHeaderUpdate();
|
||||||
|
});
|
||||||
|
desktopHeaderQuery.addEventListener("change", () => {
|
||||||
|
requestHeaderUpdate();
|
||||||
|
bindFooterDividerObserver();
|
||||||
|
});
|
||||||
|
document.addEventListener("astro:page-load", () => {
|
||||||
|
headerMetrics = null;
|
||||||
|
lastScrollY = window.scrollY;
|
||||||
|
scrollDirection = 0;
|
||||||
|
directionDistance = 0;
|
||||||
|
isFooterDividerVisible = false;
|
||||||
|
document
|
||||||
|
.querySelector<HTMLElement>(".header-slot")
|
||||||
|
?.classList.remove("is-scroll-hidden");
|
||||||
|
updateHeaderState();
|
||||||
|
bindFooterDividerObserver();
|
||||||
|
});
|
||||||
|
document.fonts.ready.then(() => {
|
||||||
|
headerMetrics = null;
|
||||||
|
requestHeaderUpdate();
|
||||||
|
});
|
||||||
|
updateHeaderState();
|
||||||
|
bindFooterDividerObserver();
|
||||||
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
const { lang = "zh" } = Astro.props;
|
const { lang = "zh" } = Astro.props;
|
||||||
const host = import.meta.env.PUBLIC_REMARK42_HOST;
|
const host = import.meta.env.PUBLIC_REMARK42_HOST;
|
||||||
const siteId = import.meta.env.PUBLIC_REMARK42_SITE_ID;
|
const siteId = import.meta.env.PUBLIC_REMARK42_SITE_ID;
|
||||||
const max = 10;
|
const max = 5;
|
||||||
const heading = lang === "zh" ? "最新评论" : "Latest comments";
|
const heading = lang === "zh" ? "最新评论" : "Latest comments";
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -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) {
|
function getAvatar(item) {
|
||||||
return (
|
return (
|
||||||
item?.user?.picture || item?.user?.photo || item?.user?.avatar || ""
|
item?.user?.picture || item?.user?.photo || item?.user?.avatar || ""
|
||||||
|
|
@ -75,6 +82,22 @@ const heading = lang === "zh" ? "最新评论" : "Latest comments";
|
||||||
return item?.user?.name || (lang === "zh" ? "匿名用户" : "Anonymous");
|
return item?.user?.name || (lang === "zh" ? "匿名用户" : "Anonymous");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCommentPreview(html, lang) {
|
||||||
|
const template = document.createElement("template");
|
||||||
|
template.innerHTML = String(html || "");
|
||||||
|
|
||||||
|
const text = (template.content.textContent || "")
|
||||||
|
.replace(/\s+/g, " ")
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
if (text) return escapeHtml(text);
|
||||||
|
if (template.content.querySelector("img")) {
|
||||||
|
return lang === "zh" ? "[图片]" : "[Image]";
|
||||||
|
}
|
||||||
|
|
||||||
|
return lang === "zh" ? "[无文字内容]" : "[No text content]";
|
||||||
|
}
|
||||||
|
|
||||||
function normalizePath(url) {
|
function normalizePath(url) {
|
||||||
if (!url) return "";
|
if (!url) return "";
|
||||||
|
|
||||||
|
|
@ -207,10 +230,14 @@ const heading = lang === "zh" ? "最新评论" : "Latest comments";
|
||||||
item?.title ||
|
item?.title ||
|
||||||
(lang === "zh" ? "未命名文章" : "Untitled post"),
|
(lang === "zh" ? "未命名文章" : "Untitled post"),
|
||||||
);
|
);
|
||||||
const href = rewritePostUrl(item?.locator?.url || "", lang);
|
const href = escapeHtml(
|
||||||
|
rewritePostUrl(item?.locator?.url || "", lang),
|
||||||
|
);
|
||||||
|
const discussionHref = escapeHtml(
|
||||||
|
getDiscussionUrl(item?.locator?.url || "", lang),
|
||||||
|
);
|
||||||
|
|
||||||
// Remark42 返回的 text 是处理后的 HTML,适合直接渲染
|
const preview = getCommentPreview(item?.text, lang);
|
||||||
const html = item?.text || "";
|
|
||||||
const time = formatTime(item?.time, lang);
|
const time = formatTime(item?.time, lang);
|
||||||
|
|
||||||
const replyTo = escapeHtml(item?.replyToName || "");
|
const replyTo = escapeHtml(item?.replyToName || "");
|
||||||
|
|
@ -227,7 +254,7 @@ const heading = lang === "zh" ? "最新评论" : "Latest comments";
|
||||||
return `
|
return `
|
||||||
<article class="comment-card">
|
<article class="comment-card">
|
||||||
<div class="comment-card-body">
|
<div class="comment-card-body">
|
||||||
<div class="comment-card-info">
|
<header class="comment-card-info">
|
||||||
<div class="comment-avatar">
|
<div class="comment-avatar">
|
||||||
${avatarHtml}
|
${avatarHtml}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -238,19 +265,37 @@ const heading = lang === "zh" ? "最新评论" : "Latest comments";
|
||||||
${time ? `<span class="comment-time">${escapeHtml(time)}</span>` : ""}
|
${time ? `<span class="comment-time">${escapeHtml(time)}</span>` : ""}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</header>
|
||||||
|
|
||||||
<div class="comment-card-title">
|
<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>
|
<a class="comment-title-link" href="${href}">${title}</a>
|
||||||
</div>
|
</div>
|
||||||
|
<a class="comment-discussion-link" href="${discussionHref}">${
|
||||||
<div class="comment-card-text">${html}</div>
|
lang === "zh" ? "查看完整讨论" : "View discussion"
|
||||||
|
}</a>
|
||||||
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
`;
|
`;
|
||||||
})
|
})
|
||||||
.join("");
|
.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";
|
loading.style.display = "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -259,7 +304,7 @@ const heading = lang === "zh" ? "最新评论" : "Latest comments";
|
||||||
|
|
||||||
const host = section.dataset.host;
|
const host = section.dataset.host;
|
||||||
const siteId = section.dataset.siteId;
|
const siteId = section.dataset.siteId;
|
||||||
const max = section.dataset.max || "10";
|
const max = section.dataset.max || "5";
|
||||||
const lang = section.dataset.lang || "zh";
|
const lang = section.dataset.lang || "zh";
|
||||||
|
|
||||||
const list = section.querySelector(".latest-comments-list");
|
const list = section.querySelector(".latest-comments-list");
|
||||||
|
|
|
||||||
|
|
@ -3,25 +3,48 @@ import { getLangFromUrl, getTranslations } from "@/i18n";
|
||||||
|
|
||||||
const lang = getLangFromUrl(Astro.url);
|
const lang = getLangFromUrl(Astro.url);
|
||||||
const t = getTranslations(lang);
|
const t = getTranslations(lang);
|
||||||
|
const pathname = Astro.url.pathname;
|
||||||
|
const items = [
|
||||||
|
{ href: `/${lang}/`, label: t.nav.home, exact: true },
|
||||||
|
{ href: `/${lang}/about/`, label: t.nav.about },
|
||||||
|
{ href: `/${lang}/tags/`, label: t.nav.tags },
|
||||||
|
{ href: `/${lang}/timeline/`, label: t.nav.timeline },
|
||||||
|
{ href: `/${lang}/friends/`, label: t.nav.friends },
|
||||||
|
];
|
||||||
|
|
||||||
|
const isCurrent = (href: string, exact = false) =>
|
||||||
|
exact
|
||||||
|
? pathname === href || pathname === href.slice(0, -1)
|
||||||
|
: pathname.startsWith(href);
|
||||||
---
|
---
|
||||||
|
|
||||||
<nav class="site-nav" aria-label="Site navigation">
|
<nav class="site-nav" aria-label="Site navigation">
|
||||||
<div class="site-nav-desktop">
|
<div class="site-nav-desktop">
|
||||||
<a href={`/${lang}/`}>{t.nav.home}</a>
|
{
|
||||||
<a href={`/${lang}/about/`}>{t.nav.about}</a>
|
items.map(({ href, label, exact }) => (
|
||||||
<a href={`/${lang}/tags/`}>{t.nav.tags}</a>
|
<a
|
||||||
<a href={`/${lang}/timeline/`}>{t.nav.timeline}</a>
|
href={href}
|
||||||
<a href={`/${lang}/friends/`}>{t.nav.friends}</a>
|
aria-current={isCurrent(href, exact) ? "page" : undefined}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</a>
|
||||||
|
))
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<details class="site-nav-mobile">
|
<details class="site-nav-mobile">
|
||||||
<summary>☰ {lang === "zh" ? "导航栏" : "Menu"}</summary>
|
<summary>☰ {lang === "zh" ? "导航栏" : "Menu"}</summary>
|
||||||
<div class="dropdown-menu">
|
<div class="dropdown-menu">
|
||||||
<a href={`/${lang}/`}>{t.nav.home}</a>
|
{
|
||||||
<a href={`/${lang}/about/`}>{t.nav.about}</a>
|
items.map(({ href, label, exact }) => (
|
||||||
<a href={`/${lang}/tags/`}>{t.nav.tags}</a>
|
<a
|
||||||
<a href={`/${lang}/timeline/`}>{t.nav.timeline}</a>
|
href={href}
|
||||||
<a href={`/${lang}/friends/`}>{t.nav.friends}</a>
|
aria-current={isCurrent(href, exact) ? "page" : undefined}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</a>
|
||||||
|
))
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
@ -54,22 +77,45 @@ const t = getTranslations(lang);
|
||||||
}
|
}
|
||||||
|
|
||||||
.site-nav a {
|
.site-nav a {
|
||||||
color: black;
|
position: relative;
|
||||||
text-decoration: underline;
|
color: var(--text-color);
|
||||||
font-weight: 700;
|
text-decoration: none;
|
||||||
font-size: 1.2rem;
|
font-weight: 500;
|
||||||
|
font-size: 1.05rem;
|
||||||
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.site-nav a:hover {
|
.site-nav-desktop a::after {
|
||||||
text-decoration: underline;
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
bottom: -0.28rem;
|
||||||
|
width: 100%;
|
||||||
|
height: 2px;
|
||||||
|
background: var(--deep-red);
|
||||||
|
transform: translateX(-50%) scaleX(0);
|
||||||
|
transform-origin: center;
|
||||||
|
transition: transform 0.18s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-nav-desktop a:hover::after,
|
||||||
|
.site-nav-desktop a:focus-visible::after,
|
||||||
|
.site-nav-desktop a[aria-current="page"]::after {
|
||||||
|
transform: translateX(-50%) scaleX(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-nav a:focus-visible {
|
||||||
|
outline: 2px solid var(--url-color);
|
||||||
|
outline-offset: 0.35rem;
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.dark) .site-nav a {
|
:global(.dark) .site-nav a {
|
||||||
color: white;
|
color: var(--text-color-dark);
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.dark) .site-nav a:hover {
|
:global(.dark) .site-nav a:hover {
|
||||||
color: #ddd;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.site-nav-mobile {
|
.site-nav-mobile {
|
||||||
|
|
@ -81,10 +127,10 @@ const t = getTranslations(lang);
|
||||||
list-style: none;
|
list-style: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-weight: 700;
|
font-weight: 500;
|
||||||
font-size: 1.1rem;
|
font-size: 1rem;
|
||||||
color: black;
|
color: var(--text-color);
|
||||||
text-decoration: underline;
|
text-decoration: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -121,6 +167,11 @@ const t = getTranslations(lang);
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dropdown-menu a[aria-current="page"] {
|
||||||
|
color: var(--deep-red);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
:global(.dark) .dropdown-menu {
|
:global(.dark) .dropdown-menu {
|
||||||
background: #1f1b18;
|
background: #1f1b18;
|
||||||
border-color: #c8a98a;
|
border-color: #c8a98a;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
---
|
---
|
||||||
import "@/styles/global.css";
|
import "@/styles/global.css";
|
||||||
import Remark42Counter from "@/components/Remark42Counter.astro";
|
import Remark42Counter from "@/components/Remark42Counter.astro";
|
||||||
|
import TagLink from "@/components/TagLink.astro";
|
||||||
import { getLangFromUrl } from "@/i18n";
|
import { getLangFromUrl } from "@/i18n";
|
||||||
|
|
||||||
const lang = getLangFromUrl(Astro.url);
|
const lang = getLangFromUrl(Astro.url);
|
||||||
|
|
@ -22,9 +23,7 @@ const tags = data.tags;
|
||||||
<div class="tags" transition:name={`post-tags-${data.postId}`}>
|
<div class="tags" transition:name={`post-tags-${data.postId}`}>
|
||||||
{
|
{
|
||||||
tags.map((tag: string) => (
|
tags.map((tag: string) => (
|
||||||
<p class="tag">
|
<TagLink tag={tag} lang={lang} />
|
||||||
<a href={`/${lang}/tags/${tag}`}>{tag}</a>
|
|
||||||
</p>
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -51,81 +50,31 @@ const tags = data.tags;
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<div class="section-divider"></div>
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.tags {
|
.tags {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 0.4rem;
|
gap: 0.25rem 0.9rem;
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.75rem;
|
||||||
margin-bottom: 0.5rem;
|
min-width: 0;
|
||||||
}
|
font-size: 0.85rem;
|
||||||
|
|
||||||
.tag {
|
|
||||||
font-family:
|
|
||||||
"Noto Sans SC",
|
|
||||||
system-ui,
|
|
||||||
-apple-system,
|
|
||||||
BlinkMacSystemFont,
|
|
||||||
"Segoe UI",
|
|
||||||
Roboto,
|
|
||||||
Oxygen,
|
|
||||||
Ubuntu,
|
|
||||||
Cantarell,
|
|
||||||
"Open Sans",
|
|
||||||
"Helvetica Neue",
|
|
||||||
sans-serif;
|
|
||||||
|
|
||||||
margin: 0;
|
|
||||||
padding: 0.22em 0.65em;
|
|
||||||
font-size: 1rem;
|
|
||||||
line-height: 1.2;
|
|
||||||
border: 1px dashed #8b6b4a;
|
|
||||||
border-radius: 0;
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tag a {
|
|
||||||
color: #6f4e37;
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tag a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.dark) .tag {
|
|
||||||
border-color: #d8c7a1;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.dark) .tag a {
|
|
||||||
color: #e6d8b8;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.dark) .tag {
|
|
||||||
border-color: #d8c7a1;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.dark) .tag a {
|
|
||||||
color: #e6d8b8;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-card {
|
.post-card {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin: 0 0 1rem 0;
|
margin: 0;
|
||||||
padding-left: 0;
|
padding: 0;
|
||||||
padding-bottom: 0.9rem;
|
}
|
||||||
padding-top: 0.9rem;
|
|
||||||
|
.post-card + .post-card {
|
||||||
|
margin-top: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-link {
|
.post-link {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: stretch;
|
align-items: flex-start;
|
||||||
gap: 1rem;
|
gap: 1.5rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
@ -136,14 +85,13 @@ const tags = data.tags;
|
||||||
max-width: 40rem;
|
max-width: 40rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
min-height: calc(1.6em * 4);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-title-link {
|
.post-title-link {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
display: block;
|
display: block;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-title {
|
.post-title {
|
||||||
|
|
@ -163,7 +111,8 @@ const tags = data.tags;
|
||||||
sans-serif;
|
sans-serif;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 1.34rem;
|
font-size: 1.34rem;
|
||||||
line-height: 1.6;
|
line-height: 1.5;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
-webkit-line-clamp: 3;
|
-webkit-line-clamp: 3;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
|
|
@ -171,11 +120,13 @@ const tags = data.tags;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-description {
|
.post-description {
|
||||||
color: black;
|
margin-top: 0.5rem;
|
||||||
|
color: #4e5e6e;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 0.92rem;
|
font-size: 0.95rem;
|
||||||
/* font-style: italic; */
|
line-height: 1.7;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-meta-row {
|
.post-meta-row {
|
||||||
|
|
@ -183,11 +134,13 @@ const tags = data.tags;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.9rem;
|
gap: 0.9rem;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #66717c;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-date {
|
.post-date {
|
||||||
color: gray;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
@ -196,8 +149,6 @@ const tags = data.tags;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.7rem;
|
gap: 0.7rem;
|
||||||
color: #6f8090;
|
|
||||||
font-size: 0.92rem;
|
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
@ -208,22 +159,28 @@ const tags = data.tags;
|
||||||
gap: 0.2rem;
|
gap: 0.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.post-image-link {
|
||||||
|
display: block;
|
||||||
|
flex: 0 0 11rem;
|
||||||
|
width: 11rem;
|
||||||
|
aspect-ratio: 16 / 10;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.post-image {
|
.post-image {
|
||||||
width: calc((1.6em * 4) * 16 / 10);
|
width: 100%;
|
||||||
height: calc(1.6em * 4);
|
height: 100%;
|
||||||
aspect-ratio: 16 / 10;
|
aspect-ratio: 16 / 10;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
object-position: center;
|
object-position: center;
|
||||||
border: 1.5px #94a0ab dashed;
|
|
||||||
flex-shrink: 0;
|
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.dark) .post-description {
|
:global(.dark) .post-description {
|
||||||
color: #e6e6e6;
|
color: #c4ccd5;
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.dark) .post-stats {
|
:global(.dark) .post-meta-row {
|
||||||
color: #aab7c4;
|
color: #aab7c4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -264,7 +221,6 @@ const tags = data.tags;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-image {
|
.post-image {
|
||||||
/* border: none; */
|
|
||||||
grid-area: image;
|
grid-area: image;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: calc(145px * 10 / 16);
|
height: calc(145px * 10 / 16);
|
||||||
|
|
@ -274,8 +230,9 @@ const tags = data.tags;
|
||||||
.post-image-link {
|
.post-image-link {
|
||||||
grid-area: image;
|
grid-area: image;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
display: block;
|
|
||||||
width: 145px;
|
width: 145px;
|
||||||
|
aspect-ratio: auto;
|
||||||
|
overflow: visible;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,15 +59,15 @@ const latestPosts = filteredPosts.slice(0, 7);
|
||||||
}
|
}
|
||||||
|
|
||||||
.more-link-wrap {
|
.more-link-wrap {
|
||||||
margin-top: 1rem;
|
margin-top: 2rem;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.more-link {
|
.more-link {
|
||||||
color: #6f8090;
|
color: #6f8090;
|
||||||
font-weight: 700;
|
font-weight: 400;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: 1.14rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.more-link:hover {
|
.more-link:hover {
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,9 @@ const tocHeadings = headings.filter((h) => h.depth === 2 || h.depth === 3);
|
||||||
<style>
|
<style>
|
||||||
.post-menu {
|
.post-menu {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
min-width: 0;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-menu-title {
|
.post-menu-title {
|
||||||
|
|
@ -125,6 +128,7 @@ const tocHeadings = headings.filter((h) => h.depth === 2 || h.depth === 3);
|
||||||
|
|
||||||
.post-menu-item {
|
.post-menu-item {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
min-width: 0;
|
||||||
margin: 0.42rem 0;
|
margin: 0.42rem 0;
|
||||||
line-height: 1.45;
|
line-height: 1.45;
|
||||||
}
|
}
|
||||||
|
|
@ -136,27 +140,41 @@ const tocHeadings = headings.filter((h) => h.depth === 2 || h.depth === 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-menu a {
|
.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;
|
color: inherit;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
transition:
|
||||||
|
border-color 0.16s ease,
|
||||||
|
color 0.16s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-menu a:hover {
|
.post-menu a:hover {
|
||||||
text-decoration: underline;
|
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 {
|
.post-menu-mobile-toggle {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1200px) {
|
@media (min-width: 1200px) {
|
||||||
.post-menu {
|
.post-menu {
|
||||||
--content-width: 90ch;
|
--content-width: var(--page-content-width);
|
||||||
--menu-width: clamp(
|
--menu-width: clamp(
|
||||||
140px,
|
140px,
|
||||||
calc((100vw - 1200px) * 0.42 + 140px),
|
calc((100vw - 1200px) * 0.42 + 140px),
|
||||||
260px
|
260px
|
||||||
);
|
);
|
||||||
--menu-gap: 0rem;
|
--menu-gap: clamp(1.5rem, 2vw, 2rem);
|
||||||
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 8.5rem;
|
top: 8.5rem;
|
||||||
|
|
@ -168,7 +186,9 @@ const tocHeadings = headings.filter((h) => h.depth === 2 || h.depth === 3);
|
||||||
);
|
);
|
||||||
|
|
||||||
max-height: calc(100vh - 10rem);
|
max-height: calc(100vh - 10rem);
|
||||||
overflow: auto;
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
overscroll-behavior: contain;
|
||||||
padding: 0.2rem 0.2rem 0.2rem 0;
|
padding: 0.2rem 0.2rem 0.2rem 0;
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
}
|
}
|
||||||
|
|
@ -187,8 +207,11 @@ const tocHeadings = headings.filter((h) => h.depth === 2 || h.depth === 3);
|
||||||
padding: 0.6rem 0.75rem;
|
padding: 0.6rem 0.75rem;
|
||||||
border: 1.5px solid var(--deep-blue);
|
border: 1.5px solid var(--deep-blue);
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
background: rgba(255, 255, 255, 0.92);
|
background: color-mix(
|
||||||
backdrop-filter: blur(6px);
|
in srgb,
|
||||||
|
var(--background-color) 94%,
|
||||||
|
transparent
|
||||||
|
);
|
||||||
color: inherit;
|
color: inherit;
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
@ -232,11 +255,16 @@ const tocHeadings = headings.filter((h) => h.depth === 2 || h.depth === 3);
|
||||||
z-index: 29;
|
z-index: 29;
|
||||||
width: min(88vw, 24rem);
|
width: min(88vw, 24rem);
|
||||||
max-height: min(65vh, 32rem);
|
max-height: min(65vh, 32rem);
|
||||||
overflow: auto;
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
overscroll-behavior: contain;
|
||||||
padding: 0.9rem 1rem;
|
padding: 0.9rem 1rem;
|
||||||
border: 1.5px solid var(--deep-blue);
|
border: 1.5px solid var(--deep-blue);
|
||||||
background: rgba(255, 255, 255, 0.96);
|
background: color-mix(
|
||||||
backdrop-filter: blur(8px);
|
in srgb,
|
||||||
|
var(--background-color) 97%,
|
||||||
|
transparent
|
||||||
|
);
|
||||||
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.08);
|
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 = () => {
|
const updateCurrentHeading = () => {
|
||||||
if (window.innerWidth >= 1200) return;
|
|
||||||
|
|
||||||
headingItems = getHeadingElements();
|
headingItems = getHeadingElements();
|
||||||
|
|
||||||
let current = null;
|
let current = null;
|
||||||
|
|
@ -347,10 +373,16 @@ const tocHeadings = headings.filter((h) => h.depth === 2 || h.depth === 3);
|
||||||
|
|
||||||
preview.textContent = current ? current.text : defaultText;
|
preview.textContent = current ? current.text : defaultText;
|
||||||
|
|
||||||
headingLinks.forEach((link) =>
|
headingLinks.forEach((link) => {
|
||||||
link.classList.remove("is-current"),
|
const isCurrent = link === current?.link;
|
||||||
);
|
link.classList.toggle("is-current", isCurrent);
|
||||||
if (current) current.link.classList.add("is-current");
|
|
||||||
|
if (isCurrent) {
|
||||||
|
link.setAttribute("aria-current", "location");
|
||||||
|
} else {
|
||||||
|
link.removeAttribute("aria-current");
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleMenu = () => {
|
const toggleMenu = () => {
|
||||||
|
|
@ -377,7 +409,11 @@ const tocHeadings = headings.filter((h) => h.depth === 2 || h.depth === 3);
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
el.scrollIntoView({
|
el.scrollIntoView({
|
||||||
behavior: "smooth",
|
behavior: window.matchMedia(
|
||||||
|
"(prefers-reduced-motion: reduce)",
|
||||||
|
).matches
|
||||||
|
? "auto"
|
||||||
|
: "smooth",
|
||||||
block: "start",
|
block: "start",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
48
src/components/TagLink.astro
Normal file
48
src/components/TagLink.astro
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
---
|
||||||
|
interface Props {
|
||||||
|
tag: string;
|
||||||
|
lang: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { tag, lang } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<a class="tag-link" href={`/${lang}/tags/${encodeURIComponent(tag)}/`}>#{tag}</a>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.tag-link {
|
||||||
|
display: inline-block;
|
||||||
|
max-width: 100%;
|
||||||
|
padding-block: 0.2rem;
|
||||||
|
font-family: "Noto Sans SC", system-ui, sans-serif;
|
||||||
|
font-size: inherit;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #536493;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
text-decoration-line: underline;
|
||||||
|
text-decoration-style: dashed;
|
||||||
|
text-decoration-thickness: 1px;
|
||||||
|
text-underline-offset: 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-link:hover,
|
||||||
|
.tag-link:focus-visible {
|
||||||
|
color: var(--text-color);
|
||||||
|
text-decoration: underline dashed 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-link:focus-visible {
|
||||||
|
outline: 2px solid currentColor;
|
||||||
|
outline-offset: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(html.dark) .tag-link {
|
||||||
|
color: #aabbd8;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(html.dark) .tag-link:hover,
|
||||||
|
:global(html.dark) .tag-link:focus-visible {
|
||||||
|
color: #d9e5f6;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -8,6 +8,7 @@ import "katex/dist/katex.min.css";
|
||||||
|
|
||||||
const lang = getLangFromUrl(Astro.url);
|
const lang = getLangFromUrl(Astro.url);
|
||||||
const htmlLang = lang === "zh" ? "zh-CN" : "en-US";
|
const htmlLang = lang === "zh" ? "zh-CN" : "en-US";
|
||||||
|
const isHomePage = new RegExp(`^/${lang}/?$`).test(Astro.url.pathname);
|
||||||
import SEO from "@/components/SEO.astro";
|
import SEO from "@/components/SEO.astro";
|
||||||
import FloatingActions from "@/components/FloatingActions.astro";
|
import FloatingActions from "@/components/FloatingActions.astro";
|
||||||
import Spinner from "@/components/Spinner.astro";
|
import Spinner from "@/components/Spinner.astro";
|
||||||
|
|
@ -44,6 +45,33 @@ const {
|
||||||
<FloatingActions />
|
<FloatingActions />
|
||||||
<script is:inline>
|
<script is:inline>
|
||||||
function addCodeCopyButtons() {
|
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) => {
|
document.querySelectorAll("pre").forEach((pre) => {
|
||||||
const code = pre.querySelector("code");
|
const code = pre.querySelector("code");
|
||||||
if (!code) return;
|
if (!code) return;
|
||||||
|
|
@ -62,6 +90,29 @@ const {
|
||||||
pre.parentNode?.insertBefore(wrapper, pre);
|
pre.parentNode?.insertBefore(wrapper, pre);
|
||||||
wrapper.appendChild(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");
|
const button = document.createElement("button");
|
||||||
button.className = "code-copy-button";
|
button.className = "code-copy-button";
|
||||||
|
|
@ -76,13 +127,15 @@ const {
|
||||||
icon.setAttribute("aria-hidden", "true");
|
icon.setAttribute("aria-hidden", "true");
|
||||||
|
|
||||||
button.appendChild(icon);
|
button.appendChild(icon);
|
||||||
wrapper.appendChild(button);
|
toolbar.appendChild(button);
|
||||||
|
|
||||||
button.addEventListener("click", async () => {
|
button.addEventListener("click", async () => {
|
||||||
const text = code.textContent ?? "";
|
const text = code.textContent ?? "";
|
||||||
|
let copied = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(text);
|
await navigator.clipboard.writeText(text);
|
||||||
|
copied = true;
|
||||||
} catch {
|
} catch {
|
||||||
const textarea = document.createElement("textarea");
|
const textarea = document.createElement("textarea");
|
||||||
textarea.value = text;
|
textarea.value = text;
|
||||||
|
|
@ -90,16 +143,25 @@ const {
|
||||||
textarea.style.opacity = "0";
|
textarea.style.opacity = "0";
|
||||||
textarea.style.pointerEvents = "none";
|
textarea.style.pointerEvents = "none";
|
||||||
|
|
||||||
|
try {
|
||||||
document.body.appendChild(textarea);
|
document.body.appendChild(textarea);
|
||||||
textarea.select();
|
textarea.select();
|
||||||
document.execCommand("copy");
|
copied = document.execCommand("copy");
|
||||||
|
} catch {
|
||||||
|
copied = false;
|
||||||
|
} finally {
|
||||||
textarea.remove();
|
textarea.remove();
|
||||||
|
button.focus({ preventScroll: true });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
icon.src = "/images/correct-signal-svgrepo-com.svg";
|
icon.src = copied
|
||||||
button.setAttribute("aria-label", "已复制");
|
? "/images/correct-signal-svgrepo-com.svg"
|
||||||
button.title = "已复制";
|
: "/images/copy-svgrepo-com.svg";
|
||||||
button.classList.add("copied");
|
const message = copied ? "已复制" : "复制失败,请手动选择代码复制";
|
||||||
|
button.setAttribute("aria-label", message);
|
||||||
|
button.title = message;
|
||||||
|
button.classList.toggle("copied", copied);
|
||||||
|
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
icon.src = "/images/copy-svgrepo-com.svg";
|
icon.src = "/images/copy-svgrepo-com.svg";
|
||||||
|
|
@ -115,7 +177,7 @@ const {
|
||||||
document.addEventListener("astro:page-load", addCodeCopyButtons);
|
document.addEventListener("astro:page-load", addCodeCopyButtons);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<body>
|
<body class:list={{ "is-home": isHomePage }}>
|
||||||
<Header />
|
<Header />
|
||||||
<Spinner />
|
<Spinner />
|
||||||
<main
|
<main
|
||||||
|
|
@ -129,10 +191,15 @@ const {
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
html,
|
html {
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
min-height: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
import BaseLayout from "./BaseLayout.astro";
|
import BaseLayout from "./BaseLayout.astro";
|
||||||
import Remark42Embed from "@/components/Remark42Embed.astro";
|
import Remark42Embed from "@/components/Remark42Embed.astro";
|
||||||
import PostNav from "@/components/Posts/PostNav.astro";
|
import PostNav from "@/components/Posts/PostNav.astro";
|
||||||
|
import TagLink from "@/components/TagLink.astro";
|
||||||
import PostMenu from "@/components/Posts/PostMenu.astro";
|
import PostMenu from "@/components/Posts/PostMenu.astro";
|
||||||
import Dialog from "@/components/Posts/Dialog.astro";
|
import Dialog from "@/components/Posts/Dialog.astro";
|
||||||
import { fade } from "astro:transitions";
|
import { fade } from "astro:transitions";
|
||||||
|
|
@ -11,6 +12,14 @@ import "@/styles/global.css";
|
||||||
const { post, frontmatter, lang, slug, postId, headings } = Astro.props;
|
const { post, frontmatter, lang, slug, postId, headings } = Astro.props;
|
||||||
const comments = lang === "zh" ? "评论区" : "comments";
|
const comments = lang === "zh" ? "评论区" : "comments";
|
||||||
const t = getTranslations(lang);
|
const t = getTranslations(lang);
|
||||||
|
const publishedDate = new Intl.DateTimeFormat(
|
||||||
|
lang === "zh" ? "zh-CN" : "en-US",
|
||||||
|
{
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
},
|
||||||
|
).format(frontmatter.pubDate);
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout
|
<BaseLayout
|
||||||
|
|
@ -18,6 +27,7 @@ const t = getTranslations(lang);
|
||||||
description={frontmatter.description}
|
description={frontmatter.description}
|
||||||
image={frontmatter.image?.url}
|
image={frontmatter.image?.url}
|
||||||
>
|
>
|
||||||
|
<div class="post-layout page-shell">
|
||||||
<div id="reading-progress" aria-hidden="true"></div>
|
<div id="reading-progress" aria-hidden="true"></div>
|
||||||
<a
|
<a
|
||||||
href="/"
|
href="/"
|
||||||
|
|
@ -50,19 +60,20 @@ const t = getTranslations(lang);
|
||||||
{frontmatter.title}
|
{frontmatter.title}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="description">
|
<p class="description">
|
||||||
<em>{frontmatter.description}</em>
|
{frontmatter.description}
|
||||||
</p>
|
</p>
|
||||||
<p class="meta-line">
|
<p class="post-byline">
|
||||||
{t.post.publishedOn}: {frontmatter.pubDate.toLocaleDateString()}
|
<span>{t.post.writtenBy} {frontmatter.author}</span>
|
||||||
|
<span class="byline-separator" aria-hidden="true">·</span>
|
||||||
|
<time datetime={frontmatter.pubDate.toISOString()}>
|
||||||
|
{t.post.publishedOn} {publishedDate}
|
||||||
|
</time>
|
||||||
</p>
|
</p>
|
||||||
<p class="meta-line">{t.post.writtenBy}: {frontmatter.author}</p>
|
|
||||||
|
|
||||||
<div class="tags" transition:name={`post-tags-${postId}`}>
|
<div class="tags" transition:name={`post-tags-${postId}`}>
|
||||||
{
|
{
|
||||||
frontmatter.tags.map((tag: string) => (
|
frontmatter.tags.map((tag: string) => (
|
||||||
<p class="tag">
|
<TagLink tag={tag} lang={lang} />
|
||||||
<a href={`/${lang}/tags/${tag}`}>{tag}</a>
|
|
||||||
</p>
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -86,10 +97,17 @@ const t = getTranslations(lang);
|
||||||
|
|
||||||
<PostNav post={post} lang={lang} />
|
<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} />)}
|
{t.post.commentsDesc.map((line: string) => <p set:html={line} />)}
|
||||||
<Remark42Embed slug={slug} />
|
<Remark42Embed slug={slug} />
|
||||||
|
</section>
|
||||||
</article>
|
</article>
|
||||||
|
</div>
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
|
||||||
<script
|
<script
|
||||||
|
|
@ -148,36 +166,68 @@ const t = getTranslations(lang);
|
||||||
backButton.dataset.bound = "true";
|
backButton.dataset.bound = "true";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const updateProgress = () => {
|
const initReadingProgress = () => {
|
||||||
const doc = document.documentElement;
|
const readingWindow = window as typeof window & {
|
||||||
const scrollTop = window.scrollY || doc.scrollTop;
|
__postReadingProgressHandler?: () => void;
|
||||||
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)}%`;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
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("scroll", updateProgress, { passive: true });
|
||||||
window.addEventListener("resize", updateProgress);
|
window.addEventListener("resize", updateProgress);
|
||||||
|
updateProgress();
|
||||||
|
};
|
||||||
|
|
||||||
|
initReadingProgress();
|
||||||
initFloatingActions();
|
initFloatingActions();
|
||||||
|
document.addEventListener("astro:page-load", initReadingProgress);
|
||||||
document.addEventListener("astro:page-load", initFloatingActions);
|
document.addEventListener("astro:page-load", initFloatingActions);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.post-layout {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
#reading-progress {
|
#reading-progress {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
width: 0;
|
width: 100%;
|
||||||
height: 3px;
|
height: 3px;
|
||||||
background: #5a89ff;
|
background: #5a89ff;
|
||||||
transition: width 0.08s linear;
|
transform: scaleX(0);
|
||||||
|
transform-origin: left center;
|
||||||
|
transition: transform 0.08s linear;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -192,15 +242,12 @@ const t = getTranslations(lang);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
border: #536493 1.5px solid;
|
border-top: #536493 1.5px dotted;
|
||||||
border-top: #536493 2.5px dotted;
|
border-left: #536493 1.5px dotted;
|
||||||
border-left: #536493 2.5px dotted;
|
|
||||||
border-right: #536493 2.5px solid;
|
border-right: #536493 2.5px solid;
|
||||||
border-bottom: #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;
|
color: #222;
|
||||||
/* box-shadow: 0 4px 14px rgba(0, 0, 0, 0.16); */
|
|
||||||
backdrop-filter: blur(6px);
|
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
transform: translateY(-80px);
|
transform: translateY(-80px);
|
||||||
|
|
@ -230,10 +277,16 @@ const t = getTranslations(lang);
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.dark) .back-button {
|
:global(.dark) .back-button {
|
||||||
background: rgba(34, 34, 34, 0.92);
|
background: color-mix(
|
||||||
|
in srgb,
|
||||||
|
var(--background-color-dark) 94%,
|
||||||
|
transparent
|
||||||
|
);
|
||||||
color: #f5f5f5;
|
color: #f5f5f5;
|
||||||
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.32);
|
border-top-color: #515151;
|
||||||
border: #2f406d 2px solid;
|
border-left-color: #515151;
|
||||||
|
border-right-color: #515151;
|
||||||
|
border-bottom-color: #515151;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
|
|
@ -245,6 +298,9 @@ const t = getTranslations(lang);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.post-content {
|
.post-content {
|
||||||
|
--article-muted: #596672;
|
||||||
|
--article-rule: rgba(83, 100, 147, 0.25);
|
||||||
|
--article-tint: rgba(83, 100, 147, 0.045);
|
||||||
font-family:
|
font-family:
|
||||||
system-ui,
|
system-ui,
|
||||||
-apple-system,
|
-apple-system,
|
||||||
|
|
@ -257,6 +313,130 @@ const t = getTranslations(lang);
|
||||||
"Source Han Sans SC",
|
"Source Han Sans SC",
|
||||||
sans-serif;
|
sans-serif;
|
||||||
font-weight: 400;
|
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),
|
.post-content :global(code),
|
||||||
|
|
@ -267,7 +447,7 @@ const t = getTranslations(lang);
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: var(--post-link-color);
|
color: var(--url-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-header {
|
.post-header {
|
||||||
|
|
@ -287,31 +467,64 @@ const t = getTranslations(lang);
|
||||||
line-height: 1.25;
|
line-height: 1.25;
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
|
text-wrap: balance;
|
||||||
}
|
}
|
||||||
|
|
||||||
.description {
|
.description {
|
||||||
margin: 0 0 0.7rem 0;
|
margin: 0 0 0.7rem 0;
|
||||||
opacity: 0.9;
|
color: #596672;
|
||||||
|
font-weight: 400;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.meta-line {
|
.post-byline {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 0.3rem 0.55rem;
|
||||||
margin: 0.3rem 0;
|
margin: 0.3rem 0;
|
||||||
line-height: 1.6;
|
color: #66717c;
|
||||||
|
font-family: "Noto Sans SC", system-ui, sans-serif;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.5;
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.byline-separator {
|
||||||
|
opacity: 0.65;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(html.dark) .post-byline {
|
||||||
|
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 {
|
.post-cover {
|
||||||
width: 300px;
|
width: 300px;
|
||||||
height: auto;
|
height: auto;
|
||||||
aspect-ratio: 16 / 10;
|
aspect-ratio: 16 / 10;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
object-position: center;
|
object-position: center;
|
||||||
/* border-radius: 0.4rem; */
|
|
||||||
border: 1.5px #94a0ab dashed;
|
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -327,39 +540,10 @@ const t = getTranslations(lang);
|
||||||
.tags {
|
.tags {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 0.4rem;
|
gap: 0.25rem 0.9rem;
|
||||||
margin-top: 0.8rem;
|
margin-top: 0.8rem;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
|
||||||
|
|
||||||
.tag {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0.22em 0.65em;
|
|
||||||
font-size: 0.88rem;
|
font-size: 0.88rem;
|
||||||
line-height: 1.2;
|
|
||||||
border: 1px dashed #8b6b4a;
|
|
||||||
border-radius: 0;
|
|
||||||
background-color: transparent;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tag a {
|
|
||||||
color: #6f4e37;
|
|
||||||
text-decoration: none;
|
|
||||||
overflow-wrap: anywhere;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tag a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.dark) .tag {
|
|
||||||
border-color: #d8c7a1;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.dark) .tag a {
|
|
||||||
color: #e6d8b8;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 560px) {
|
@media (max-width: 560px) {
|
||||||
|
|
@ -374,15 +558,16 @@ const t = getTranslations(lang);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
details {
|
.post-content :global(details) {
|
||||||
margin: 1.5rem 0;
|
margin: 1.5rem 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-top: 2px solid #1e3a5f;
|
border-top: 1px solid var(--article-rule);
|
||||||
border-radius: 0 0 0.5rem 0.5rem;
|
border-bottom: 1px solid var(--article-rule);
|
||||||
background: rgba(128, 128, 128, 0.14);
|
border-radius: 0;
|
||||||
|
background: var(--article-tint);
|
||||||
}
|
}
|
||||||
|
|
||||||
details summary {
|
.post-content :global(details summary) {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
|
|
@ -393,11 +578,11 @@ const t = getTranslations(lang);
|
||||||
list-style: none;
|
list-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
details summary::-webkit-details-marker {
|
.post-content :global(details summary::-webkit-details-marker) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
details summary::before {
|
.post-content :global(details summary::before) {
|
||||||
content: "›";
|
content: "›";
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
|
|
@ -405,46 +590,46 @@ const t = getTranslations(lang);
|
||||||
transition: transform 0.2s ease;
|
transition: transform 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
details[open] summary::before {
|
.post-content :global(details[open] summary::before) {
|
||||||
transform: rotate(90deg);
|
transform: rotate(90deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 展开内容 */
|
/* 展开内容 */
|
||||||
details > :not(summary) {
|
.post-content :global(details > :not(summary)) {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin-top: 0;
|
margin: 0.8rem 1rem;
|
||||||
margin-bottom: 0;
|
padding: 0;
|
||||||
padding-right: 1rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 为列表序号预留空间 */
|
/* 为列表序号预留空间 */
|
||||||
details > ol,
|
.post-content :global(details > ol),
|
||||||
details > ul {
|
.post-content :global(details > ul) {
|
||||||
padding: 0.6rem 1rem 1rem 3rem;
|
padding-inline-start: 1.6em;
|
||||||
}
|
}
|
||||||
|
|
||||||
details li {
|
.post-content :global(details li) {
|
||||||
margin: 0.8rem 0;
|
margin: 0.8rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
blockquote {
|
.post-content :global(blockquote) {
|
||||||
margin: 1.25rem 0;
|
margin: 1.5rem 0;
|
||||||
padding: 0.75rem 1rem;
|
padding: 0.15rem 0 0.15rem 1rem;
|
||||||
border-left: 4px solid var(--deep-red);
|
border-left: 3px solid var(--deep-blue);
|
||||||
background: rgba(128, 128, 128, 0.1);
|
color: var(--article-muted);
|
||||||
border-radius: 0 0.4rem 0.4rem 0;
|
background: transparent;
|
||||||
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
blockquote p {
|
.post-content :global(blockquote p) {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
blockquote p + p {
|
.post-content :global(blockquote p + p) {
|
||||||
margin-top: 0.75rem;
|
margin-top: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 块级公式:超出屏幕时只滚动公式,不撑宽整篇文章 */
|
/* 块级公式:超出屏幕时只滚动公式,不撑宽整篇文章 */
|
||||||
.katex-display {
|
.post-content :global(.katex-display) {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
|
|
@ -453,7 +638,27 @@ const t = getTranslations(lang);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 避免 KaTeX 内部元素强行把父容器撑宽 */
|
/* 避免 KaTeX 内部元素强行把父容器撑宽 */
|
||||||
.katex-display > .katex {
|
.post-content :global(.katex-display > .katex) {
|
||||||
min-width: max-content;
|
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>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import "@/styles/global.css";
|
||||||
pageTitle="404 Not Found"
|
pageTitle="404 Not Found"
|
||||||
description="Sorry, the page doesn't exists"
|
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" />
|
<img src="/images/marisa.png" alt="Marisa" class="marisa-404" />
|
||||||
<div class="hint">
|
<div class="hint">
|
||||||
<h3>对不起,您请求的页面不存在哦。</h3>
|
<h3>对不起,您请求的页面不存在哦。</h3>
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ const pageTitle = t.about.title;
|
||||||
pageTitle=`${pageTitle} - ${t.banner.title}`
|
pageTitle=`${pageTitle} - ${t.banner.title}`
|
||||||
image={t.about.profilePicture}
|
image={t.about.profilePicture}
|
||||||
>
|
>
|
||||||
<main class="about">
|
<div class="about page-shell">
|
||||||
<h1>{t.about.title}</h1>
|
<h1>{t.about.title}</h1>
|
||||||
|
|
||||||
<div class="intro">
|
<div class="intro">
|
||||||
|
|
@ -36,14 +36,12 @@ const pageTitle = t.about.title;
|
||||||
<div class="content">
|
<div class="content">
|
||||||
{t.about.content.map((line: string) => <p set:html={line} />)}
|
{t.about.content.map((line: string) => <p set:html={line} />)}
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</div>
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.about {
|
.about {
|
||||||
max-width: 800px;
|
padding-block: 2rem;
|
||||||
margin: 0 auto;
|
|
||||||
padding: 2rem 1rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.about h1 {
|
.about h1 {
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,11 @@ const headerTitle = lang === "zh" ? "友情链接" : "Friends";
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout pageTitle=`${headerTitle} - ${t.banner.title}`>
|
<BaseLayout pageTitle=`${headerTitle} - ${t.banner.title}`>
|
||||||
|
<div class="page-shell">
|
||||||
<FriendsDescription />
|
<FriendsDescription />
|
||||||
<div class="section-divider"></div>
|
<div class="section-divider"></div>
|
||||||
<FriendlyLinkList />
|
<FriendlyLinkList />
|
||||||
|
</div>
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,11 @@ const lang = getLangFromUrl(Astro.url);
|
||||||
const t = getTranslations(lang);
|
const t = getTranslations(lang);
|
||||||
const headerTitle = lang === "zh" ? "Cloverta的博客" : "Cloverta's blog";
|
const headerTitle = lang === "zh" ? "Cloverta的博客" : "Cloverta's blog";
|
||||||
const pageTitle = t.home.title;
|
const pageTitle = t.home.title;
|
||||||
const rssLink = lang === "zh" ? "/rss.xml" : "/en/rss.xml"
|
const rssLink = lang === "zh" ? "/rss.xml" : "/en/rss.xml";
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout pageTitle=`${headerTitle} - ${t.banner.subtitle}`>
|
<BaseLayout pageTitle=`${headerTitle} - ${t.banner.subtitle}`>
|
||||||
|
<div class="index page-shell">
|
||||||
<h1 class="page-title">
|
<h1 class="page-title">
|
||||||
<span>{pageTitle}</span>
|
<span>{pageTitle}</span>
|
||||||
<a
|
<a
|
||||||
|
|
@ -48,17 +49,22 @@ const rssLink = lang === "zh" ? "/rss.xml" : "/en/rss.xml"
|
||||||
|
|
||||||
<div class="section-divider"></div>
|
<div class="section-divider"></div>
|
||||||
|
|
||||||
<h2>{t.home.latestTitle}</h2>
|
<h2 class="section-title">{t.home.latestTitle}</h2>
|
||||||
|
|
||||||
<PostList />
|
<PostList />
|
||||||
|
|
||||||
<div class="section-divider"></div>
|
<div class="section-divider"></div>
|
||||||
|
|
||||||
<!-- <Remark42LatestComments lang={lang} client:load /> -->
|
<!-- <Remark42LatestComments lang={lang} client:load /> -->
|
||||||
<LatestComments />
|
<LatestComments lang={lang} />
|
||||||
|
</div>
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.index {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.section-divider {
|
.section-divider {
|
||||||
height: 1px;
|
height: 1px;
|
||||||
background: #cdd2d8;
|
background: #cdd2d8;
|
||||||
|
|
@ -73,4 +79,19 @@ const rssLink = lang === "zh" ? "/rss.xml" : "/en/rss.xml"
|
||||||
.page-title {
|
.page-title {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.section-title,
|
||||||
|
.index :global(.latest-comments > h2) {
|
||||||
|
margin: 0 0 1.25rem;
|
||||||
|
font-family: "Noto Sans SC", system-ui, sans-serif;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: #66717c;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(html.dark) .section-title,
|
||||||
|
:global(html.dark) .index :global(.latest-comments > h2) {
|
||||||
|
color: #aab7c4;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ const t = getTranslations(lang);
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout pageTitle={String(tag)}>
|
<BaseLayout pageTitle={String(tag)}>
|
||||||
|
<div class="page-shell">
|
||||||
<p class="tag-heading">
|
<p class="tag-heading">
|
||||||
{lang === "zh" ? "带有标签" : "Posts tagged with"}{" "}
|
{lang === "zh" ? "带有标签" : "Posts tagged with"}{" "}
|
||||||
<span class="tag-chip" transition:name={`post-tags-${tag}`}>{tag}</span>
|
<span class="tag-chip" transition:name={`post-tags-${tag}`}>{tag}</span>
|
||||||
|
|
@ -60,6 +61,7 @@ const t = getTranslations(lang);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
|
</div>
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
---
|
---
|
||||||
import BaseLayout from "@/layouts/BaseLayout.astro";
|
import BaseLayout from "@/layouts/BaseLayout.astro";
|
||||||
|
import TagLink from "@/components/TagLink.astro";
|
||||||
import { getCollection } from "astro:content";
|
import { getCollection } from "astro:content";
|
||||||
import { getTranslations, type Lang } from "@/i18n";
|
import { getTranslations, type Lang } from "@/i18n";
|
||||||
import "@/styles/global.css";
|
import "@/styles/global.css";
|
||||||
|
|
@ -16,6 +17,7 @@ const pageTitle = lang === "zh" ? "标签索引" : "Tag Index";
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout pageTitle=`${pageTitle} - ${t.banner.title}`>
|
<BaseLayout pageTitle=`${pageTitle} - ${t.banner.title}`>
|
||||||
|
<div class="tags-layout page-shell">
|
||||||
<h1>{t.tags.title}</h1>
|
<h1>{t.tags.title}</h1>
|
||||||
<p>{t.tags.description}</p>
|
<p>{t.tags.description}</p>
|
||||||
|
|
||||||
|
|
@ -23,60 +25,25 @@ const pageTitle = lang === "zh" ? "标签索引" : "Tag Index";
|
||||||
{
|
{
|
||||||
tags.map((tag) => (
|
tags.map((tag) => (
|
||||||
<p class="tag" transition:name={`post-tags-${tag}`}>
|
<p class="tag" transition:name={`post-tags-${tag}`}>
|
||||||
<a href={`/${lang}/tags/${tag}`}>{tag}</a>
|
<TagLink tag={String(tag)} lang={lang} />
|
||||||
</p>
|
</p>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.tags {
|
.tags {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 0.4rem;
|
gap: 0.4rem 1.25rem;
|
||||||
margin-top: 0.8rem;
|
margin-top: 0.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag {
|
.tag {
|
||||||
font-family:
|
|
||||||
"Noto Sans SC",
|
|
||||||
system-ui,
|
|
||||||
-apple-system,
|
|
||||||
BlinkMacSystemFont,
|
|
||||||
"Segoe UI",
|
|
||||||
Roboto,
|
|
||||||
Oxygen,
|
|
||||||
Ubuntu,
|
|
||||||
Cantarell,
|
|
||||||
"Open Sans",
|
|
||||||
"Helvetica Neue",
|
|
||||||
sans-serif;
|
|
||||||
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0.22em 0.65em;
|
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
line-height: 1.2;
|
max-width: 100%;
|
||||||
border: 1px dashed #8b6b4a;
|
|
||||||
border-radius: 0;
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tag a {
|
|
||||||
color: #6f4e37;
|
|
||||||
text-decoration: none;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tag a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.dark) .tag {
|
|
||||||
border-color: #d8c7a1;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.dark) .tag a {
|
|
||||||
color: #e6d8b8;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ const headerTitle = lang === "zh" ? "时间轴" : "Timeline";
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout pageTitle=`${headerTitle} - ${t.banner.title}` `>
|
<BaseLayout pageTitle=`${headerTitle} - ${t.banner.title}` `>
|
||||||
|
<div class="page-shell">
|
||||||
<h1>{t.nav.timeline}</h1>
|
<h1>{t.nav.timeline}</h1>
|
||||||
<PostTimeline />
|
<PostTimeline />
|
||||||
|
</div>
|
||||||
</BaseLayout>
|
</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 {
|
.code-block-wrapper {
|
||||||
position: relative;
|
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 {
|
.code-block-wrapper pre {
|
||||||
|
margin: 0;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
border-radius: 3px;
|
max-width: 100%;
|
||||||
|
border-radius: 0 0 3px 3px;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
margin: 1rem 0;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* pre:has(> .code-copy-button) {
|
|
||||||
padding-top: 2.75rem;
|
|
||||||
} */
|
|
||||||
|
|
||||||
code {
|
code {
|
||||||
font-family: "Maple Mono", monospace;
|
font-family: "Maple Mono", monospace;
|
||||||
font-size: 0.96rem;
|
font-size: 0.96rem;
|
||||||
|
|
@ -31,12 +41,15 @@ td code {
|
||||||
background: rgba(127, 127, 127, 0.12);
|
background: rgba(127, 127, 127, 0.12);
|
||||||
}
|
}
|
||||||
|
|
||||||
.code-copy-button {
|
.code-block-wrapper pre > code {
|
||||||
position: absolute;
|
padding: 0;
|
||||||
top: 0.5rem;
|
border-radius: 0;
|
||||||
right: 0.5rem;
|
background: transparent;
|
||||||
z-index: 2;
|
}
|
||||||
|
|
||||||
|
.code-copy-button {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
margin-left: auto;
|
||||||
display: grid;
|
display: grid;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
width: 2rem;
|
width: 2rem;
|
||||||
|
|
@ -58,6 +71,18 @@ td code {
|
||||||
box-shadow 0.15s ease;
|
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 {
|
.code-copy-icon {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
@ -81,6 +106,11 @@ td code {
|
||||||
background: #dcebd5;
|
background: #dcebd5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html.dark .code-language-label,
|
||||||
|
.dark .code-language-label {
|
||||||
|
color: #aeb9c4;
|
||||||
|
}
|
||||||
|
|
||||||
/* .dark .code-copy-button {
|
/* .dark .code-copy-button {
|
||||||
border-color: #9ecbff;
|
border-color: #9ecbff;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,23 +45,48 @@ body {
|
||||||
font-size: 1.15rem;
|
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 {
|
body::after {
|
||||||
content: "";
|
content: "";
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 400px;
|
width: clamp(300px, 31vw, 400px);
|
||||||
/* 按需调整大小 */
|
height: clamp(300px, 31vw, 400px);
|
||||||
height: 400px;
|
|
||||||
/* 按需调整大小 */
|
|
||||||
background-image: url("https://files.seeusercontent.com/2026/04/16/1sXb/touhou___kirisame_marisa__2__by_.webp");
|
background-image: url("https://files.seeusercontent.com/2026/04/16/1sXb/touhou___kirisame_marisa__2__by_.webp");
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-position: right bottom;
|
background-position: right bottom;
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
opacity: 0.35;
|
opacity: 0;
|
||||||
|
-webkit-mask-image: radial-gradient(
|
||||||
|
ellipse at right bottom,
|
||||||
|
#000 0%,
|
||||||
|
#000 38%,
|
||||||
|
transparent 78%
|
||||||
|
);
|
||||||
|
mask-image: radial-gradient(
|
||||||
|
ellipse at right bottom,
|
||||||
|
#000 0%,
|
||||||
|
#000 38%,
|
||||||
|
transparent 78%
|
||||||
|
);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
|
transition: opacity 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.is-home::after {
|
||||||
|
opacity: 0.16;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.dark body::after {
|
html.dark body::after {
|
||||||
|
|
@ -69,7 +94,7 @@ html.dark body::after {
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
body::after {
|
body.is-home::after {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -98,7 +123,7 @@ h1 {
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: var(--url-color);
|
color: var(--url-color);
|
||||||
font-weight: 700;
|
font-weight: inherit;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -119,7 +144,6 @@ html.dark {
|
||||||
|
|
||||||
.dark .nav-links a {
|
.dark .nav-links a {
|
||||||
color: #9ecbff;
|
color: #9ecbff;
|
||||||
font-weight: 700;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .nav-links a:hover,
|
.dark .nav-links a:hover,
|
||||||
|
|
@ -129,7 +153,6 @@ html.dark {
|
||||||
|
|
||||||
.dark a {
|
.dark a {
|
||||||
color: #9ecbff;
|
color: #9ecbff;
|
||||||
font-weight: 700;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark a:hover,
|
.dark a:hover,
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,15 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading-card {
|
.loading-card {
|
||||||
height: 92px;
|
height: 82px;
|
||||||
margin: 0 0 14px 0;
|
padding: 1rem 0;
|
||||||
padding: 0.9rem 1rem;
|
border-bottom: 1px solid rgba(110, 125, 140, 0.28);
|
||||||
border: 1px dashed #aeb8c2;
|
background: linear-gradient(
|
||||||
background: linear-gradient(90deg,
|
90deg,
|
||||||
rgba(160, 175, 190, 0.06) 25%,
|
transparent 15%,
|
||||||
rgba(160, 175, 190, 0.16) 50%,
|
rgba(160, 175, 190, 0.13) 50%,
|
||||||
rgba(160, 175, 190, 0.06) 75%);
|
transparent 85%
|
||||||
|
);
|
||||||
background-size: 200% 100%;
|
background-size: 200% 100%;
|
||||||
animation: shimmer 1.2s infinite linear;
|
animation: shimmer 1.2s infinite linear;
|
||||||
}
|
}
|
||||||
|
|
@ -34,65 +35,58 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment-card {
|
.comment-card {
|
||||||
display: block;
|
padding: 1rem 0;
|
||||||
margin: 0 0 14px 0;
|
border-bottom: 1px solid rgba(110, 125, 140, 0.28);
|
||||||
padding: 0.6rem 1rem;
|
}
|
||||||
border: 2px dashed #aeb8c2;
|
|
||||||
background-color: var(--comment-background);
|
|
||||||
|
|
||||||
|
.comment-card:first-child {
|
||||||
|
padding-top: 0.35rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment-card:last-child {
|
.comment-card:last-child {
|
||||||
margin-bottom: 0;
|
padding-bottom: 0;
|
||||||
|
border-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment-card-body {
|
.comment-card-body {
|
||||||
display: grid;
|
min-width: 0;
|
||||||
grid-template-columns: minmax(0, 1fr) 320px;
|
|
||||||
grid-template-areas:
|
|
||||||
"info title"
|
|
||||||
"text title";
|
|
||||||
gap: 0.2rem 1rem;
|
|
||||||
align-items: start;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment-card-info {
|
.comment-card-info {
|
||||||
grid-area: info;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.7rem;
|
gap: 0.65rem;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment-avatar {
|
.comment-avatar {
|
||||||
width: 35px;
|
width: 32px;
|
||||||
height: 35px;
|
height: 32px;
|
||||||
flex-shrink: 0;
|
flex: 0 0 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment-avatar-img,
|
.comment-avatar-img,
|
||||||
.comment-avatar-fallback {
|
.comment-avatar-fallback {
|
||||||
width: 35px;
|
width: 32px;
|
||||||
height: 35px;
|
height: 32px;
|
||||||
display: block;
|
display: block;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment-avatar-img {
|
.comment-avatar-img {
|
||||||
border: 1px dashed #aeb8c2;
|
background: rgba(160, 175, 190, 0.1);
|
||||||
background: #f3f5f7;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment-avatar-fallback {
|
.comment-avatar-fallback {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
border: 1px dashed #aeb8c2;
|
background: rgba(160, 175, 190, 0.14);
|
||||||
background: rgba(160, 175, 190, 0.12);
|
|
||||||
color: #5e6b77;
|
color: #5e6b77;
|
||||||
font-weight: 700;
|
font-family: system-ui, sans-serif;
|
||||||
font-size: 1rem;
|
font-size: 0.85rem;
|
||||||
|
font-weight: 600;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,90 +99,92 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
gap: 0.6rem;
|
gap: 0.25rem 0.55rem;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
line-height: 1.3;
|
font-family: system-ui, sans-serif;
|
||||||
font-style: italic;
|
line-height: 1.35;
|
||||||
font-family:
|
|
||||||
system-ui,
|
|
||||||
-apple-system,
|
|
||||||
BlinkMacSystemFont,
|
|
||||||
"Segoe UI",
|
|
||||||
"PingFang SC",
|
|
||||||
"Hiragino Sans GB",
|
|
||||||
"Microsoft YaHei",
|
|
||||||
"Noto Sans CJK SC",
|
|
||||||
"Source Han Sans SC",
|
|
||||||
sans-serif;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment-author {
|
.comment-author,
|
||||||
|
.comment-reply-to {
|
||||||
|
color: #66717c;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
font-weight: 500;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-reply-sep,
|
||||||
|
.comment-time {
|
||||||
|
color: #7a858f;
|
||||||
|
font-size: 0.72rem;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 0.8rem;
|
}
|
||||||
color: gray;
|
|
||||||
|
.comment-card-text {
|
||||||
|
display: -webkit-box;
|
||||||
|
margin: 0.6rem 0 0.5rem;
|
||||||
|
max-height: 4.65em;
|
||||||
|
overflow: hidden;
|
||||||
|
color: #3f4a54;
|
||||||
|
font-family: system-ui, sans-serif;
|
||||||
|
font-size: 0.93rem;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.55;
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment-time {
|
.comment-source {
|
||||||
color: #7a7a7a;
|
display: flex;
|
||||||
font-size: 0.7rem;
|
align-items: baseline;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 0.55rem 1rem;
|
||||||
|
min-width: 0;
|
||||||
|
color: #89939c;
|
||||||
|
font-family: system-ui, sans-serif;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
line-height: 1.45;
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment-card-title {
|
.comment-source-context {
|
||||||
grid-area: title;
|
display: flex;
|
||||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
align-items: baseline;
|
||||||
text-align: right;
|
gap: 0.4rem;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment-title-link {
|
.comment-title-link {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
color: #6f8090;
|
color: #6f8090;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
font-weight: 400;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: 1.1rem;
|
text-overflow: ellipsis;
|
||||||
font-weight: 700;
|
white-space: nowrap;
|
||||||
line-height: 1.45;
|
|
||||||
white-space: normal;
|
|
||||||
overflow-wrap: anywhere;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment-title-link:hover {
|
.comment-title-link:hover,
|
||||||
|
.comment-title-link:focus-visible {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment-card-text {
|
.comment-discussion-link {
|
||||||
grid-area: text;
|
display: none;
|
||||||
margin: 0;
|
flex: 0 0 auto;
|
||||||
color: #555;
|
color: var(--url-color);
|
||||||
line-height: 1.5;
|
font-size: 0.76rem;
|
||||||
font-size: 0.93rem;
|
font-weight: 500;
|
||||||
min-width: 0;
|
text-decoration-line: underline;
|
||||||
overflow-wrap: anywhere;
|
text-decoration-style: dashed;
|
||||||
word-break: break-word;
|
text-underline-offset: 0.22em;
|
||||||
white-space: normal;
|
white-space: nowrap;
|
||||||
font-family:
|
|
||||||
system-ui,
|
|
||||||
-apple-system,
|
|
||||||
BlinkMacSystemFont,
|
|
||||||
"Segoe UI",
|
|
||||||
"PingFang SC",
|
|
||||||
"Hiragino Sans GB",
|
|
||||||
"Microsoft YaHei",
|
|
||||||
"Noto Sans CJK SC",
|
|
||||||
"Source Han Sans SC",
|
|
||||||
sans-serif;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment-card-text :global(p),
|
.comment-card.is-truncated .comment-discussion-link {
|
||||||
.comment-card-text p {
|
display: inline;
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comment-card-text :global(img),
|
|
||||||
.comment-card-text img {
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.latest-comments-empty {
|
.latest-comments-empty {
|
||||||
|
|
@ -196,68 +192,67 @@
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment-reply-sep {
|
html.dark .comment-card,
|
||||||
font-size: 0.8rem;
|
.dark .comment-card,
|
||||||
opacity: 0.7;
|
html.dark .loading-card,
|
||||||
|
.dark .loading-card {
|
||||||
|
border-color: rgba(170, 183, 196, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
.latest-comments .comment-reply-to {
|
html.dark .comment-card-text,
|
||||||
font-weight: 400;
|
.dark .comment-card-text {
|
||||||
font-size: 0.8rem;
|
|
||||||
color: gray;
|
|
||||||
opacity: 0.9;
|
|
||||||
overflow-wrap: anywhere;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
html.dark .latest-comments .comment-card,
|
|
||||||
.dark .latest-comments .comment-card {
|
|
||||||
border-color: #7f8c97;
|
|
||||||
background-color: #252525;
|
|
||||||
}
|
|
||||||
|
|
||||||
html.dark .comment-title-link {
|
|
||||||
color: #c8d2dc;
|
|
||||||
}
|
|
||||||
|
|
||||||
html.dark .comment-card-text {
|
|
||||||
color: #d3d7db;
|
color: #d3d7db;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.dark .comment-time {
|
html.dark .comment-author,
|
||||||
color: #a8b0b7;
|
.dark .comment-author,
|
||||||
|
html.dark .comment-reply-to,
|
||||||
|
.dark .comment-reply-to {
|
||||||
|
color: #b9c3cc;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.dark .latest-comments .comment-avatar-fallback,
|
html.dark .comment-time,
|
||||||
.dark .latest-comments .comment-avatar-fallback {
|
.dark .comment-time,
|
||||||
border-color: #7f8c97;
|
html.dark .comment-reply-sep,
|
||||||
background-color: #3a444d;
|
.dark .comment-reply-sep,
|
||||||
color: #dbe3ea;
|
html.dark .comment-source,
|
||||||
|
.dark .comment-source {
|
||||||
|
color: #929da7;
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.dark) .comment-avatar-fallback {
|
html.dark .comment-title-link,
|
||||||
background: rgba(180, 190, 200, 0.12);
|
.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);
|
||||||
color: #dbe3ea;
|
color: #dbe3ea;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
.comment-card-body {
|
.comment-card {
|
||||||
grid-template-columns: 1fr;
|
padding-block: 0.85rem;
|
||||||
grid-template-areas:
|
|
||||||
"title"
|
|
||||||
"info"
|
|
||||||
"text";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment-card-title {
|
.comment-card-text {
|
||||||
text-align: left;
|
margin-top: 0.5rem;
|
||||||
margin-bottom: 0.2rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment-title-link {
|
.comment-source {
|
||||||
display: block;
|
align-items: flex-start;
|
||||||
white-space: nowrap;
|
flex-wrap: wrap;
|
||||||
overflow: hidden;
|
}
|
||||||
text-overflow: ellipsis;
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.loading-card {
|
||||||
|
animation: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
/* 默认配色 */
|
/* 默认配色 */
|
||||||
:root {
|
:root {
|
||||||
|
--page-content-width: 850px;
|
||||||
--deep-blue: #536493;
|
--deep-blue: #536493;
|
||||||
--deep-red: #ef5a6f;
|
--deep-red: #ef5a6f;
|
||||||
--background-color: #f9f2ed;
|
--background-color: #f9f2ed;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue