@xingwangzhe/stalux 1.35.11 → 1.35.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -11
- package/package.json +5 -2
- package/src/components/stalux/common/Typewriter.astro +32 -1
- package/src/components/stalux/posts/postContent.astro +7 -1
- package/src/index.ts +51 -104
- package/src/internal/bounded-tasks.ts +28 -0
- package/src/internal/compact-styles.ts +55 -13
- package/src/internal/font-slices.ts +150 -47
- package/src/internal/html-images.ts +2 -0
- package/src/internal/html-output.ts +98 -0
- package/src/internal/image-dimensions.ts +99 -74
- package/src/internal/page-output-cache.ts +26 -9
- package/src/internal/public-assets.ts +59 -0
- package/src/internal/runtime-cache-key.ts +14 -3
- package/src/internal/style-names.generated.ts +27 -26
- package/src/layouts/PostLayout.astro +1 -0
- package/src/layouts/Stalux.astro +3 -1
- package/src/pages/api/post.abbrlink.json.ts +0 -3
- package/src/pages/api/posts.json.ts +16 -19
- package/src/pages/openapi.json.ts +3 -44
- package/src/pages/posts/[post].md.ts +4 -40
- package/src/plugins/feature-flags.ts +8 -1
- package/src/scripts/navigation-font.ts +28 -0
- package/src/scripts/navigation.ts +4 -2
- package/src/scripts/page-runtime.ts +20 -6
- package/src/scripts/pagefind-api.ts +63 -0
- package/src/scripts/photoswipe.ts +13 -3
- package/src/scripts/random-posts.ts +12 -10
- package/src/scripts/search-dialog.ts +2 -1
- package/src/scripts/view-transitions.ts +12 -1
- package/src/scripts/webmcp.ts +397 -141
- package/src/scripts/word-list.ts +13 -3
- package/src/styles/base.css +7 -0
- package/src/styles/generated.css +1 -1
- package/src/utils/collections-stats.ts +9 -22
- package/src/utils/content-index.ts +13 -4
- package/src/utils/dayjs.ts +26 -11
- package/src/utils/feed.ts +30 -29
- package/src/utils/post-lookup.ts +51 -0
- package/src/utils/post-markdown.ts +31 -0
- package/src/utils/site-path.ts +4 -0
- package/src/utils/toc-scrollspy.ts +20 -6
- package/src/utils/word-count-utils.ts +45 -41
package/src/scripts/word-list.ts
CHANGED
|
@@ -14,10 +14,16 @@ function scrollToWord(hash: string): void {
|
|
|
14
14
|
registerPageLifecycle("word-list", () => {
|
|
15
15
|
if (!document.querySelector("[data-word-hash]")) return;
|
|
16
16
|
const controller = new AbortController();
|
|
17
|
+
let firstFrame = 0;
|
|
18
|
+
let secondFrame = 0;
|
|
17
19
|
|
|
18
20
|
if (location.hash.startsWith("#word-")) {
|
|
19
|
-
requestAnimationFrame(() => {
|
|
20
|
-
|
|
21
|
+
firstFrame = requestAnimationFrame(() => {
|
|
22
|
+
firstFrame = 0;
|
|
23
|
+
secondFrame = requestAnimationFrame(() => {
|
|
24
|
+
secondFrame = 0;
|
|
25
|
+
scrollToWord(location.hash);
|
|
26
|
+
});
|
|
21
27
|
});
|
|
22
28
|
}
|
|
23
29
|
|
|
@@ -35,5 +41,9 @@ registerPageLifecycle("word-list", () => {
|
|
|
35
41
|
{ signal: controller.signal },
|
|
36
42
|
);
|
|
37
43
|
|
|
38
|
-
return () =>
|
|
44
|
+
return () => {
|
|
45
|
+
controller.abort();
|
|
46
|
+
if (firstFrame) cancelAnimationFrame(firstFrame);
|
|
47
|
+
if (secondFrame) cancelAnimationFrame(secondFrame);
|
|
48
|
+
};
|
|
39
49
|
});
|
package/src/styles/base.css
CHANGED
|
@@ -15,6 +15,13 @@
|
|
|
15
15
|
var(--font-body), "Noto Sans SC", "Noto Sans CJK SC", system-ui, -apple-system,
|
|
16
16
|
sans-serif;
|
|
17
17
|
text-rendering: optimizeLegibility;
|
|
18
|
+
/* Keep line boxes stable while the page font replaces the system fallback. */
|
|
19
|
+
line-height: 1.25;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* Reserve the classic scrollbar before content height changes across routes. */
|
|
23
|
+
html {
|
|
24
|
+
scrollbar-gutter: stable;
|
|
18
25
|
}
|
|
19
26
|
|
|
20
27
|
html[data-clarity-id] {
|
package/src/styles/generated.css
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
@layer theme, base, components, utilities;
|
|
2
|
-
@layer components{.stalux-404-home-btn{background:var(--black-20p);border:var(--border-thin) solid var(--white-20p);transition:background-color var(--transition-normal), transform var(--transition-normal), box-shadow var(--transition-normal);border-radius:var(--radius-md);padding:var(--space-md) var(--space-lg);color:var(--white-90p);font-size:var(--font-size-base);text-decoration:none;display:inline-block}.stalux-ai-prompt{clip-path:inset(50%);white-space:pre-wrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.stalux-badge-group{border:none}.stalux-badge-group-arrow{font-size:.65rem;transition:transform .2s;display:inline-block}.stalux-badge-group-summary{user-select:none;cursor:pointer;color:var(--white-70p);gap:.4rem;padding:.2rem 0;font-size:.85rem;list-style-type:none;transition:color .2s}.stalux-beian-public-security-beian{color:#ffffffb3;gap:5px;font-size:.9rem;text-decoration:none;transition:color .3s;display:inline-flex}.stalux-card-post-categories,.stalux-card-post-tags{gap:.4rem}.stalux-card-post-category{background:var(--category-bg);color:#fff;border-radius:6px;padding:.18rem .5rem;font-size:.84rem;line-height:1;display:inline-block}.stalux-card-post-description{overflow-wrap:anywhere;color:var(--white-70p);line-height:1.6}.stalux-card-post-meta{gap:.6rem}.stalux-card-post-tag{background:var(--tag-bg);color:#fff;border-radius:9999px;padding:.22rem .7rem;font-size:.85rem;line-height:1;display:inline-block}.stalux-card-post-title{overflow-wrap:anywhere;margin:0;line-height:1.35}.stalux-card-read-more{align-self:flex-start;margin-top:.6rem}.stalux-card-time{gap:.45rem;display:inline-flex}.stalux-copyright{color:var(--white-70p);gap:.3em;font-weight:500}.stalux-footer{backdrop-filter:blur(10px);color:var(--white-full);background-color:#0000001a;margin-top:auto;padding:1.5rem 0;font-size:.9rem}.stalux-footer-content{gap:.8rem;max-width:1200px;margin:0 auto;padding:0 1rem}.stalux-home-navs{order:-1;max-width:100%}.stalux-nav-list{transform:scale(var(--nav-scale,1));transform-origin:50%;box-sizing:border-box;gap:2rem;max-width:100%;transition:transform .22s cubic-bezier(.22,.72,.22,1);overflow:visible}.stalux-nav-overlay{pointer-events:none;z-index:999;opacity:0;background:#0009;height:100%;transition:opacity .3s;display:none;position:fixed;top:0;left:0}.stalux-nav-scroll-btn{background:var(--black-30p);backdrop-filter:blur(8px);box-shadow:var(--shadow-md);pointer-events:none;z-index:10;cursor:pointer;width:36px;height:36px;color:var(--white-full);opacity:0;border:none;border-radius:9999px;padding:0;transition:opacity .25s;display:none;position:absolute;top:50%;transform:translateY(-50%)}.stalux-nav-toggle{z-index:1001;cursor:pointer;min-width:48px;min-height:48px;color:inherit;background:0 0;border:none;padding:.75rem;display:none}.stalux-post-card{box-sizing:border-box;cursor:pointer;gap:var(--space-lg);border-radius:var(--radius-lg);background-color:var(--card-bg);height:100%;padding:var(--space-lg);box-shadow:var(--card-box-shadow);transition:transform .22s cubic-bezier(.22,.72,.22,1)}.stalux-stats-site-build-time{color:#fffc;margin-top:.5rem;font-size:.9rem}.stalux-stats-stats-separator{color:#ffffff80}.stalux-theme-info{color:var(--white-70p);gap:.3em;font-size:.85rem}.stalux-home-body{min-height:100vh;padding-bottom:var(--space-3xl);position:relative}.stalux-nav-container{box-sizing:border-box;max-width:100%;position:relative;overflow:hidden}.stalux-author{margin-top:4em}.stalux-card-post-cover{border-radius:var(--radius-md);flex-shrink:0;height:180px;margin-bottom:.5rem;overflow:hidden}.stalux-home-title{color:var(--white-full);text-shadow:var(--stalux-text-shadow);margin-top:1.5em;font-size:4em}.stalux-nav-item{text-shadow:var(--stalux-text-shadow);flex-shrink:0;margin-left:1em;margin-right:1em;font-size:1.3em}.stalux-page-description{color:var(--white-60p);margin-bottom:1.5em;font-size:1.2em}.stalux-page-title{text-shadow:var(--stalux-text-shadow);margin-top:1.5em;font-size:2.5em}.stalux-typewriter{min-height:2em;text-shadow:var(--stalux-text-shadow);margin-top:1.5em;font-size:2.5em}.stalux-404-error-container{min-height:60vh;padding:2rem}.stalux-badge,.stalux-badges-wrapper .stalux-badge{width:auto}.stalux-u-min-w-0{min-width:0}.stalux-u-w-full{width:100%}.stalux-u-flex{display:flex}.stalux-u-flex-col{flex-direction:column}.stalux-u-flex-wrap{flex-wrap:wrap}.stalux-u-items-center{align-items:center}.stalux-u-justify-center{justify-content:center}.stalux-badges,.stalux-badges-wrapper{gap:.5rem}.stalux-beian-beian-info{gap:1rem}.stalux-stats-site-stats{gap:.5rem;font-size:.9rem}.stalux-nav-text{white-space:nowrap;color:var(--white-full)}.stalux-badge-group-items{padding-top:.35rem}.stalux-u-text-center{text-align:center}.stalux-badge-group-hint{color:var(--white-70p);font-size:.7rem}.stalux-badge-group-title{color:var(--white-80p)}#stalux-home-show-post-list{cursor:pointer;text-shadow:var(--stalux-text-shadow);margin-top:3em;font-size:1.5em}.stalux-post-list-container{width:100%;height:auto;margin:5px 0}.stalux-post-list-container ul{flex-wrap:wrap;gap:2rem;margin:3em;padding:0;list-style-type:none;display:flex}.stalux-post-list-container ul>li{flex:min(22rem,100%);min-width:0;margin:0}.stalux-card-post-title a{color:inherit;text-decoration:none}.stalux-post-card>a:first-child{width:100%;min-width:0;display:block}.stalux-card-post-cover img{object-fit:cover;width:100%;max-width:100%;height:100%;transition:transform var(--transition-fast)}.stalux-card-post-date,.stalux-card-post-updated{color:#e8f2ff;border-radius:10px;align-items:center;gap:.35rem;width:fit-content;padding:.28rem .6rem;font-size:.9rem;line-height:1.1;display:inline-flex}.stalux-card-post-date{background:var(--date-bg);border:1px solid var(--date-border);color:#e8f2ff}.stalux-card-post-updated{background:var(--updated-bg);border:1px solid var(--updated-border);color:#fff7de}.stalux-card-post-tags a,.stalux-card-post-categories a{text-decoration:none}.stalux-card-post-tags a:focus-visible .stalux-card-post-tag{background:var(--tag-bg-hover)}.stalux-card-post-categories a:focus-visible .stalux-card-post-category{background:var(--category-bg-hover)}.stalux-social{margin-top:3em}.stalux-social ul{flex-wrap:wrap;justify-content:center;gap:1em;margin:0;padding:0;display:flex}.stalux-social ul li{box-sizing:border-box;text-align:center;background-color:var(--white-15p);border-radius:9999px;justify-content:center;align-items:center;width:2.5em;height:2.5em;font-size:large;display:inline-flex;overflow:hidden}.stalux-social ul a,.stalux-social ul .stalux-social-icon-container,.stalux-social ul .stalux-social-custom-icon{justify-content:center;align-items:center;width:100%;height:100%;line-height:1;display:inline-flex}.stalux-social[data-stalux-social-animate=true] li{animation:.28s cubic-bezier(.2,.8,.2,1) both stalux-social-stalux-social-arrive}.stalux-social[data-stalux-social-animate=true] li:nth-child(2){animation-delay:30ms}.stalux-social[data-stalux-social-animate=true] li:nth-child(3){animation-delay:60ms}.stalux-social[data-stalux-social-animate=true] li:nth-child(4){animation-delay:90ms}.stalux-social[data-stalux-social-animate=true] li:nth-child(5){animation-delay:.12s}.stalux-social[data-stalux-social-animate=true] li:nth-child(6){animation-delay:.15s}.stalux-social[data-stalux-social-animate=true] li:nth-child(n+7){animation-delay:.18s}nav{width:100%;min-width:0;max-width:100%}.stalux-nav-scroll-btn[data-visible]{pointer-events:auto;opacity:1}.stalux-nav-scroll-btn[data-side=left]{left:4px}.stalux-nav-scroll-btn[data-side=right]{right:4px}.stalux-nav-scroll-btn svg{stroke:currentColor;stroke-width:2px;fill:none;width:20px;height:20px}.stalux-nav-link,.stalux-nav-link:visited{cursor:pointer;width:100%;color:inherit;min-height:44px;font:inherit;background:0 0;border:none;align-items:center;gap:.5rem;padding:.5rem .75rem;text-decoration:none;display:flex}.stalux-nav-link .nav-icon{stroke:var(--white-full)}.stalux-nav-toggle svg{width:32px;height:32px}.stalux-nav-overlay[data-state=show]{pointer-events:auto;opacity:1;display:block}#stalux-to-archive-archs{margin-top:2em}#stalux-date-date-time{text-shadow:var(--stalux-text-shadow);color:var(--white-full);flex-direction:column;align-items:center;margin-top:2em;display:flex}#stalux-date-date-time #stalux-date-date{margin-bottom:.5em;font-size:2em}#stalux-date-date-time #stalux-date-time{font-size:3em}.stalux-author-figure{justify-content:center;align-items:center;display:flex}.stalux-author-figure img{box-shadow:0 0 10px var(--stalux-accent-color);border-radius:9999px;transition:transform .65s ease-in-out}.stalux-author-figure figcaption{text-shadow:var(--stalux-text-shadow);color:var(--white-full);margin-left:1em;font-size:2.5em;font-weight:700}.stalux-author-figure-column figcaption{text-align:center;margin-top:.5em;margin-left:0}.agent-home-summary{clip-path:inset(50%);white-space:normal;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.stalux-404-error-content h1{color:var(--white-90p);margin:0;font-size:6rem;font-weight:700}.stalux-404-error-content p{color:var(--white-70p);margin:1rem 0 2rem;font-size:1.5rem}.stalux-404-countdown-text{color:var(--white-50p)!important;margin:-.5rem 0 1.5rem!important;font-size:.95rem!important}.agent-recovery{max-width:42rem;line-height:1.7;margin:0 auto 1.5rem!important;font-size:1rem!important}.agent-recovery a{color:inherit;text-decoration-line:underline}.stalux-theme-info span,.stalux-copyright span{margin:0}.stalux-theme-info a{color:#fffc;margin:0 .2em;text-decoration:none;transition:color .3s}.stalux-beian-beian-info a{color:#ffffffb3;font-size:.9rem;text-decoration:none;transition:color .3s}.stalux-beian-public-security-beian img{width:15px;height:15px;display:inline-block}.stalux-badge-group-summary::-webkit-details-marker,.stalux-badge-group[open] .stalux-badge-group-hint[data-hint=collapsed],.stalux-badge-group:not([open]) .stalux-badge-group-hint[data-hint=expanded]{display:none}.stalux-badge-group[open] .stalux-badge-group-arrow{transform:rotate(90deg)}@media (hover:hover) and (pointer:fine){.stalux-card-post-tags a:hover .stalux-card-post-tag{background:var(--tag-bg-hover)}.stalux-card-post-categories a:hover .stalux-card-post-category{background:var(--category-bg-hover)}.stalux-post-card-shell:hover .stalux-post-card .stalux-card-post-cover img{transform:scale(1.025)}.stalux-post-card-shell:hover .stalux-post-card{box-shadow:var(--card-box-shadow-hover)}.stalux-nav-scroll-btn:hover{background:0 0}.stalux-nav-container:hover .stalux-nav-scroll-btn[data-visible]{opacity:1}.stalux-nav-link:hover .nav-icon,.stalux-nav-link:hover .stalux-nav-text{stroke:var(--stalux-accent-color);color:var(--stalux-accent-color)}.stalux-author-figure>a:hover img{animation:.65s ease-in-out stalux-link-avatar-spin}.stalux-404-home-btn:hover{background:var(--accent-20p);box-shadow:0 4px 12px var(--shadow-dark)}.stalux-theme-info a:hover{color:#fff;text-decoration:none}.stalux-beian-beian-info a:hover,.stalux-beian-public-security-beian:hover{color:#ffffffe6;text-decoration:none}.stalux-badge-group-summary:hover{color:var(--white-70p)}}@media (width<=1100px){.stalux-nav-list{gap:1rem}.stalux-nav-item{margin-left:.6em;margin-right:.6em;font-size:1em}}@media (width<=900px){.stalux-nav-item{margin-left:.25em;margin-right:.25em;font-size:.95em}.stalux-nav-list{gap:.5rem}}@media (width<=768px){.stalux-home-body{box-sizing:border-box;width:100%;max-width:95vw;height:auto;padding:2rem .5rem 1.5rem}.stalux-home-title{word-break:break-all;margin-top:1em;font-size:2em}.stalux-home-navs{flex-direction:column;align-items:center;gap:.5em}.stalux-home-navs nav{width:100%}#stalux-home-show-post-list{margin-top:2em;font-size:1.1em}.stalux-post-list-container ul{margin:1rem}.stalux-footer{padding:1.2rem 0;font-size:.8rem}.stalux-footer-content{gap:.6rem;padding:0 .8rem}.stalux-author{box-sizing:border-box;width:100%;max-width:95vw;margin-top:2em;padding:1em .5em}.stalux-author-figure{gap:.5em;flex-direction:column!important}.stalux-author-figure img{max-width:20vw;max-height:20vw;width:64px!important;height:64px!important}.stalux-author-figure figcaption{text-align:center;word-break:break-all;overflow-wrap:break-word;white-space:normal;max-width:90vw;margin-top:.5em;margin-left:0;font-size:1.5em}.stalux-404-error-content h1{font-size:4rem}.stalux-404-error-content p{font-size:1.2rem}.stalux-badges{gap:.3rem}.stalux-badge{width:auto}}@media (width<=600px){.stalux-nav-toggle{display:block;position:fixed;top:.75rem;left:.75rem}.stalux-nav-container{overflow:visible}nav{width:auto}.stalux-nav-list{pointer-events:none;z-index:1000;background:var(--black-20p);backdrop-filter:blur(20px);flex-direction:column;justify-content:flex-start;align-items:flex-start;gap:0;width:min(220px,100vw - 1rem);height:100vh;padding:.5rem 1rem 2rem;position:fixed;top:0;left:0;right:auto;overflow-y:auto;transform:translate(-100%)}.stalux-nav-list[data-state=open]{pointer-events:auto;transform:translate(0)}.stalux-nav-item{border-bottom:1px solid var(--white-10p);flex-shrink:0;width:100%;margin:0;font-size:.9em}.stalux-nav-text{display:inline}.stalux-nav-link{width:100%;min-height:44px;font-size:inherit;padding:.45rem .75rem}.stalux-nav-link .nav-icon{width:18px;height:18px}}@media (width<=480px){.stalux-footer{padding:1rem 0;font-size:.8rem}.stalux-footer-content{gap:.6rem;padding:0 .8rem}}@media (prefers-reduced-motion:reduce){.stalux-social[data-stalux-social-animate=true] li{animation:none}}.stalux-main,.stalux-post-page,.stalux-post-layout>*,.stalux-link-card-content,.stalux-word-list-item,.stalux-word-card{box-sizing:border-box;min-width:0;max-width:100%}.stalux-main{overflow-wrap:anywhere}.stalux-link-shell{min-width:0;max-width:100%;display:block}.stalux-link-card{box-sizing:border-box;width:100%;max-width:100%}.stalux-category-shell{width:100%;min-width:0}.stalux-author-figure>a{flex-shrink:0;display:inline-block}.stalux-author-figure img{display:block}.stalux-post-card-shell,.stalux-category-shell,.stalux-link-shell{position:relative}.stalux-post-card,.stalux-category-card,.stalux-link-card,.stalux-word-card{transition:transform .2s,background-color .2s,border-color .2s,box-shadow .2s}:is(.stalux-post-card-shell,.stalux-category-shell):focus-within>*,.stalux-link-shell:focus-visible>*{box-shadow:var(--card-box-shadow-hover)}:is(button,summary,[role=button]):focus-visible{outline:2px solid var(--stalux-accent-color);outline-offset:3px}@media (prefers-reduced-motion:reduce){.stalux-main *,.stalux-main :before,.stalux-main :after{transition-duration:0s!important;animation:none!important}.stalux-post-card,.stalux-category-card,.stalux-link-card,.stalux-link-card-content,.stalux-card-post-cover img,.stalux-category-icon,.stalux-author-figure img,.stalux-link-card-avatar img{transform:none!important}}@keyframes stalux-social-stalux-social-arrive{0%{opacity:0;transform:translateY(.75rem)rotate(-12deg)}to{opacity:1;transform:translateY(0)rotate(0)}}@keyframes stalux-random-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes stalux-post-content-reveal{0%{opacity:0}to{opacity:1}}@keyframes stalux-link-avatar-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes stalux-word-word-glow{0%{box-shadow:0 0 30px var(--accent-50p), 0 0 80px var(--accent-30p), var(--shadow-lg);transform:scale(1.02)}to{box-shadow:0 0 20px var(--accent-40p), 0 0 60px var(--accent-20p), var(--shadow-lg);transform:scale(1)}}}@layer utilities{.sr-only{clip:rect(0,0,0,0);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.visible{visibility:visible}.absolute{position:absolute}.static{position:static}.grid{display:grid}.block{display:block}.inline-block{display:inline-block}.hidden{display:none}.h-full{height:100%}.flex{display:flex}.flex-shrink{flex-shrink:1}.flex-wrap{flex-wrap:wrap}.transform{transform:translateX(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotateZ(var(--un-rotate-z)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z))}.b,.border{border-width:1px}.px{padding-left:1rem;padding-right:1rem}.text-inherit{color:inherit}.no-underline{text-decoration:none}.outline{outline-style:solid}}@layer theme;@layer base{html,body{color:#fff;width:100%;height:100%;margin:0;& main{flex:1}scrollbar-color:#4a4a4a #2a2a2a;font-family:var(--font-body), "Noto Sans SC", "Noto Sans CJK SC", system-ui, -apple-system, sans-serif;text-rendering:optimizelegibility;display:block}html[data-clarity-id]{opacity:1!important}a{color:inherit;cursor:pointer;text-decoration-skip-ink:auto;background-color:#0000;text-decoration:none;transition:color .2s,text-shadow .2s;position:relative}.a-none{cursor:pointer;text-decoration-skip-ink:auto;background-color:#0000;color:inherit!important;text-decoration:none!important}.a-none:after{display:none!important}a:not(.a-none):not([data-stalux-card]):not(.stalux-nav-link):after{content:"";background:linear-gradient(90deg, var(--stalux-accent-color), var(--accent-50p));transform-origin:0;pointer-events:none;width:100%;height:1px;transition:transform .25s cubic-bezier(.25,.9,.2,1);position:absolute;bottom:-2px;left:0;transform:scaleX(0)}a:not(.a-none):not([data-stalux-card]):not(.stalux-nav-link):focus-visible:after,a:not(.a-none):not([data-stalux-card]):not(.stalux-nav-link):active:after{transform:scaleX(1)}@media (hover:hover) and (pointer:fine){a:not(.a-none):not([data-stalux-card]):not(.stalux-nav-link):hover:after{transform:scaleX(1)}}a:active,a:focus-visible{text-decoration:none}@media (hover:hover) and (pointer:fine){a:hover{text-decoration:none}}a:focus{outline:none}a:focus-visible{outline:2px solid var(--stalux-accent-color);outline-offset:2px}ul{margin:0;padding:0;list-style:none}.none{display:none}.stalux-root{color:#fff;min-height:100vh}.stalux-main{min-height:100vh}:root{--stalux-bg-color:#202020;--stalux-bg-filter:none;--stalux-text-shadow:var(--stalux-accent-color) .08rem .08rem .18rem;--stalux-accent-color:#eab308;--stalux-accent-rgb:234 179 8;--surface-glass:var(--black-05p);--surface-glass-shadow:var(--shadow-sidebar);--card-bg:var(--surface-glass);--card-border:#ffffff1a;--card-box-shadow:0 10px 24px #00000014;--card-box-shadow-hover:0 14px 28px #0000001f;--tag-bg:#ffffff1f;--tag-bg-hover:#ffffff2e;--category-bg:#ffffff14;--category-bg-hover:#ffffff24;--date-bg:#42a0ff2e;--date-border:#42a0ff40;--updated-bg:#ffc94733;--updated-border:#ffc9474d;--white-full:#fff;--white-95p:#fffffff2;--white-90p:#ffffffe6;--white-85p:#ffffffd9;--white-80p:#ffffffe6;--white-78p:#ffffffc7;--white-75p:#ffffffbf;--white-70p:#ffffffdb;--white-60p:#fff9;--white-50p:#ffffff80;--white-30p:#ffffff4d;--white-20p:#fff3;--white-15p:#ffffff26;--white-10p:#ffffff1a;--white-05p:#ffffff0d;--black-30p:#0000004d;--black-20p:#0003;--black-15p:#00000026;--black-10p:#0000001a;--black-05p:#0000000d;--accent-90p:rgb(var(--stalux-accent-rgb) / .9);--accent-85p:rgb(var(--stalux-accent-rgb) / .85);--accent-80p:rgb(var(--stalux-accent-rgb) / .8);--accent-70p:rgb(var(--stalux-accent-rgb) / .7);--accent-60p:rgb(var(--stalux-accent-rgb) / .6);--accent-50p:rgb(var(--stalux-accent-rgb) / .5);--accent-40p:rgb(var(--stalux-accent-rgb) / .4);--accent-30p:rgb(var(--stalux-accent-rgb) / .3);--accent-20p:rgb(var(--stalux-accent-rgb) / .2);--transition-fast:.18s ease;--transition-medium:.2s ease;--transition-normal:.25s ease;--transition-slow:.3s ease;--transition-slower:.5s ease;--transition-slowest:.6s ease;--easing-bounce:cubic-bezier(.175, .885, .32, 1.275);--space-2xs:.25rem;--space-xs:.75rem;--space-sm:.5rem;--space-md:1rem;--space-lg:1.5rem;--space-xl:2rem;--space-2xl:3rem;--space-3xl:4rem;--radius-sm:4px;--radius-md:6px;--radius-lg:8px;--radius-full:9999px;--shadow-sm:0 2px 5px #0000001a;--shadow-md:0 6px 16px #0003;--shadow-lg:0 8px 20px #0000004d;--shadow-xl:0 12px 26px #00000047;--shadow-sidebar:0 16px 20px -12px #000000d6, 10px 4px 16px -12px #00000094, -10px 4px 16px -12px #00000094;--shadow-raised-hover:0 20px 24px -13px #000000e6, 12px 5px 18px -13px #000000ad, -12px 5px 18px -13px #000000ad;--shadow-dark:#000000c0;--muted-text:#bbb;--font-body:"Noto Sans SC", "Noto Sans CJK SC", system-ui, sans-serif;--font-size-subtitle:1.35rem;--font-size-base:1.1rem;--font-size-small:.95rem;--font-size-smaller:.9rem;--font-size-tiny:.85rem;--border-thin:1px;--border-medium:2px;--border-thick:3px}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:#2a2a2a;border-radius:4px}::-webkit-scrollbar-thumb{background:#4a4a4a;border-radius:4px;transition:background .3s ease-out}@media (hover:hover) and (pointer:fine){::-webkit-scrollbar-thumb:hover{background:#5a5a5a}}::-webkit-scrollbar-corner{background:#2a2a2a}.bg-layer{z-index:-10;background-position:50%;background-repeat:repeat;background-size:auto;background-color:var(--stalux-bg-color,transparent);filter:var(--stalux-bg-filter,none);pointer-events:none;opacity:0;forced-color-adjust:none;color-scheme:light only;transition:opacity .65s ease-in-out;position:fixed;inset:0}@media (prefers-reduced-motion:reduce){.bg-layer{transition-duration:0s}}footer{padding:var(--space-lg) var(--space-md);text-align:center;color:var(--muted-text);border-top:1px solid var(--white-10p);backdrop-filter:blur(16px);background:linear-gradient(#0c0e122e,#0c0e12b8)}}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.stagger-children>:first-child{animation-delay:.1s}.stagger-children>:nth-child(2){animation-delay:.15s}.stagger-children>:nth-child(3){animation-delay:.2s}.stagger-children>:nth-child(4){animation-delay:.25s}.stagger-children>:nth-child(5){animation-delay:.3s}.stagger-children>:nth-child(n+6){animation-delay:.35s}@keyframes stalux-page-exit{to{opacity:0}}@keyframes stalux-page-enter{0%{opacity:0;transform:translateY(9px)}to{opacity:1;transform:translateY(0)}}@keyframes stalux-page-enter-back{0%{opacity:0;transform:translateY(-9px)}to{opacity:1;transform:translateY(0)}}@keyframes stalux-taxonomy-enter{0%{opacity:0;transform:translate(12px)}to{opacity:1;transform:translate(0)}}@keyframes stalux-taxonomy-enter-back{0%{opacity:0;transform:translate(-12px)}to{opacity:1;transform:translate(0)}}@keyframes stalux-article-enter{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}@keyframes stalux-article-enter-back{0%{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:translateY(0)}}@keyframes stalux-sidebar-left-in{0%{opacity:0;transform:translate(-14px)}to{opacity:1;transform:translate(0)}}@keyframes stalux-sidebar-left-out{to{opacity:0;transform:translate(-10px)}}@keyframes stalux-sidebar-left-back-in{0%{opacity:0;transform:translate(14px)}to{opacity:1;transform:translate(0)}}@keyframes stalux-sidebar-left-back-out{to{opacity:0;transform:translate(10px)}}@keyframes stalux-sidebar-right-in{0%{opacity:0;transform:translate(14px)}to{opacity:1;transform:translate(0)}}@keyframes stalux-sidebar-right-out{to{opacity:0;transform:translate(10px)}}@keyframes stalux-sidebar-right-back-in{0%{opacity:0;transform:translate(-14px)}to{opacity:1;transform:translate(0)}}@keyframes stalux-sidebar-right-back-out{to{opacity:0;transform:translate(-10px)}}::view-transition-group(stalux-page){z-index:1}::view-transition-old(stalux-page),::view-transition-new(stalux-page){transform-origin:top}::view-transition-group(*){animation-duration:.24s;animation-timing-function:cubic-bezier(.22,.72,.22,1)}@media (prefers-reduced-motion:reduce){::view-transition-group(*),::view-transition-old(*),::view-transition-new(*){animation-duration:1ms!important;animation-delay:0s!important}.stalux-main,.stalux-main *,.postLayout *{scroll-behavior:auto!important;transition-duration:1ms!important;animation-duration:1ms!important;animation-iteration-count:1!important}}
|
|
2
|
+
@layer components{.stalux-404-home-btn{background:var(--black-20p);border:var(--border-thin) solid var(--white-20p);transition:background-color var(--transition-normal), transform var(--transition-normal), box-shadow var(--transition-normal);border-radius:var(--radius-md);padding:var(--space-md) var(--space-lg);color:var(--white-90p);font-size:var(--font-size-base);text-decoration:none;display:inline-block}.stalux-ai-prompt{clip-path:inset(50%);white-space:pre-wrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.stalux-badge-group{border:none}.stalux-badge-group-arrow{font-size:.65rem;transition:transform .2s;display:inline-block}.stalux-badge-group-summary{user-select:none;cursor:pointer;color:var(--white-70p);gap:.4rem;padding:.2rem 0;font-size:.85rem;list-style-type:none;transition:color .2s}.stalux-beian-public-security-beian{color:#ffffffb3;gap:5px;font-size:.9rem;text-decoration:none;transition:color .3s;display:inline-flex}.stalux-card-post-categories,.stalux-card-post-tags{gap:.4rem}.stalux-card-post-category{background:var(--category-bg);color:#fff;border-radius:6px;padding:.18rem .5rem;font-size:.84rem;line-height:1;display:inline-block}.stalux-card-post-description{overflow-wrap:anywhere;color:var(--white-70p);line-height:1.6}.stalux-card-post-meta{gap:.6rem}.stalux-card-post-tag{background:var(--tag-bg);color:#fff;border-radius:9999px;padding:.22rem .7rem;font-size:.85rem;line-height:1;display:inline-block}.stalux-card-post-title{overflow-wrap:anywhere;margin:0;line-height:1.35}.stalux-card-read-more{align-self:flex-start;margin-top:.6rem}.stalux-card-time{gap:.45rem;display:inline-flex}.stalux-copyright{color:var(--white-70p);gap:.3em;font-weight:500}.stalux-footer{backdrop-filter:blur(10px);color:var(--white-full);background-color:#0000001a;margin-top:auto;padding:1.5rem 0;font-size:.9rem}.stalux-footer-content{gap:.8rem;max-width:1200px;margin:0 auto;padding:0 1rem}.stalux-home-navs{order:-1;max-width:100%}.stalux-nav-list{transform:scale(var(--nav-scale,1));transform-origin:50%;box-sizing:border-box;gap:2rem;max-width:100%;transition:transform .22s cubic-bezier(.22,.72,.22,1);overflow:visible}.stalux-nav-overlay{pointer-events:none;z-index:999;opacity:0;background:#0009;height:100%;transition:opacity .3s;display:none;position:fixed;top:0;left:0}.stalux-nav-scroll-btn{background:var(--black-30p);backdrop-filter:blur(8px);box-shadow:var(--shadow-md);pointer-events:none;z-index:10;cursor:pointer;width:36px;height:36px;color:var(--white-full);opacity:0;border:none;border-radius:9999px;padding:0;transition:opacity .25s;display:none;position:absolute;top:50%;transform:translateY(-50%)}.stalux-nav-toggle{z-index:1001;cursor:pointer;min-width:48px;min-height:48px;color:inherit;background:0 0;border:none;padding:.75rem;display:none}.stalux-post-card{box-sizing:border-box;cursor:pointer;gap:var(--space-lg);border-radius:var(--radius-lg);background-color:var(--card-bg);height:100%;padding:var(--space-lg);box-shadow:var(--card-box-shadow);transition:transform .22s cubic-bezier(.22,.72,.22,1)}.stalux-stats-site-build-time{color:#fffc;margin-top:.5rem;font-size:.9rem}.stalux-stats-stats-separator{color:#ffffff80}.stalux-theme-info{color:var(--white-70p);gap:.3em;font-size:.85rem}.stalux-home-body{min-height:100vh;padding-bottom:var(--space-3xl);position:relative}.stalux-nav-container{box-sizing:border-box;max-width:100%;position:relative;overflow:hidden}.stalux-author{margin-top:4em}.stalux-card-post-cover{border-radius:var(--radius-md);flex-shrink:0;height:180px;margin-bottom:.5rem;overflow:hidden}.stalux-home-title{color:var(--white-full);text-shadow:var(--stalux-text-shadow);margin-top:1.5em;font-size:4em}.stalux-nav-item{text-shadow:var(--stalux-text-shadow);flex-shrink:0;margin-left:1em;margin-right:1em;font-size:1.3em}.stalux-page-description{color:var(--white-60p);margin-bottom:1.5em;font-size:1.2em}.stalux-page-title{text-shadow:var(--stalux-text-shadow);margin-top:1.5em;font-size:2.5em}.stalux-typewriter{min-height:2em;text-shadow:var(--stalux-text-shadow);margin-top:1.5em;font-size:2.5em}.stalux-404-error-container{min-height:60vh;padding:2rem}.stalux-badge,.stalux-badges-wrapper .stalux-badge{width:auto}.stalux-u-min-w-0{min-width:0}.stalux-u-w-full{width:100%}.stalux-u-flex{display:flex}.stalux-u-flex-col{flex-direction:column}.stalux-u-flex-wrap{flex-wrap:wrap}.stalux-u-items-center{align-items:center}.stalux-u-justify-center{justify-content:center}.stalux-badges,.stalux-badges-wrapper{gap:.5rem}.stalux-beian-beian-info{gap:1rem}.stalux-stats-site-stats{gap:.5rem;font-size:.9rem}.stalux-nav-text{white-space:nowrap;color:var(--white-full)}.stalux-badge-group-items{padding-top:.35rem}.stalux-u-text-center{text-align:center}.stalux-badge-group-hint{color:var(--white-70p);font-size:.7rem}.stalux-badge-group-title{color:var(--white-80p)}#stalux-home-show-post-list{cursor:pointer;text-shadow:var(--stalux-text-shadow);margin-top:3em;font-size:1.5em}.stalux-post-list-container{width:100%;height:auto;margin:5px 0}.stalux-post-list-container ul{flex-wrap:wrap;gap:2rem;margin:3em;padding:0;list-style-type:none;display:flex}.stalux-post-list-container ul>li{flex:min(22rem,100%);min-width:0;margin:0}.stalux-card-post-title a{color:inherit;text-decoration:none}.stalux-post-card>a:first-child{width:100%;min-width:0;display:block}.stalux-card-post-cover img{object-fit:cover;width:100%;max-width:100%;height:100%;transition:transform var(--transition-fast)}.stalux-card-post-date,.stalux-card-post-updated{color:#e8f2ff;border-radius:10px;align-items:center;gap:.35rem;width:fit-content;padding:.28rem .6rem;font-size:.9rem;line-height:1.1;display:inline-flex}.stalux-card-post-date{background:var(--date-bg);border:1px solid var(--date-border);color:#e8f2ff}.stalux-card-post-updated{background:var(--updated-bg);border:1px solid var(--updated-border);color:#fff7de}.stalux-card-post-tags a,.stalux-card-post-categories a{text-decoration:none}.stalux-card-post-tags a:focus-visible .stalux-card-post-tag{background:var(--tag-bg-hover)}.stalux-card-post-categories a:focus-visible .stalux-card-post-category{background:var(--category-bg-hover)}.stalux-social{margin-top:3em}.stalux-social ul{flex-wrap:wrap;justify-content:center;gap:1em;margin:0;padding:0;display:flex}.stalux-social ul li{box-sizing:border-box;text-align:center;background-color:var(--white-15p);border-radius:9999px;justify-content:center;align-items:center;width:2.5em;height:2.5em;font-size:large;display:inline-flex;overflow:hidden}.stalux-social ul a,.stalux-social ul .stalux-social-icon-container,.stalux-social ul .stalux-social-custom-icon{justify-content:center;align-items:center;width:100%;height:100%;line-height:1;display:inline-flex}.stalux-social[data-stalux-social-animate=true] li{animation:.28s cubic-bezier(.2,.8,.2,1) both stalux-social-stalux-social-arrive}.stalux-social[data-stalux-social-animate=true] li:nth-child(2){animation-delay:30ms}.stalux-social[data-stalux-social-animate=true] li:nth-child(3){animation-delay:60ms}.stalux-social[data-stalux-social-animate=true] li:nth-child(4){animation-delay:90ms}.stalux-social[data-stalux-social-animate=true] li:nth-child(5){animation-delay:.12s}.stalux-social[data-stalux-social-animate=true] li:nth-child(6){animation-delay:.15s}.stalux-social[data-stalux-social-animate=true] li:nth-child(n+7){animation-delay:.18s}nav{width:100%;min-width:0;max-width:100%}.stalux-nav-scroll-btn[data-visible]{pointer-events:auto;opacity:1}.stalux-nav-scroll-btn[data-side=left]{left:4px}.stalux-nav-scroll-btn[data-side=right]{right:4px}.stalux-nav-scroll-btn svg{stroke:currentColor;stroke-width:2px;fill:none;width:20px;height:20px}.stalux-nav-link,.stalux-nav-link:visited{cursor:pointer;width:100%;color:inherit;min-height:44px;font:inherit;background:0 0;border:none;align-items:center;gap:.5rem;padding:.5rem .75rem;text-decoration:none;display:flex}.stalux-nav-link .nav-icon{stroke:var(--white-full)}.stalux-nav-toggle svg{width:32px;height:32px}.stalux-nav-overlay[data-state=show]{pointer-events:auto;opacity:1;display:block}#stalux-to-archive-archs{margin-top:2em}#stalux-date-date-time{text-shadow:var(--stalux-text-shadow);color:var(--white-full);flex-direction:column;align-items:center;margin-top:2em;display:flex}#stalux-date-date-time #stalux-date-date{min-height:1.25em;margin-bottom:.5em;font-size:2em;line-height:1.25}#stalux-date-date-time #stalux-date-time{font-variant-numeric:tabular-nums;min-height:1.25em;font-size:3em;line-height:1.25}.stalux-author-figure{justify-content:center;align-items:center;display:flex}.stalux-author-figure img{box-shadow:0 0 10px var(--stalux-accent-color);border-radius:9999px;transition:transform .65s ease-in-out}.stalux-author-figure figcaption{text-shadow:var(--stalux-text-shadow);color:var(--white-full);margin-left:1em;font-size:2.5em;font-weight:700}.stalux-author-figure-column figcaption{text-align:center;margin-top:.5em;margin-left:0}.agent-home-summary{clip-path:inset(50%);white-space:normal;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.stalux-404-error-content h1{color:var(--white-90p);margin:0;font-size:6rem;font-weight:700}.stalux-404-error-content p{color:var(--white-70p);margin:1rem 0 2rem;font-size:1.5rem}.stalux-404-countdown-text{color:var(--white-50p)!important;margin:-.5rem 0 1.5rem!important;font-size:.95rem!important}.agent-recovery{max-width:42rem;line-height:1.7;margin:0 auto 1.5rem!important;font-size:1rem!important}.agent-recovery a{color:inherit;text-decoration-line:underline}.stalux-theme-info span,.stalux-copyright span{margin:0}.stalux-theme-info a{color:#fffc;margin:0 .2em;text-decoration:none;transition:color .3s}.stalux-beian-beian-info a{color:#ffffffb3;font-size:.9rem;text-decoration:none;transition:color .3s}.stalux-beian-public-security-beian img{width:15px;height:15px;display:inline-block}.stalux-badge-group-summary::-webkit-details-marker,.stalux-badge-group[open] .stalux-badge-group-hint[data-hint=collapsed],.stalux-badge-group:not([open]) .stalux-badge-group-hint[data-hint=expanded]{display:none}.stalux-badge-group[open] .stalux-badge-group-arrow{transform:rotate(90deg)}@media (hover:hover) and (pointer:fine){.stalux-card-post-tags a:hover .stalux-card-post-tag{background:var(--tag-bg-hover)}.stalux-card-post-categories a:hover .stalux-card-post-category{background:var(--category-bg-hover)}.stalux-post-card-shell:hover .stalux-post-card .stalux-card-post-cover img{transform:scale(1.025)}.stalux-post-card-shell:hover .stalux-post-card{box-shadow:var(--card-box-shadow-hover)}.stalux-nav-scroll-btn:hover{background:0 0}.stalux-nav-container:hover .stalux-nav-scroll-btn[data-visible]{opacity:1}.stalux-nav-link:hover .nav-icon,.stalux-nav-link:hover .stalux-nav-text{stroke:var(--stalux-accent-color);color:var(--stalux-accent-color)}.stalux-author-figure>a:hover img{animation:.65s ease-in-out stalux-link-avatar-spin}.stalux-404-home-btn:hover{background:var(--accent-20p);box-shadow:0 4px 12px var(--shadow-dark)}.stalux-theme-info a:hover{color:#fff;text-decoration:none}.stalux-beian-beian-info a:hover,.stalux-beian-public-security-beian:hover{color:#ffffffe6;text-decoration:none}.stalux-badge-group-summary:hover{color:var(--white-70p)}}@media (width<=1100px){.stalux-nav-list{gap:1rem}.stalux-nav-item{margin-left:.6em;margin-right:.6em;font-size:1em}}@media (width<=900px){.stalux-nav-item{margin-left:.25em;margin-right:.25em;font-size:.95em}.stalux-nav-list{gap:.5rem}}@media (width<=768px){.stalux-home-body{box-sizing:border-box;width:100%;max-width:95vw;height:auto;padding:2rem .5rem 1.5rem}.stalux-home-title{word-break:break-all;margin-top:1em;font-size:2em}.stalux-home-navs{flex-direction:column;align-items:center;gap:.5em}.stalux-home-navs nav{width:100%}#stalux-home-show-post-list{margin-top:2em;font-size:1.1em}.stalux-post-list-container ul{margin:1rem}.stalux-footer{padding:1.2rem 0;font-size:.8rem}.stalux-footer-content{gap:.6rem;padding:0 .8rem}.stalux-author{box-sizing:border-box;width:100%;max-width:95vw;margin-top:2em;padding:1em .5em}.stalux-author-figure{gap:.5em;flex-direction:column!important}.stalux-author-figure img{max-width:20vw;max-height:20vw;width:64px!important;height:64px!important}.stalux-author-figure figcaption{text-align:center;word-break:break-all;overflow-wrap:break-word;white-space:normal;max-width:90vw;margin-top:.5em;margin-left:0;font-size:1.5em}.stalux-404-error-content h1{font-size:4rem}.stalux-404-error-content p{font-size:1.2rem}.stalux-badges{gap:.3rem}.stalux-badge{width:auto}}@media (width<=600px){.stalux-nav-toggle{display:block;position:fixed;top:.75rem;left:.75rem}.stalux-nav-container{overflow:visible}nav{width:auto}.stalux-nav-list{pointer-events:none;z-index:1000;background:var(--black-20p);backdrop-filter:blur(20px);flex-direction:column;justify-content:flex-start;align-items:flex-start;gap:0;width:min(220px,100vw - 1rem);height:100vh;padding:.5rem 1rem 2rem;position:fixed;top:0;left:0;right:auto;overflow-y:auto;transform:translate(-100%)}.stalux-nav-list[data-state=open]{pointer-events:auto;transform:translate(0)}.stalux-nav-item{border-bottom:1px solid var(--white-10p);flex-shrink:0;width:100%;margin:0;font-size:.9em}.stalux-nav-text{display:inline}.stalux-nav-link{width:100%;min-height:44px;font-size:inherit;padding:.45rem .75rem}.stalux-nav-link .nav-icon{width:18px;height:18px}}@media (width<=480px){.stalux-footer{padding:1rem 0;font-size:.8rem}.stalux-footer-content{gap:.6rem;padding:0 .8rem}}@media (prefers-reduced-motion:reduce){.stalux-social[data-stalux-social-animate=true] li{animation:none}}.stalux-main,.stalux-post-page,.stalux-post-layout>*,.stalux-link-card-content,.stalux-word-list-item,.stalux-word-card{box-sizing:border-box;min-width:0;max-width:100%}.stalux-main{overflow-wrap:anywhere}.stalux-link-shell{min-width:0;max-width:100%;display:block}.stalux-link-card{box-sizing:border-box;width:100%;max-width:100%}.stalux-category-shell{width:100%;min-width:0}.stalux-author-figure>a{flex-shrink:0;display:inline-block}.stalux-author-figure img{display:block}.stalux-post-card-shell,.stalux-category-shell,.stalux-link-shell{position:relative}.stalux-post-card,.stalux-category-card,.stalux-link-card,.stalux-word-card{transition:transform .2s,background-color .2s,border-color .2s,box-shadow .2s}:is(.stalux-post-card-shell,.stalux-category-shell):focus-within>*,.stalux-link-shell:focus-visible>*{box-shadow:var(--card-box-shadow-hover)}:is(button,summary,[role=button]):focus-visible{outline:2px solid var(--stalux-accent-color);outline-offset:3px}@media (prefers-reduced-motion:reduce){.stalux-main *,.stalux-main :before,.stalux-main :after{transition-duration:0s!important;animation:none!important}.stalux-post-card,.stalux-category-card,.stalux-link-card,.stalux-link-card-content,.stalux-card-post-cover img,.stalux-category-icon,.stalux-author-figure img,.stalux-link-card-avatar img{transform:none!important}}@keyframes stalux-social-stalux-social-arrive{0%{opacity:0;transform:translateY(.75rem)rotate(-12deg)}to{opacity:1;transform:translateY(0)rotate(0)}}@keyframes stalux-random-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes stalux-post-content-reveal{0%{opacity:0}to{opacity:1}}@keyframes stalux-link-avatar-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes stalux-word-word-glow{0%{box-shadow:0 0 30px var(--accent-50p), 0 0 80px var(--accent-30p), var(--shadow-lg);transform:scale(1.02)}to{box-shadow:0 0 20px var(--accent-40p), 0 0 60px var(--accent-20p), var(--shadow-lg);transform:scale(1)}}}@layer utilities{.sr-only{clip:rect(0,0,0,0);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.visible{visibility:visible}.absolute{position:absolute}.static{position:static}.grid{display:grid}.block{display:block}.inline-block{display:inline-block}.hidden{display:none}.h-full{height:100%}.flex{display:flex}.flex-shrink{flex-shrink:1}.flex-wrap{flex-wrap:wrap}.transform{transform:translateX(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotateZ(var(--un-rotate-z)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z))}.b,.border{border-width:1px}.px{padding-left:1rem;padding-right:1rem}.text-inherit{color:inherit}.no-underline{text-decoration:none}.outline{outline-style:solid}}@layer theme;@layer base{html,body{color:#fff;width:100%;height:100%;margin:0;& main{flex:1}scrollbar-color:#4a4a4a #2a2a2a;font-family:var(--font-body), "Noto Sans SC", "Noto Sans CJK SC", system-ui, -apple-system, sans-serif;text-rendering:optimizelegibility;line-height:1.25;display:block}html{scrollbar-gutter:stable}html[data-clarity-id]{opacity:1!important}a{color:inherit;cursor:pointer;text-decoration-skip-ink:auto;background-color:#0000;text-decoration:none;transition:color .2s,text-shadow .2s;position:relative}.a-none{cursor:pointer;text-decoration-skip-ink:auto;background-color:#0000;color:inherit!important;text-decoration:none!important}.a-none:after{display:none!important}a:not(.a-none):not([data-stalux-card]):not(.stalux-nav-link):after{content:"";background:linear-gradient(90deg, var(--stalux-accent-color), var(--accent-50p));transform-origin:0;pointer-events:none;width:100%;height:1px;transition:transform .25s cubic-bezier(.25,.9,.2,1);position:absolute;bottom:-2px;left:0;transform:scaleX(0)}a:not(.a-none):not([data-stalux-card]):not(.stalux-nav-link):focus-visible:after,a:not(.a-none):not([data-stalux-card]):not(.stalux-nav-link):active:after{transform:scaleX(1)}@media (hover:hover) and (pointer:fine){a:not(.a-none):not([data-stalux-card]):not(.stalux-nav-link):hover:after{transform:scaleX(1)}}a:active,a:focus-visible{text-decoration:none}@media (hover:hover) and (pointer:fine){a:hover{text-decoration:none}}a:focus{outline:none}a:focus-visible{outline:2px solid var(--stalux-accent-color);outline-offset:2px}ul{margin:0;padding:0;list-style:none}.none{display:none}.stalux-root{color:#fff;min-height:100vh}.stalux-main{min-height:100vh}:root{--stalux-bg-color:#202020;--stalux-bg-filter:none;--stalux-text-shadow:var(--stalux-accent-color) .08rem .08rem .18rem;--stalux-accent-color:#eab308;--stalux-accent-rgb:234 179 8;--surface-glass:var(--black-05p);--surface-glass-shadow:var(--shadow-sidebar);--card-bg:var(--surface-glass);--card-border:#ffffff1a;--card-box-shadow:0 10px 24px #00000014;--card-box-shadow-hover:0 14px 28px #0000001f;--tag-bg:#ffffff1f;--tag-bg-hover:#ffffff2e;--category-bg:#ffffff14;--category-bg-hover:#ffffff24;--date-bg:#42a0ff2e;--date-border:#42a0ff40;--updated-bg:#ffc94733;--updated-border:#ffc9474d;--white-full:#fff;--white-95p:#fffffff2;--white-90p:#ffffffe6;--white-85p:#ffffffd9;--white-80p:#ffffffe6;--white-78p:#ffffffc7;--white-75p:#ffffffbf;--white-70p:#ffffffdb;--white-60p:#fff9;--white-50p:#ffffff80;--white-30p:#ffffff4d;--white-20p:#fff3;--white-15p:#ffffff26;--white-10p:#ffffff1a;--white-05p:#ffffff0d;--black-30p:#0000004d;--black-20p:#0003;--black-15p:#00000026;--black-10p:#0000001a;--black-05p:#0000000d;--accent-90p:rgb(var(--stalux-accent-rgb) / .9);--accent-85p:rgb(var(--stalux-accent-rgb) / .85);--accent-80p:rgb(var(--stalux-accent-rgb) / .8);--accent-70p:rgb(var(--stalux-accent-rgb) / .7);--accent-60p:rgb(var(--stalux-accent-rgb) / .6);--accent-50p:rgb(var(--stalux-accent-rgb) / .5);--accent-40p:rgb(var(--stalux-accent-rgb) / .4);--accent-30p:rgb(var(--stalux-accent-rgb) / .3);--accent-20p:rgb(var(--stalux-accent-rgb) / .2);--transition-fast:.18s ease;--transition-medium:.2s ease;--transition-normal:.25s ease;--transition-slow:.3s ease;--transition-slower:.5s ease;--transition-slowest:.6s ease;--easing-bounce:cubic-bezier(.175, .885, .32, 1.275);--space-2xs:.25rem;--space-xs:.75rem;--space-sm:.5rem;--space-md:1rem;--space-lg:1.5rem;--space-xl:2rem;--space-2xl:3rem;--space-3xl:4rem;--radius-sm:4px;--radius-md:6px;--radius-lg:8px;--radius-full:9999px;--shadow-sm:0 2px 5px #0000001a;--shadow-md:0 6px 16px #0003;--shadow-lg:0 8px 20px #0000004d;--shadow-xl:0 12px 26px #00000047;--shadow-sidebar:0 16px 20px -12px #000000d6, 10px 4px 16px -12px #00000094, -10px 4px 16px -12px #00000094;--shadow-raised-hover:0 20px 24px -13px #000000e6, 12px 5px 18px -13px #000000ad, -12px 5px 18px -13px #000000ad;--shadow-dark:#000000c0;--muted-text:#bbb;--font-body:"Noto Sans SC", "Noto Sans CJK SC", system-ui, sans-serif;--font-size-subtitle:1.35rem;--font-size-base:1.1rem;--font-size-small:.95rem;--font-size-smaller:.9rem;--font-size-tiny:.85rem;--border-thin:1px;--border-medium:2px;--border-thick:3px}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:#2a2a2a;border-radius:4px}::-webkit-scrollbar-thumb{background:#4a4a4a;border-radius:4px;transition:background .3s ease-out}@media (hover:hover) and (pointer:fine){::-webkit-scrollbar-thumb:hover{background:#5a5a5a}}::-webkit-scrollbar-corner{background:#2a2a2a}.bg-layer{z-index:-10;background-position:50%;background-repeat:repeat;background-size:auto;background-color:var(--stalux-bg-color,transparent);filter:var(--stalux-bg-filter,none);pointer-events:none;opacity:0;forced-color-adjust:none;color-scheme:light only;transition:opacity .65s ease-in-out;position:fixed;inset:0}@media (prefers-reduced-motion:reduce){.bg-layer{transition-duration:0s}}footer{padding:var(--space-lg) var(--space-md);text-align:center;color:var(--muted-text);border-top:1px solid var(--white-10p);backdrop-filter:blur(16px);background:linear-gradient(#0c0e122e,#0c0e12b8)}}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.stagger-children>:first-child{animation-delay:.1s}.stagger-children>:nth-child(2){animation-delay:.15s}.stagger-children>:nth-child(3){animation-delay:.2s}.stagger-children>:nth-child(4){animation-delay:.25s}.stagger-children>:nth-child(5){animation-delay:.3s}.stagger-children>:nth-child(n+6){animation-delay:.35s}@keyframes stalux-page-exit{to{opacity:0}}@keyframes stalux-page-enter{0%{opacity:0;transform:translateY(9px)}to{opacity:1;transform:translateY(0)}}@keyframes stalux-page-enter-back{0%{opacity:0;transform:translateY(-9px)}to{opacity:1;transform:translateY(0)}}@keyframes stalux-taxonomy-enter{0%{opacity:0;transform:translate(12px)}to{opacity:1;transform:translate(0)}}@keyframes stalux-taxonomy-enter-back{0%{opacity:0;transform:translate(-12px)}to{opacity:1;transform:translate(0)}}@keyframes stalux-article-enter{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}@keyframes stalux-article-enter-back{0%{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:translateY(0)}}@keyframes stalux-sidebar-left-in{0%{opacity:0;transform:translate(-14px)}to{opacity:1;transform:translate(0)}}@keyframes stalux-sidebar-left-out{to{opacity:0;transform:translate(-10px)}}@keyframes stalux-sidebar-left-back-in{0%{opacity:0;transform:translate(14px)}to{opacity:1;transform:translate(0)}}@keyframes stalux-sidebar-left-back-out{to{opacity:0;transform:translate(10px)}}@keyframes stalux-sidebar-right-in{0%{opacity:0;transform:translate(14px)}to{opacity:1;transform:translate(0)}}@keyframes stalux-sidebar-right-out{to{opacity:0;transform:translate(10px)}}@keyframes stalux-sidebar-right-back-in{0%{opacity:0;transform:translate(-14px)}to{opacity:1;transform:translate(0)}}@keyframes stalux-sidebar-right-back-out{to{opacity:0;transform:translate(-10px)}}::view-transition-group(stalux-page){z-index:1}::view-transition-old(stalux-page),::view-transition-new(stalux-page){transform-origin:top}::view-transition-group(*){animation-duration:.24s;animation-timing-function:cubic-bezier(.22,.72,.22,1)}@media (prefers-reduced-motion:reduce){::view-transition-group(*),::view-transition-old(*),::view-transition-new(*){animation-duration:1ms!important;animation-delay:0s!important}.stalux-main,.stalux-main *,.postLayout *{scroll-behavior:auto!important;transition-duration:1ms!important;animation-duration:1ms!important;animation-iteration-count:1!important}}
|
|
3
3
|
/*$vite$:1*/
|
|
@@ -1,26 +1,15 @@
|
|
|
1
1
|
import type { AstroRuntimeLogger } from "astro";
|
|
2
2
|
import { getPostContentIndex, type PostEntry } from "./content-index";
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
* @param keyFn 从单篇文章取出若干分类键(如标签、分类)
|
|
8
|
-
* @param defaultValue 文章没有任何分类键时的兜底分类名(可选)
|
|
9
|
-
*/
|
|
10
|
-
function countBy(
|
|
11
|
-
posts: PostEntry[],
|
|
12
|
-
keyFn: (post: PostEntry) => string[] | undefined,
|
|
13
|
-
defaultValue?: string,
|
|
14
|
-
): Array<{ name: string; count: number }> {
|
|
15
|
-
const counts = new Map<string, number>();
|
|
4
|
+
/** Count the existing uncategorized fallback without building a second taxonomy map. */
|
|
5
|
+
function countUncategorized(posts: PostEntry[]): number {
|
|
6
|
+
let count = 0;
|
|
16
7
|
for (const post of posts) {
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
for (const name of names)
|
|
20
|
-
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
21
|
-
}
|
|
8
|
+
const names = post.data.categories ?? [];
|
|
9
|
+
if (!names.length) count++;
|
|
10
|
+
else for (const name of names) if (name === "uncategorized") count++;
|
|
22
11
|
}
|
|
23
|
-
return
|
|
12
|
+
return count;
|
|
24
13
|
}
|
|
25
14
|
|
|
26
15
|
/**
|
|
@@ -42,9 +31,7 @@ export async function getCategoryCountList(logger?: AstroRuntimeLogger) {
|
|
|
42
31
|
name,
|
|
43
32
|
count: categoryPosts.length,
|
|
44
33
|
}));
|
|
45
|
-
const uncategorized =
|
|
46
|
-
|
|
47
|
-
);
|
|
48
|
-
if (uncategorized) counts.push(uncategorized);
|
|
34
|
+
const uncategorized = countUncategorized(posts);
|
|
35
|
+
if (uncategorized) counts.push({ name: "uncategorized", count: uncategorized });
|
|
49
36
|
return counts;
|
|
50
37
|
}
|
|
@@ -47,8 +47,8 @@ function addToTaxonomy<T extends IndexablePost>(
|
|
|
47
47
|
map: Map<string, MutableTaxonomyBucket<T>>,
|
|
48
48
|
post: T,
|
|
49
49
|
names: string[] | undefined,
|
|
50
|
+
timestamp: number,
|
|
50
51
|
): void {
|
|
51
|
-
const timestamp = toTimestamp(post.data.updated ?? post.data.date);
|
|
52
52
|
for (const name of new Set(names ?? [])) {
|
|
53
53
|
const existing = map.get(name);
|
|
54
54
|
if (existing) {
|
|
@@ -94,8 +94,16 @@ function finalizeTaxonomy<T extends IndexablePost>(
|
|
|
94
94
|
export function buildPostContentIndex<T extends IndexablePost>(
|
|
95
95
|
sourcePosts: readonly T[],
|
|
96
96
|
): PostContentIndex<T> {
|
|
97
|
+
const times = new Map<T, { date: number; updated: number }>();
|
|
98
|
+
for (const post of sourcePosts) {
|
|
99
|
+
const date = toTimestamp(post.data.date);
|
|
100
|
+
times.set(post, {
|
|
101
|
+
date,
|
|
102
|
+
updated: post.data.updated == null ? date : toTimestamp(post.data.updated),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
97
105
|
const posts = sourcePosts.toSorted(
|
|
98
|
-
(left, right) =>
|
|
106
|
+
(left, right) => (times.get(right)?.date ?? 0) - (times.get(left)?.date ?? 0),
|
|
99
107
|
);
|
|
100
108
|
const byAbbrlink = new Map<string, T>();
|
|
101
109
|
const tags = new Map<string, MutableTaxonomyBucket<T>>();
|
|
@@ -108,8 +116,9 @@ export function buildPostContentIndex<T extends IndexablePost>(
|
|
|
108
116
|
throw new Error(`Duplicate post abbrlink: ${abbrlink}`);
|
|
109
117
|
}
|
|
110
118
|
byAbbrlink.set(abbrlink, post);
|
|
111
|
-
|
|
112
|
-
addToTaxonomy(
|
|
119
|
+
const timestamp = times.get(post)?.updated ?? 0;
|
|
120
|
+
addToTaxonomy(tags, post, post.data.tags, timestamp);
|
|
121
|
+
addToTaxonomy(categories, post, post.data.categories, timestamp);
|
|
113
122
|
neighbors.set(abbrlink, {
|
|
114
123
|
previous: posts[index + 1],
|
|
115
124
|
next: index > 0 ? posts[index - 1] : undefined,
|
package/src/utils/dayjs.ts
CHANGED
|
@@ -18,6 +18,31 @@ export const toTimestamp = (v: unknown): number => parseDate(v).getTime();
|
|
|
18
18
|
|
|
19
19
|
const pad2 = (n: number): string => String(n).padStart(2, "0");
|
|
20
20
|
|
|
21
|
+
// Bounded across user-supplied zones; invalid zones still throw before caching.
|
|
22
|
+
const timezoneFormatters = new Map<string, Intl.DateTimeFormat>();
|
|
23
|
+
function getTimezoneFormatter(timeZone: string): Intl.DateTimeFormat {
|
|
24
|
+
let formatter = timezoneFormatters.get(timeZone);
|
|
25
|
+
if (!formatter) {
|
|
26
|
+
formatter = new Intl.DateTimeFormat("en-US", {
|
|
27
|
+
timeZone,
|
|
28
|
+
year: "numeric",
|
|
29
|
+
month: "2-digit",
|
|
30
|
+
day: "2-digit",
|
|
31
|
+
hour: "2-digit",
|
|
32
|
+
minute: "2-digit",
|
|
33
|
+
second: "2-digit",
|
|
34
|
+
hourCycle: "h23",
|
|
35
|
+
timeZoneName: "longOffset",
|
|
36
|
+
});
|
|
37
|
+
if (timezoneFormatters.size >= 16) {
|
|
38
|
+
const oldest = timezoneFormatters.keys().next().value;
|
|
39
|
+
if (oldest !== undefined) timezoneFormatters.delete(oldest);
|
|
40
|
+
}
|
|
41
|
+
timezoneFormatters.set(timeZone, formatter);
|
|
42
|
+
}
|
|
43
|
+
return formatter;
|
|
44
|
+
}
|
|
45
|
+
|
|
21
46
|
/**
|
|
22
47
|
* 轻量 format:仅覆盖本项目实际用到的 token。
|
|
23
48
|
* 支持:yyyy MM dd HH mm ss
|
|
@@ -51,17 +76,7 @@ export function formatInTimeZone(
|
|
|
51
76
|
const d = date instanceof Date ? date : new Date(date);
|
|
52
77
|
if (Number.isNaN(d.getTime())) return "";
|
|
53
78
|
|
|
54
|
-
const parts =
|
|
55
|
-
timeZone,
|
|
56
|
-
year: "numeric",
|
|
57
|
-
month: "2-digit",
|
|
58
|
-
day: "2-digit",
|
|
59
|
-
hour: "2-digit",
|
|
60
|
-
minute: "2-digit",
|
|
61
|
-
second: "2-digit",
|
|
62
|
-
hourCycle: "h23",
|
|
63
|
-
timeZoneName: "longOffset",
|
|
64
|
-
}).formatToParts(d);
|
|
79
|
+
const parts = getTimezoneFormatter(timeZone).formatToParts(d);
|
|
65
80
|
|
|
66
81
|
const get = (type: Intl.DateTimeFormatPartTypes): string =>
|
|
67
82
|
parts.find((p) => p.type === type)?.value ?? "";
|
package/src/utils/feed.ts
CHANGED
|
@@ -32,43 +32,44 @@ export async function buildFeedItems(
|
|
|
32
32
|
updatedTag: string,
|
|
33
33
|
logger?: AstroRuntimeLogger,
|
|
34
34
|
): Promise<FeedItem[]> {
|
|
35
|
+
const timestamps = new Map(
|
|
36
|
+
posts.map((post) => [post, toTimestamp(post.data.updated || post.data.date)]),
|
|
37
|
+
);
|
|
35
38
|
const sortedPosts = posts.toSorted((a, b) => {
|
|
36
39
|
// Newest first: compare post dates, use the greater of updated/date
|
|
37
|
-
const aTime =
|
|
38
|
-
const bTime =
|
|
40
|
+
const aTime = timestamps.get(a) ?? 0;
|
|
41
|
+
const bTime = timestamps.get(b) ?? 0;
|
|
39
42
|
return bTime - aTime;
|
|
40
43
|
});
|
|
41
44
|
|
|
42
45
|
const descriptions = await getPostDescriptions(logger);
|
|
43
46
|
logDetail(logger, "feed", `rendering ${sortedPosts.length} entries; format=${updatedTag}`);
|
|
44
|
-
return
|
|
45
|
-
|
|
46
|
-
const cached = descriptions.get(String(post.data.abbrlink));
|
|
47
|
+
return sortedPosts.map((post) => {
|
|
48
|
+
const cached = descriptions.get(String(post.data.abbrlink));
|
|
47
49
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
if (post.data.cc) {
|
|
61
|
-
customData += `<rights>${post.data.cc}</rights>`;
|
|
50
|
+
let customData = "";
|
|
51
|
+
if (post.data.updated) {
|
|
52
|
+
const parsed = parseDate(post.data.updated);
|
|
53
|
+
if (isValid(parsed)) {
|
|
54
|
+
const updatedIso = formatInTimeZone(
|
|
55
|
+
parsed,
|
|
56
|
+
stalux.timezone,
|
|
57
|
+
"yyyy-MM-dd'T'HH:mm:ssXXX",
|
|
58
|
+
);
|
|
59
|
+
customData += `<${updatedTag}>${updatedIso}</${updatedTag}>`;
|
|
62
60
|
}
|
|
61
|
+
}
|
|
62
|
+
if (post.data.cc) {
|
|
63
|
+
customData += `<rights>${post.data.cc}</rights>`;
|
|
64
|
+
}
|
|
63
65
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
);
|
|
66
|
+
return {
|
|
67
|
+
title: post.data.title,
|
|
68
|
+
pubDate: new Date(toMachineDateTime(post.data.date, stalux.timezone)),
|
|
69
|
+
description: cached?.desc || post.data.desc || "",
|
|
70
|
+
link: `/posts/${post.data.abbrlink}/`,
|
|
71
|
+
categories: post.data.categories || [],
|
|
72
|
+
customData,
|
|
73
|
+
};
|
|
74
|
+
});
|
|
74
75
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
interface SearchablePost {
|
|
2
|
+
abbrlink: string;
|
|
3
|
+
title: string;
|
|
4
|
+
tags?: string[];
|
|
5
|
+
categories?: string[];
|
|
6
|
+
desc?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** One immutable snapshot; duplicate IDs and fuzzy matches keep first-match order. */
|
|
10
|
+
export function createPostLookup<T extends SearchablePost>(posts: readonly T[]) {
|
|
11
|
+
const byId = new Map<string, T>();
|
|
12
|
+
const searchable = posts.map((post) => {
|
|
13
|
+
if (!byId.has(post.abbrlink)) byId.set(post.abbrlink, post);
|
|
14
|
+
return {
|
|
15
|
+
post,
|
|
16
|
+
title: post.title.toLowerCase(),
|
|
17
|
+
tags: post.tags?.map((tag) => tag.toLowerCase()) ?? [],
|
|
18
|
+
categories: post.categories?.map((category) => category.toLowerCase()) ?? [],
|
|
19
|
+
description: post.desc?.toLowerCase() ?? "",
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
return {
|
|
23
|
+
findById: (id: string): T | undefined => byId.get(id),
|
|
24
|
+
findMatches(keyword: string, limit: number): T[] {
|
|
25
|
+
const query = keyword.toLowerCase();
|
|
26
|
+
const titles: T[] = [];
|
|
27
|
+
const other: T[] = [];
|
|
28
|
+
for (const entry of searchable) {
|
|
29
|
+
if (entry.title.includes(query)) {
|
|
30
|
+
titles.push(entry.post);
|
|
31
|
+
if (titles.length >= limit) break;
|
|
32
|
+
} else if (
|
|
33
|
+
other.length < limit &&
|
|
34
|
+
(entry.tags.some((tag) => tag.includes(query)) ||
|
|
35
|
+
entry.categories.some((category) => category.includes(query)) ||
|
|
36
|
+
entry.description.includes(query))
|
|
37
|
+
) {
|
|
38
|
+
other.push(entry.post);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return [...titles, ...other].slice(0, limit);
|
|
42
|
+
},
|
|
43
|
+
findByKeyword(keyword: string): T | undefined {
|
|
44
|
+
const query = keyword.toLowerCase();
|
|
45
|
+
return (
|
|
46
|
+
searchable.find(({ title }) => title.includes(query)) ??
|
|
47
|
+
searchable.find(({ tags }) => tags.some((tag) => tag.includes(query)))
|
|
48
|
+
)?.post;
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Document } from "yaml";
|
|
2
|
+
|
|
3
|
+
interface MarkdownPostData {
|
|
4
|
+
title: string;
|
|
5
|
+
tags?: string[];
|
|
6
|
+
categories?: string[];
|
|
7
|
+
date: string;
|
|
8
|
+
updated?: string;
|
|
9
|
+
desc: string;
|
|
10
|
+
abbrlink: string | number;
|
|
11
|
+
draft?: boolean;
|
|
12
|
+
cc?: string;
|
|
13
|
+
cover?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Serialize the collection's parsed metadata as YAML AST; preserve the original Markdown body. */
|
|
17
|
+
export function reconstructMarkdown(data: MarkdownPostData, body = ""): string {
|
|
18
|
+
const frontmatter = new Document({
|
|
19
|
+
title: data.title,
|
|
20
|
+
...(data.tags?.length ? { tags: data.tags } : {}),
|
|
21
|
+
...(data.categories?.length ? { categories: data.categories } : {}),
|
|
22
|
+
date: data.date,
|
|
23
|
+
...(data.updated ? { updated: data.updated } : {}),
|
|
24
|
+
desc: data.desc,
|
|
25
|
+
abbrlink: String(data.abbrlink),
|
|
26
|
+
...(data.draft ? { draft: true } : {}),
|
|
27
|
+
...(data.cc && data.cc !== "CC-BY-NC-SA-4.0" ? { cc: data.cc } : {}),
|
|
28
|
+
...(data.cover ? { cover: data.cover } : {}),
|
|
29
|
+
});
|
|
30
|
+
return `---\n${frontmatter.toString({ lineWidth: 0 })}---\n${body}${body.endsWith("\n") ? "" : "\n"}`;
|
|
31
|
+
}
|
|
@@ -25,6 +25,8 @@ export function initTocScrollSpy(activeClass: string): (() => void) | undefined
|
|
|
25
25
|
let pending = false;
|
|
26
26
|
let frameId = 0;
|
|
27
27
|
let sidebarHovered = false;
|
|
28
|
+
const headingOrder = new Map(headings.map((heading, index) => [heading, index]));
|
|
29
|
+
const visibleHeadings = new Set<HTMLElement>();
|
|
28
30
|
const controller = new AbortController();
|
|
29
31
|
|
|
30
32
|
// 鼠标悬停在侧边栏上时跳过自动滚动,避免跟用户手动操作冲突
|
|
@@ -61,7 +63,12 @@ export function initTocScrollSpy(activeClass: string): (() => void) | undefined
|
|
|
61
63
|
};
|
|
62
64
|
|
|
63
65
|
const observer = new IntersectionObserver(
|
|
64
|
-
() => {
|
|
66
|
+
(entries) => {
|
|
67
|
+
for (const entry of entries) {
|
|
68
|
+
const heading = entry.target as HTMLElement;
|
|
69
|
+
if (entry.isIntersecting) visibleHeadings.add(heading);
|
|
70
|
+
else visibleHeadings.delete(heading);
|
|
71
|
+
}
|
|
65
72
|
// 用 rAF 合并同一帧内的多次回调,避免频繁触发布局计算
|
|
66
73
|
if (pending) return;
|
|
67
74
|
pending = true;
|
|
@@ -70,11 +77,17 @@ export function initTocScrollSpy(activeClass: string): (() => void) | undefined
|
|
|
70
77
|
pending = false;
|
|
71
78
|
|
|
72
79
|
// 找到视口上方 35% 区域内最靠上的标题
|
|
73
|
-
let current =
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
80
|
+
let current: HTMLElement | null = null;
|
|
81
|
+
let currentOrder = headings.length;
|
|
82
|
+
for (const heading of visibleHeadings) {
|
|
83
|
+
const order = headingOrder.get(heading) ?? headings.length;
|
|
84
|
+
if (order >= currentOrder) continue;
|
|
85
|
+
const rect = heading.getBoundingClientRect();
|
|
86
|
+
if (rect.top < window.innerHeight * 0.35 && rect.bottom > 0) {
|
|
87
|
+
current = heading;
|
|
88
|
+
currentOrder = order;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
78
91
|
|
|
79
92
|
// 兜底:滚动到文章底部时,取最后一个仍在视口上方或已经越过顶部的标题
|
|
80
93
|
current ??=
|
|
@@ -111,6 +124,7 @@ export function initTocScrollSpy(activeClass: string): (() => void) | undefined
|
|
|
111
124
|
return () => {
|
|
112
125
|
controller.abort();
|
|
113
126
|
observer.disconnect();
|
|
127
|
+
visibleHeadings.clear();
|
|
114
128
|
if (frameId) cancelAnimationFrame(frameId);
|
|
115
129
|
activeLink?.classList.remove(activeClass);
|
|
116
130
|
};
|
|
@@ -7,65 +7,69 @@
|
|
|
7
7
|
import { getCollection } from "astro:content";
|
|
8
8
|
import { analyzeFeatureFlags } from "@plugins/feature-flags";
|
|
9
9
|
import type { AstroRuntimeLogger } from "astro";
|
|
10
|
+
import { forEachBounded } from "../internal/bounded-tasks";
|
|
10
11
|
import { logDetail, logFailure } from "./diagnostics";
|
|
11
12
|
|
|
12
13
|
// ---------------------------------------------------------------------------
|
|
13
14
|
// 模块级缓存(整个构建周期只计算一次)
|
|
14
15
|
// ---------------------------------------------------------------------------
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
interface PostStatistics {
|
|
18
|
+
total: number;
|
|
19
|
+
descriptions: Map<string, { desc: string; wordCount: number }>;
|
|
20
|
+
}
|
|
17
21
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
let statisticsPromise: Promise<PostStatistics> | undefined;
|
|
23
|
+
|
|
24
|
+
function loadStatistics(logger?: AstroRuntimeLogger): Promise<PostStatistics> {
|
|
25
|
+
if (!statisticsPromise) {
|
|
26
|
+
statisticsPromise = (async () => {
|
|
27
|
+
const posts = await getCollection("posts", ({ data }) => !data.draft);
|
|
28
|
+
const results = new Array<{ id: string; desc: string; wordCount: number }>(
|
|
29
|
+
posts.length,
|
|
30
|
+
);
|
|
31
|
+
await forEachBounded(
|
|
32
|
+
posts.map((post, index) => ({ post, index })),
|
|
33
|
+
4,
|
|
34
|
+
async ({ post, index }) => {
|
|
35
|
+
results[index] = {
|
|
36
|
+
id: String(post.data.abbrlink ?? post.id),
|
|
37
|
+
desc: post.data.desc || "",
|
|
38
|
+
wordCount: (await analyzeFeatureFlags(post.body, logger)).wordCount,
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
);
|
|
42
|
+
const descriptions = new Map<string, { desc: string; wordCount: number }>();
|
|
43
|
+
let total = 0;
|
|
44
|
+
for (const result of results) {
|
|
45
|
+
total += result.wordCount;
|
|
46
|
+
descriptions.set(result.id, { desc: result.desc, wordCount: result.wordCount });
|
|
47
|
+
}
|
|
48
|
+
logDetail(logger, "word-count", `analyzed ${posts.length} posts`);
|
|
49
|
+
return { total, descriptions };
|
|
50
|
+
})().catch((error: unknown) => {
|
|
51
|
+
statisticsPromise = undefined;
|
|
52
|
+
throw error;
|
|
53
|
+
});
|
|
26
54
|
}
|
|
27
|
-
|
|
55
|
+
return statisticsPromise;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Shared in-flight statistics prevent concurrent pages from repeating collection work. */
|
|
59
|
+
export async function getTotalWordCount(logger?: AstroRuntimeLogger): Promise<number> {
|
|
28
60
|
try {
|
|
29
|
-
|
|
30
|
-
const results = await Promise.all(
|
|
31
|
-
posts.map((post) => analyzeFeatureFlags(post.body, logger)),
|
|
32
|
-
);
|
|
33
|
-
_cachedTotal = results.reduce((sum, result) => sum + result.wordCount, 0);
|
|
34
|
-
logDetail(logger, "word-count", `analyzed ${posts.length} posts`);
|
|
35
|
-
return _cachedTotal;
|
|
61
|
+
return (await loadStatistics(logger)).total;
|
|
36
62
|
} catch (error) {
|
|
37
63
|
logFailure(logger, "word-count", "total calculation failed; returning zero", error);
|
|
38
64
|
return 0;
|
|
39
65
|
}
|
|
40
66
|
}
|
|
41
67
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* 获取所有已发布文章的描述和字数缓存,供 RSS/Atom/LLMs feed 共享。
|
|
46
|
-
* - desc :直接从 frontmatter 读取(post.data.desc)
|
|
47
|
-
* - wordCount:从 Sätteri AST 分析 post.body
|
|
48
|
-
*/
|
|
68
|
+
/** Keep collection insertion order and frontmatter descriptions unchanged. */
|
|
49
69
|
export async function getPostDescriptions(
|
|
50
70
|
logger?: AstroRuntimeLogger,
|
|
51
71
|
): Promise<Map<string, { desc: string; wordCount: number }>> {
|
|
52
|
-
|
|
53
|
-
logDetail(logger, "word-count", "description cache hit");
|
|
54
|
-
return _cachedDescriptions;
|
|
55
|
-
}
|
|
56
|
-
logDetail(logger, "word-count", "description cache miss");
|
|
57
|
-
const posts = await getCollection("posts", ({ data }) => !data.draft);
|
|
58
|
-
const results = await Promise.all(
|
|
59
|
-
posts.map(async (post) => ({
|
|
60
|
-
id: String(post.data.abbrlink ?? post.id),
|
|
61
|
-
desc: post.data.desc || "",
|
|
62
|
-
wordCount: (await analyzeFeatureFlags(post.body, logger)).wordCount,
|
|
63
|
-
})),
|
|
64
|
-
);
|
|
65
|
-
_cachedDescriptions = new Map(
|
|
66
|
-
results.map((r) => [r.id, { desc: r.desc, wordCount: r.wordCount }]),
|
|
67
|
-
);
|
|
68
|
-
return _cachedDescriptions;
|
|
72
|
+
return (await loadStatistics(logger)).descriptions;
|
|
69
73
|
}
|
|
70
74
|
|
|
71
75
|
export function formatWordCount(count: number, lang?: string): string {
|