@xingwangzhe/stalux 1.2.0

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.
Files changed (214) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +192 -0
  3. package/package.json +108 -0
  4. package/scripts/cli.mjs +103 -0
  5. package/src/assets/BiCcCircle.svg +1 -0
  6. package/src/assets/astro.svg +1 -0
  7. package/src/assets/background/README.md +3 -0
  8. package/src/assets/background/pattern-1.min.svg +1 -0
  9. package/src/assets/background/pattern-10.min.svg +1 -0
  10. package/src/assets/background/pattern-11.min.svg +1 -0
  11. package/src/assets/background/pattern-12.min.svg +1 -0
  12. package/src/assets/background/pattern-13.min.svg +1 -0
  13. package/src/assets/background/pattern-14.min.svg +1 -0
  14. package/src/assets/background/pattern-15.min.svg +1 -0
  15. package/src/assets/background/pattern-16.min.svg +1 -0
  16. package/src/assets/background/pattern-17.min.svg +1 -0
  17. package/src/assets/background/pattern-18.min.svg +1 -0
  18. package/src/assets/background/pattern-19.min.svg +1 -0
  19. package/src/assets/background/pattern-2.min.svg +1 -0
  20. package/src/assets/background/pattern-20.min.svg +1 -0
  21. package/src/assets/background/pattern-21.min.svg +1 -0
  22. package/src/assets/background/pattern-22.min.svg +1 -0
  23. package/src/assets/background/pattern-23.min.svg +1 -0
  24. package/src/assets/background/pattern-24.min.svg +1 -0
  25. package/src/assets/background/pattern-25.min.svg +1 -0
  26. package/src/assets/background/pattern-26.min.svg +1 -0
  27. package/src/assets/background/pattern-27.min.svg +1 -0
  28. package/src/assets/background/pattern-28.min.svg +1 -0
  29. package/src/assets/background/pattern-29.min.svg +1 -0
  30. package/src/assets/background/pattern-3.min.svg +1 -0
  31. package/src/assets/background/pattern-30.min.svg +1 -0
  32. package/src/assets/background/pattern-31.min.svg +1 -0
  33. package/src/assets/background/pattern-32.min.svg +1 -0
  34. package/src/assets/background/pattern-33.min.svg +1 -0
  35. package/src/assets/background/pattern-34.min.svg +1 -0
  36. package/src/assets/background/pattern-35.min.svg +1 -0
  37. package/src/assets/background/pattern-36.min.svg +1 -0
  38. package/src/assets/background/pattern-37.min.svg +1 -0
  39. package/src/assets/background/pattern-38.min.svg +1 -0
  40. package/src/assets/background/pattern-39.min.svg +1 -0
  41. package/src/assets/background/pattern-4.min.svg +1 -0
  42. package/src/assets/background/pattern-40.min.svg +1 -0
  43. package/src/assets/background/pattern-41.min.svg +1 -0
  44. package/src/assets/background/pattern-42.min.svg +1 -0
  45. package/src/assets/background/pattern-5.min.svg +1 -0
  46. package/src/assets/background/pattern-6.min.svg +1 -0
  47. package/src/assets/background/pattern-7.min.svg +1 -0
  48. package/src/assets/background/pattern-8.min.svg +1 -0
  49. package/src/assets/background/pattern-9.min.svg +1 -0
  50. package/src/assets/background.svg +1 -0
  51. package/src/assets/cc-icon.svg +4 -0
  52. package/src/assets/fonts/GoogleSansCode-Italic.woff2 +0 -0
  53. package/src/assets/fonts/GoogleSansCode.woff2 +0 -0
  54. package/src/assets/fonts/LXGWWenKai-Regular.ttf +0 -0
  55. package/src/assets/fonts/LXGWWenKai-Regular.woff2 +0 -0
  56. package/src/components/stalux/analytics/clarity.astro +25 -0
  57. package/src/components/stalux/analytics/google.astro +31 -0
  58. package/src/components/stalux/analytics/umami.astro +16 -0
  59. package/src/components/stalux/archives/archivesList.astro +116 -0
  60. package/src/components/stalux/archives/toArchive.astro +14 -0
  61. package/src/components/stalux/categories/categoriesCard.astro +49 -0
  62. package/src/components/stalux/categories/categoriesList.astro +17 -0
  63. package/src/components/stalux/common/author.astro +35 -0
  64. package/src/components/stalux/common/date.astro +59 -0
  65. package/src/components/stalux/common/search.astro +14 -0
  66. package/src/components/stalux/common/textType.astro +16 -0
  67. package/src/components/stalux/common/verCount.astro +10 -0
  68. package/src/components/stalux/footer/FooterBadges.astro +150 -0
  69. package/src/components/stalux/footer/FooterBeian.astro +91 -0
  70. package/src/components/stalux/footer/FooterCopyright.astro +40 -0
  71. package/src/components/stalux/footer/FooterStats.astro +108 -0
  72. package/src/components/stalux/footer/FooterThemeInfo.astro +67 -0
  73. package/src/components/stalux/layout/footer.astro +87 -0
  74. package/src/components/stalux/layout/head.astro +194 -0
  75. package/src/components/stalux/layout/listPage.astro +26 -0
  76. package/src/components/stalux/layout/navs.astro +174 -0
  77. package/src/components/stalux/links/linkCard.astro +50 -0
  78. package/src/components/stalux/links/linkList.astro +20 -0
  79. package/src/components/stalux/links/socialMedia.astro +102 -0
  80. package/src/components/stalux/posts/PhotoSwipeStyle.astro +3 -0
  81. package/src/components/stalux/posts/PostTaxonomy.astro +65 -0
  82. package/src/components/stalux/posts/WalineComment.astro +609 -0
  83. package/src/components/stalux/posts/postAuthor.astro +23 -0
  84. package/src/components/stalux/posts/postCC.astro +52 -0
  85. package/src/components/stalux/posts/postCard.astro +83 -0
  86. package/src/components/stalux/posts/postContent.astro +174 -0
  87. package/src/components/stalux/posts/postLeft.astro +41 -0
  88. package/src/components/stalux/posts/postList.astro +25 -0
  89. package/src/components/stalux/posts/postNavigation.astro +137 -0
  90. package/src/components/stalux/posts/postRight.astro +33 -0
  91. package/src/components/stalux/posts/postToc.astro +88 -0
  92. package/src/components/stalux/posts/randomPost.astro +134 -0
  93. package/src/components/stalux/tags/tagHeader.astro +50 -0
  94. package/src/components/stalux/tags/tagsCloud.astro +100 -0
  95. package/src/components/stalux/words/wordCard.astro +62 -0
  96. package/src/components/stalux/words/wordList.astro +70 -0
  97. package/src/config.ts +58 -0
  98. package/src/i18n/en.json +193 -0
  99. package/src/i18n/zh-CN.json +193 -0
  100. package/src/index.ts +215 -0
  101. package/src/internal/components-plugin.ts +54 -0
  102. package/src/internal/dev-toolbar.ts +33 -0
  103. package/src/internal/override.ts +99 -0
  104. package/src/internal/pagefind.ts +40 -0
  105. package/src/layouts/PostLayout.astro +90 -0
  106. package/src/layouts/Stalux.astro +91 -0
  107. package/src/pages/404.astro +103 -0
  108. package/src/pages/about.astro +59 -0
  109. package/src/pages/about.md.ts +15 -0
  110. package/src/pages/api/post.abbrlink.json.ts +20 -0
  111. package/src/pages/archives.astro +23 -0
  112. package/src/pages/archives.md.ts +24 -0
  113. package/src/pages/atom.xml.ts +29 -0
  114. package/src/pages/categories/[category].astro +35 -0
  115. package/src/pages/categories/[category].md.ts +34 -0
  116. package/src/pages/categories/index.astro +23 -0
  117. package/src/pages/categories/index.md.ts +27 -0
  118. package/src/pages/index.astro +30 -0
  119. package/src/pages/index.md.ts +18 -0
  120. package/src/pages/links.astro +23 -0
  121. package/src/pages/links.md.ts +18 -0
  122. package/src/pages/llms-full.txt.ts +25 -0
  123. package/src/pages/llms.txt.ts +26 -0
  124. package/src/pages/posts/[post].astro +69 -0
  125. package/src/pages/posts/[post].md.ts +109 -0
  126. package/src/pages/rss.xml.ts +29 -0
  127. package/src/pages/tags/[tag].astro +37 -0
  128. package/src/pages/tags/[tag].md.ts +34 -0
  129. package/src/pages/tags/index.astro +23 -0
  130. package/src/pages/tags/index.md.ts +27 -0
  131. package/src/pages/words.astro +24 -0
  132. package/src/pages/words.md.ts +14 -0
  133. package/src/plugins/feature-flags.ts +68 -0
  134. package/src/plugins/satteri-temml.ts +81 -0
  135. package/src/schemas/collections.ts +140 -0
  136. package/src/schemas/config.ts +226 -0
  137. package/src/scripts/background.ts +59 -0
  138. package/src/styles/base/init.css +286 -0
  139. package/src/styles/base/math.css +411 -0
  140. package/src/styles/components/archives/archivesList.module.css +378 -0
  141. package/src/styles/components/archives/toArchive.module.css +3 -0
  142. package/src/styles/components/categories/categoriesCard.module.css +87 -0
  143. package/src/styles/components/categories/categoriesList.module.css +27 -0
  144. package/src/styles/components/common/author.module.css +73 -0
  145. package/src/styles/components/common/date.module.css +15 -0
  146. package/src/styles/components/common/textType.module.css +10 -0
  147. package/src/styles/components/footer/footer.module.css +44 -0
  148. package/src/styles/components/layout/navs.module.css +250 -0
  149. package/src/styles/components/links/linkCard.module.css +99 -0
  150. package/src/styles/components/links/linkList.module.css +67 -0
  151. package/src/styles/components/links/socialLinks.module.css +22 -0
  152. package/src/styles/components/markdown.css +528 -0
  153. package/src/styles/components/posts/postAuthor.module.css +32 -0
  154. package/src/styles/components/posts/postCC.module.css +138 -0
  155. package/src/styles/components/posts/postCard.module.css +156 -0
  156. package/src/styles/components/posts/postCategories.module.css +50 -0
  157. package/src/styles/components/posts/postContent.module.css +230 -0
  158. package/src/styles/components/posts/postLayout.module.css +142 -0
  159. package/src/styles/components/posts/postLeft.module.css +38 -0
  160. package/src/styles/components/posts/postList.module.css +24 -0
  161. package/src/styles/components/posts/postNavigation.module.css +187 -0
  162. package/src/styles/components/posts/postRight.module.css +60 -0
  163. package/src/styles/components/posts/postSidebar.module.css +57 -0
  164. package/src/styles/components/posts/postTags.module.css +51 -0
  165. package/src/styles/components/posts/postTaxonomy.module.css +87 -0
  166. package/src/styles/components/posts/postToc.module.css +74 -0
  167. package/src/styles/components/posts/randomPost.module.css +131 -0
  168. package/src/styles/components/tags/tagHeader.module.css +44 -0
  169. package/src/styles/components/tags/tagsCloud.module.css +124 -0
  170. package/src/styles/components/words/wordCard.module.css +130 -0
  171. package/src/styles/components/words/wordList.module.css +39 -0
  172. package/src/styles/pages/archives.module.css +18 -0
  173. package/src/styles/pages/categories.module.css +13 -0
  174. package/src/styles/pages/home.module.css +51 -0
  175. package/src/styles/pages/listPage.module.css +13 -0
  176. package/src/styles/pages/tags.module.css +12 -0
  177. package/src/styles/pages/words.module.css +13 -0
  178. package/src/styles/shared/animations.css +87 -0
  179. package/src/styles/shared/stagger.css +20 -0
  180. package/src/utils/ai-discovery.ts +547 -0
  181. package/src/utils/badge-generator.ts +68 -0
  182. package/src/utils/cc.ts +46 -0
  183. package/src/utils/collections-stats.ts +48 -0
  184. package/src/utils/config-utils.ts +107 -0
  185. package/src/utils/count-words.ts +20 -0
  186. package/src/utils/dayjs.ts +106 -0
  187. package/src/utils/feed.ts +70 -0
  188. package/src/utils/i18n.ts +64 -0
  189. package/src/utils/lucide-icons.ts +52 -0
  190. package/src/utils/semantic-time.ts +11 -0
  191. package/src/utils/taxonomy.ts +44 -0
  192. package/src/utils/toc-scrollspy.ts +95 -0
  193. package/src/utils/waline.ts +27 -0
  194. package/src/utils/word-count-utils.ts +83 -0
  195. package/template/astro.config.mjs +51 -0
  196. package/template/package.json +27 -0
  197. package/template/public/robots.txt +3 -0
  198. package/template/src/content.config.ts +11 -0
  199. package/template/stalux/about/index.md +14 -0
  200. package/template/stalux/config/ai-discovery.yml +2 -0
  201. package/template/stalux/config/author.yml +4 -0
  202. package/template/stalux/config/comment.yml +5 -0
  203. package/template/stalux/config/footer.yml +13 -0
  204. package/template/stalux/config/head.yml +7 -0
  205. package/template/stalux/config/links.yml +4 -0
  206. package/template/stalux/config/media-links.yml +8 -0
  207. package/template/stalux/config/navs.yml +23 -0
  208. package/template/stalux/config/promote.yml +2 -0
  209. package/template/stalux/config/site.yml +7 -0
  210. package/template/stalux/config/typetexts.yml +5 -0
  211. package/template/stalux/posts/hello-stalux.md +47 -0
  212. package/template/stalux/words/_template.md +12 -0
  213. package/template/stalux/words/einstein-imagination.md +8 -0
  214. package/template/tsconfig.json +9 -0
@@ -0,0 +1,134 @@
1
+ ---
2
+ import style from "@styles/components/posts/randomPost.module.css";
3
+ import { getCollection } from "astro:content";
4
+ import { createTranslator } from "@utils/i18n";
5
+
6
+ interface Props {
7
+ count?: number;
8
+ title?: string;
9
+ }
10
+
11
+ const configEntries = await getCollection("config");
12
+ const siteConfig = (configEntries.find(e => e.id === "site")?.data ?? {}) as Record<string, unknown>;
13
+ const { t } = createTranslator((siteConfig.lang as string) || "zh-CN");
14
+ const { count = 5, title = t("randomPost.title") } = Astro.props;
15
+ ---
16
+
17
+ <aside class={style.container} transition:persist-props aria-label={t("randomPost.title")}>
18
+ <div class={style.header}>
19
+ <h3 class={style.title}>{title}</h3>
20
+ <button
21
+ id="refresh-random-posts"
22
+ class={style.refreshButton}
23
+ title={t("randomPost.refresh")}
24
+ aria-label={t("randomPost.refresh")}
25
+ >
26
+ <svg
27
+ xmlns="http://www.w3.org/2000/svg"
28
+ width="16"
29
+ height="16"
30
+ viewBox="0 0 24 24"
31
+ fill="none"
32
+ stroke="currentColor"
33
+ stroke-width="2"
34
+ stroke-linecap="round"
35
+ stroke-linejoin="round"
36
+ >
37
+ <path
38
+ d="M21.5 2v6h-6M2.5 22v-6h6M2 11.5a10 10 0 0 1 18.8-4.3M22 12.5a10 10 0 0 1-18.8 4.2"
39
+ ></path>
40
+ </svg>
41
+ </button>
42
+ </div>
43
+ <ul class={style.list} id="random-posts-list">
44
+ <li class={style.loading}>{t("randomPost.loading")}</li>
45
+ </ul>
46
+ </aside>
47
+
48
+ <script define:vars={{ count, labels: { noPosts: t("randomPost.noPosts"), loadFailed: t("randomPost.loadFailed"), loadError: t("randomPost.loadError") } }}>
49
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
50
+
51
+ // 费雪-耶茨洗牌(crypto 优先,回退 Math.random)
52
+ const shuffleArray = (array) => {
53
+ const shuffled = [...array];
54
+ const randomIndex = (max) => {
55
+ if (globalThis.crypto?.getRandomValues) {
56
+ const buf = new Uint32Array(1);
57
+ crypto.getRandomValues(buf);
58
+ return buf[0] % max;
59
+ }
60
+ return Math.floor(Math.random() * max);
61
+ };
62
+ for (let i = shuffled.length - 1; i > 0; i--) {
63
+ const j = randomIndex(i + 1);
64
+ [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
65
+ }
66
+ return shuffled;
67
+ };
68
+
69
+ const fetchRandomPosts = async () => {
70
+ try {
71
+ const response = await fetch("/api/post.abbrlink.json");
72
+ if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
73
+ return (await response.json()) ?? [];
74
+ } catch (error) {
75
+ console.error("Failed to fetch posts:", error);
76
+ return [];
77
+ }
78
+ };
79
+
80
+ const generatePostsHTML = (posts) => {
81
+ if (posts.length === 0) {
82
+ return `<li class="no-posts">${labels.noPosts}</li>`;
83
+ }
84
+ return posts
85
+ .map(
86
+ (post) => `
87
+ <li class="random-post-item">
88
+ <a href="/posts/${post.abbrlink}/" class="random-post-link a-none">
89
+ ${post.title}
90
+ </a>
91
+ </li>
92
+ `,
93
+ )
94
+ .join("");
95
+ };
96
+
97
+ const renderRandomPosts = async (listEl, emptyMsg, errorMsg) => {
98
+ try {
99
+ const allPosts = await fetchRandomPosts();
100
+ if (allPosts.length === 0) {
101
+ listEl.innerHTML = `<li class="no-posts">${emptyMsg}</li>`;
102
+ return;
103
+ }
104
+ listEl.innerHTML = generatePostsHTML(shuffleArray(allPosts).slice(0, count));
105
+ } catch (error) {
106
+ console.error("Error rendering random posts:", error);
107
+ listEl.innerHTML = `<li class="error">${errorMsg}</li>`;
108
+ }
109
+ };
110
+
111
+ const refreshRandomPosts = async () => {
112
+ const randomPostsList = document.getElementById("random-posts-list");
113
+ if (!randomPostsList) return;
114
+
115
+ randomPostsList.style.opacity = "0.5";
116
+ await sleep(300);
117
+ await renderRandomPosts(randomPostsList, labels.noPosts, labels.loadFailed);
118
+ randomPostsList.style.opacity = "1";
119
+ };
120
+
121
+ document.addEventListener("astro:page-load", () => {
122
+ const refreshButton = document.getElementById("refresh-random-posts");
123
+ refreshButton?.addEventListener("click", async (e) => {
124
+ e.preventDefault();
125
+ refreshButton.classList.add("rotating");
126
+ await refreshRandomPosts();
127
+ await sleep(600);
128
+ refreshButton.classList.remove("rotating");
129
+ });
130
+
131
+ const list = document.getElementById("random-posts-list");
132
+ if (list) void renderRandomPosts(list, labels.noPosts, labels.loadError);
133
+ });
134
+ </script>
@@ -0,0 +1,50 @@
1
+ ---
2
+ interface Props {
3
+ type: string;
4
+ typeKey?: string;
5
+ name: string;
6
+ count: number;
7
+ }
8
+ const props = Astro.props as Props;
9
+ import { getCollection } from "astro:content";
10
+ import { getSiteData } from "@utils/config-utils";
11
+ import { createTranslator } from "@utils/i18n";
12
+ import styles from "@styles/components/tags/tagHeader.module.css";
13
+
14
+ const configEntries = await getCollection("config");
15
+ const siteConfig = getSiteData(configEntries);
16
+ const { t } = createTranslator(siteConfig.lang || "zh-CN");
17
+
18
+ const typeKey = props.typeKey || props.type;
19
+ let typeUrl = "";
20
+ if (typeKey === "category" || typeKey === "categories" || typeKey === "分类") {
21
+ typeUrl = "/categories/";
22
+ } else if (typeKey === "tag" || typeKey === "tags" || typeKey === "标签") {
23
+ typeUrl = "/tags/";
24
+ }
25
+ ---
26
+
27
+ <header class={styles.pageHeader}>
28
+ <h1 class={styles.pageTitle}>
29
+ <span class={styles.categoryIcon}>
30
+ <svg
31
+ xmlns="http://www.w3.org/2000/svg"
32
+ viewBox="0 0 24 24"
33
+ width="28"
34
+ height="28"
35
+ fill="none"
36
+ stroke="currentColor"
37
+ stroke-width="2"
38
+ stroke-linecap="round"
39
+ stroke-linejoin="round"
40
+ >
41
+ <path
42
+ d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"
43
+ ></path>
44
+ </svg>
45
+ </span>
46
+ {props.type}: {props.name}
47
+ </h1>
48
+ <p class={styles.pageDescription}>{t("taxonomy.foundPosts", { count: props.count })}</p>
49
+ <a href={typeUrl} class={styles.backLink} data-astro-prefetch>{t("taxonomy.backToList", { type: props.type })}</a>
50
+ </header>
@@ -0,0 +1,100 @@
1
+ ---
2
+ import { getTagCountList } from "@utils/collections-stats";
3
+ import { getCollection } from "astro:content";
4
+ import { getSiteData } from "@utils/config-utils";
5
+ import { createTranslator } from "@utils/i18n";
6
+ import styles from "@styles/components/tags/tagsCloud.module.css";
7
+
8
+ const configEntries = await getCollection("config");
9
+ const siteConfig = getSiteData(configEntries);
10
+ const { t } = createTranslator(siteConfig.lang || "zh-CN");
11
+ const tagList = await getTagCountList();
12
+ const tagLinks = tagList.map((t) => ({ type: "link" as const, text: t.name, url: `/tags/${t.name}/` }));
13
+ ---
14
+
15
+ <!-- 移动端:SSR 卡片 -->
16
+ <div class={styles.cards} id="tags-cards">
17
+ {
18
+ tagList.length > 0 ? (
19
+ tagList.map(({ name, count }) => (
20
+ <a
21
+ href={`/tags/${name}/`}
22
+ class={styles.tagCard}
23
+ data-count={count}
24
+ data-astro-prefetch
25
+ >
26
+ <span class={styles.tagName}>{name}</span>
27
+ <span class={styles.tagCount}>{count}</span>
28
+ </a>
29
+ ))
30
+ ) : (
31
+ <div class={styles.noTags}>{t("tags.noTags")}</div>
32
+ )
33
+ }
34
+ </div>
35
+
36
+ <!-- 桌面端:Canvas 标签云 -->
37
+ <div
38
+ class={styles.canvas}
39
+ id="tags-canvas"
40
+ data-tags={JSON.stringify(tagLinks)}
41
+ ></div>
42
+
43
+ <script>
44
+ import { TagCloud } from "@xingwangzhe/tags-cloud";
45
+ import { stagger, createTimeline } from "animejs";
46
+
47
+ document.addEventListener("astro:page-load", () => {
48
+ // ── 移动端卡片入场动画 ──
49
+ const cardsContainer = document.getElementById("tags-cards");
50
+ if (cardsContainer && window.matchMedia("(max-width: 768px)").matches) {
51
+ if (!cardsContainer.dataset.animated) {
52
+ cardsContainer.dataset.animated = "1";
53
+ const cards = cardsContainer.querySelectorAll<HTMLElement>(".tagCard");
54
+ if (cards.length > 0) {
55
+ cards.forEach((c) => (c.style.opacity = "0"));
56
+ createTimeline().add(
57
+ cards,
58
+ { opacity: [0, 1], duration: 400, ease: "outExpo", delay: stagger(30, { start: 0 }) },
59
+ 0,
60
+ );
61
+ }
62
+ }
63
+ }
64
+
65
+ // ── 桌面端:3D 标签云 ──
66
+ const canvasContainer = document.getElementById("tags-canvas");
67
+ if (!canvasContainer) return;
68
+ if (canvasContainer.dataset.inited === "1") return;
69
+ if (window.matchMedia("(max-width: 768px)").matches) return;
70
+
71
+ canvasContainer.dataset.inited = "1";
72
+
73
+ type LinkTag = { type: "link"; text: string; url: string };
74
+ const rawTags: LinkTag[] = JSON.parse(canvasContainer.dataset.tags || "[]");
75
+ if (rawTags.length === 0) return;
76
+ // 内部链接不应打开新标签页 — TagCloud 默认 link 类型会 window.open(_, _blank)
77
+ const tags = rawTags.map((t) => ({ ...t, onClick: () => { window.location.href = t.url; } }));
78
+
79
+ const N = tags.length;
80
+ const vw = window.innerWidth;
81
+ const baseRadius = vw < 1024 ? 330 : vw < 1440 ? 420 : 480;
82
+ const baseN = 100;
83
+ const densityScale = Math.sqrt(Math.max(N, baseN) / baseN);
84
+ const containerWidth = canvasContainer.getBoundingClientRect().width;
85
+ const maxRadius = Math.round(containerWidth * 0.6);
86
+ const radius = Math.min(Math.round(baseRadius * densityScale), maxRadius);
87
+
88
+ // 扩展容器高度以容纳球体垂直投影(宽度保持 100% 自然居中)
89
+ canvasContainer.style.height = `${Math.round(radius * 2)}px`;
90
+
91
+ new TagCloud(canvasContainer, {
92
+ tags,
93
+ radius,
94
+ spinY: 0.15,
95
+ fontSize: 16,
96
+ color: "#ffffff",
97
+ fontFamily: "system-ui, -apple-system, sans-serif",
98
+ });
99
+ });
100
+ </script>
@@ -0,0 +1,62 @@
1
+ ---
2
+ import type { CollectionEntry } from "astro:content";
3
+ import { render } from "astro:content";
4
+ import styles from "@styles/components/words/wordCard.module.css";
5
+ import "@styles/components/markdown.css";
6
+ import { toMachineDateTime } from "@utils/semantic-time";
7
+
8
+ interface Props {
9
+ entry: CollectionEntry<"words">;
10
+ timezone?: string;
11
+ }
12
+
13
+ const { entry, timezone } = Astro.props;
14
+ const { Content } = await render(entry);
15
+ const { source, link, sourceDate, date, abbrlink } = entry.data;
16
+
17
+ const machineDate = date && timezone
18
+ ? toMachineDateTime(date, timezone)
19
+ : date;
20
+ ---
21
+
22
+ <article class={styles.card} id={abbrlink ? `word-${abbrlink}` : undefined}>
23
+ <div class={styles.body} data-pagefind-body>
24
+ <Content />
25
+ </div>
26
+ {
27
+ (date || source || sourceDate) && (
28
+ <footer class={styles.footer}>
29
+ {date ? (
30
+ <time class={styles.date} datetime={machineDate}>
31
+ {date}
32
+ </time>
33
+ ) : (
34
+ <span />
35
+ )}
36
+ {
37
+ (source || sourceDate) && (
38
+ <div class={styles.sourceMeta}>
39
+ {sourceDate && (
40
+ <span class={styles.sourceDate}>{sourceDate}</span>
41
+ )}
42
+ {source && (
43
+ link ? (
44
+ <a
45
+ href={link}
46
+ target="_blank"
47
+ rel="noopener noreferrer"
48
+ class={styles.sourceLink}
49
+ >
50
+ — {source}
51
+ </a>
52
+ ) : (
53
+ <span class={styles.sourceText}>— {source}</span>
54
+ )
55
+ )}
56
+ </div>
57
+ )
58
+ }
59
+ </footer>
60
+ )
61
+ }
62
+ </article>
@@ -0,0 +1,70 @@
1
+ ---
2
+ import WordCard from "@components/stalux/words/wordCard.astro";
3
+ import styles from "@styles/components/words/wordList.module.css";
4
+ import type { CollectionEntry } from "astro:content";
5
+ import { toTimestamp } from "@utils/dayjs";
6
+
7
+ interface Props {
8
+ entries: CollectionEntry<"words">[];
9
+ timezone?: string;
10
+ }
11
+
12
+ const { entries, timezone } = Astro.props;
13
+
14
+ const sorted = [...entries].sort((a, b) => {
15
+ const ta = toTimestamp(a.data.date || a.id);
16
+ const tb = toTimestamp(b.data.date || b.id);
17
+ return tb - ta;
18
+ });
19
+ ---
20
+
21
+ <ul class={`${styles.list} stagger-children`}>
22
+ {
23
+ sorted.map((entry) => {
24
+ const hash = entry.data.abbrlink ? `#word-${entry.data.abbrlink}` : undefined;
25
+ return (
26
+ <li class={styles.item}>
27
+ {hash ? (
28
+ <a href="javascript:void(0)" class="a-none" data-word-hash={hash}>
29
+ <WordCard entry={entry} timezone={timezone} />
30
+ </a>
31
+ ) : (
32
+ <WordCard entry={entry} timezone={timezone} />
33
+ )}
34
+ </li>
35
+ );
36
+ })
37
+ }
38
+ </ul>
39
+
40
+ <script>
41
+ const scrollToWord = (hash) => {
42
+ const el = document.getElementById(hash.slice(1));
43
+ if (!el) return;
44
+
45
+ // 清除之前的高亮
46
+ for (const c of document.querySelectorAll("[data-glow]")) {
47
+ c.removeAttribute("data-glow");
48
+ }
49
+ // 高亮当前卡片
50
+ el.dataset.glow = "";
51
+
52
+ el.scrollIntoView({ behavior: "smooth", block: "center" });
53
+ history.replaceState(null, "", hash);
54
+ };
55
+
56
+ // 页面加载时:覆盖浏览器默认的瞬间跳转(双 rAF 等到布局稳定)
57
+ if (location.hash.startsWith("#word-")) {
58
+ requestAnimationFrame(() => {
59
+ requestAnimationFrame(() => scrollToWord(location.hash));
60
+ });
61
+ }
62
+
63
+ // 点击卡片时:阻止默认跳转,改为居中平滑滚动
64
+ document.addEventListener("click", (e) => {
65
+ const link = e.target.closest("a[data-word-hash]");
66
+ if (!link) return;
67
+ e.preventDefault();
68
+ scrollToWord(link.getAttribute("data-word-hash"));
69
+ });
70
+ </script>
package/src/config.ts ADDED
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Stalux 集成配置选项
3
+ *
4
+ * 用户在 astro.config.mjs 中传入这些选项来自定义主题行为。
5
+ */
6
+
7
+ import type { StaluxComponentKey } from "./internal/override";
8
+
9
+ /** 用户可覆盖的组件映射 */
10
+ export type ComponentOverrideMap = Partial<Record<StaluxComponentKey, string>>;
11
+
12
+ /** 内容集合路径配置 */
13
+ export interface ContentPaths {
14
+ /** 内容根目录,默认为 "stalux" */
15
+ root?: string;
16
+ /** 文章目录,相对于 root */
17
+ posts?: string;
18
+ /** 配置目录,相对于 root */
19
+ config?: string;
20
+ /** 关于页面目录,相对于 root */
21
+ about?: string;
22
+ /** 随想目录,相对于 root */
23
+ words?: string;
24
+ }
25
+
26
+ /** Analytics 配置 */
27
+ export interface AnalyticsConfig {
28
+ googleAnalyticsId?: string;
29
+ bingClarityId?: string;
30
+ umami?: {
31
+ id: string;
32
+ url: string;
33
+ };
34
+ }
35
+
36
+ /** Stalux 集成选项 */
37
+ export interface StaluxOptions {
38
+ /** 内容目录路径(默认 "stalux") */
39
+ contentDir?: string;
40
+
41
+ /** 内容子目录路径覆盖 */
42
+ contentPaths?: ContentPaths;
43
+
44
+ /** 组件覆盖映射 */
45
+ components?: ComponentOverrideMap;
46
+
47
+ /** Analytics 配置 */
48
+ analytics?: AnalyticsConfig;
49
+
50
+ /** 构建后是否自动运行 Pagefind 索引(默认 true) */
51
+ pagefind?: boolean;
52
+
53
+ /** 开发工具栏应用(默认 true) */
54
+ devToolbar?: boolean;
55
+
56
+ /** 站点 URL(默认从 astro.config 读取) */
57
+ site?: string;
58
+ }
@@ -0,0 +1,193 @@
1
+ {
2
+ "nav": {
3
+ "menu": "Open navigation menu",
4
+ "scrollLeft": "Scroll navigation left",
5
+ "scrollRight": "Scroll navigation right",
6
+ "search": "Search"
7
+ },
8
+ "search": {
9
+ "title": "Search",
10
+ "close": "Close search",
11
+ "placeholder": "Type keywords to search...",
12
+ "inputLabel": "Search input",
13
+ "noResults": "No results found",
14
+ "typePost": "Post",
15
+ "typeAbout": "About",
16
+ "typeWord": "Word"
17
+ },
18
+ "page404": {
19
+ "title": "404 - Page Not Found",
20
+ "description": "Sorry, the page you are looking for does not exist.",
21
+ "pageNotFound": "Page Not Found",
22
+ "countdown": "Redirecting to home in {n} seconds...",
23
+ "backHome": "Back to Home"
24
+ },
25
+ "post": {
26
+ "publishTime": "Published: ",
27
+ "updateTime": "Updated: ",
28
+ "readingTime": "🕒 Reading time: ",
29
+ "wordCount": "📝 Words: ",
30
+ "viewCount": "👀 Views: ",
31
+ "prevPost": "Previous",
32
+ "nextPost": "Next",
33
+ "noPrev": "No older posts",
34
+ "noNext": "No newer posts",
35
+ "author": "Author: ",
36
+ "articleLink": "Article link: ",
37
+ "licensedUnder": "This article is licensed under ",
38
+ "licenseSuffix": ".",
39
+ "viewSource": "View Markdown source"
40
+ },
41
+ "taxonomy": {
42
+ "tag": "Tag",
43
+ "category": "Category",
44
+ "toc": "Table of Contents",
45
+ "noTags": "No tags",
46
+ "noCategories": "No categories",
47
+ "noToc": "No table of contents",
48
+ "backToList": "Back to {type} list",
49
+ "foundPosts": "Found {count} related posts",
50
+ "browseTag": "Browse {count} articles tagged with “{tag}” on {title}. Explore tutorials, programming notes, and practical content related to this topic of interest.",
51
+ "browseCategory": "Browse {count} articles under category “{category}” on {title}. Find tutorials, development notes, and practical guides related to this topic area."
52
+ },
53
+ "archives": {
54
+ "title": "Archives",
55
+ "description": "Browse the complete timeline of all articles on {title}, organized by publication date. Access programming tutorials, dev notes, open source projects, and tech insights in chronological order.",
56
+ "heading": "Archives",
57
+ "desc": "All posts on the timeline ({n} total)",
58
+ "postCount": "{n} posts",
59
+ "month": "{n}",
60
+ "noPosts": "No posts",
61
+ "loadMore": "Load more",
62
+ "allLoaded": "All content loaded",
63
+ "viewAll": "View all posts"
64
+ },
65
+ "footer": {
66
+ "visitors": "Visitors: ",
67
+ "visits": "visitors",
68
+ "totalVisits": "Total visits: ",
69
+ "times": "times",
70
+ "totalWords": "Total words: ",
71
+ "chars": "chars",
72
+ "runningTime": "Site running for",
73
+ "runtimeFormat": "{days}d {hours}h {minutes}m {seconds}s",
74
+ "rightsReserved": "All rights reserved.",
75
+ "securityIcon": "Public security filing icon"
76
+ },
77
+ "comment": {
78
+ "title": "Comments",
79
+ "disclaimer": "Comments are user-generated and do not reflect the views of this site."
80
+ },
81
+ "randomPost": {
82
+ "title": "Random Post",
83
+ "refresh": "Refresh random post",
84
+ "loading": "Loading random post...",
85
+ "noPosts": "No posts available",
86
+ "loadFailed": "Failed to load, please try again",
87
+ "loadError": "Load failed"
88
+ },
89
+ "categories": {
90
+ "title": "Categories",
91
+ "description": "Browse all article categories on {title}, covering web development, Linux operations, open source projects, algorithms, AI, and programming tutorials. Find topics of interest organized by category.",
92
+ "heading": "Categories",
93
+ "browseByTopic": "Browse by topic",
94
+ "postCount": "{n} posts"
95
+ },
96
+ "tags": {
97
+ "title": "Tags",
98
+ "description": "Explore all tags on {title} — a keyword index covering web development, Linux, open source, AI, algorithms, and programming content. Discover articles by topic tags.",
99
+ "heading": "Tag Cloud",
100
+ "explore": "Explore article tags across different topics",
101
+ "noTags": "No tags"
102
+ },
103
+ "words": {
104
+ "title": "Words",
105
+ "description": "Collected quotes and snippets",
106
+ "heading": "Words",
107
+ "desc": "Fragments of words, voices of the heart ({n} entries)"
108
+ },
109
+ "links": {
110
+ "defaultTitle": "Links",
111
+ "iconAlt": "{name} icon"
112
+ },
113
+ "rss": {
114
+ "copyright": "Unless otherwise noted, each article is licensed under its own Creative Commons license."
115
+ },
116
+ "common": {
117
+ "uncategorized": "Uncategorized",
118
+ "minute": "{n} min",
119
+ "lessThanOneMinute": "Less than 1 min",
120
+ "tenThousand": "{n}0k",
121
+ "k": "{n}k",
122
+ "dateFormat": "MMM dd, yyyy"
123
+ },
124
+ "cc": {
125
+ "format": "Creative Commons {elements} {version} International License",
126
+ "zero": "CC0 1.0 Universal",
127
+ "BY": "Attribution",
128
+ "NC": "NonCommercial",
129
+ "SA": "ShareAlike",
130
+ "ND": "NoDerivatives"
131
+ },
132
+ "console": {
133
+ "name": "Stalux - Modern Astro Blog Theme",
134
+ "author": "xingwangzhe",
135
+ "copyright": "© xingwangzhe. All rights reserved.",
136
+ "certNumber": "Software Copyright No. 16914672",
137
+ "certUrl": "https://stalux.needhelp.icu/软著证明.pdf",
138
+ "regDate": "2025-11-24",
139
+ "labelVersion": "📦 Version: ",
140
+ "labelAuthor": "👤 Author: ",
141
+ "labelRepo": "🔗 Repository: ",
142
+ "labelWebsite": "🌐 Website: ",
143
+ "labelCert": "📜 Certificate: ",
144
+ "labelCertNumber": "🎫 Registration No.: ",
145
+ "labelRegDate": "📅 Registration Date: ",
146
+ "labelLicense": "⚖️ License: ",
147
+ "labelAuthNote": "✅ Authorization: ",
148
+ "labelTip": "💡 Tip: ",
149
+ "labelFeedback": "🐛 Feedback: ",
150
+ "authNote": "This software is licensed under the MIT open source license. You are free to use, modify, and distribute this software, but please retain the original copyright notice.",
151
+ "tip": "If you like this theme, please give us a ⭐️ on GitHub!",
152
+ "feedback": "https://github.com/xingwangzhe/stalux/issues"
153
+ },
154
+ "ai": {
155
+ "contact": "Contact",
156
+ "author": "Author",
157
+ "email": "Email",
158
+ "socialMedia": "Social media",
159
+ "noContact": "No public contact information available",
160
+ "posts": "Posts",
161
+ "noPosts": "No posts available",
162
+ "untitled": "Untitled",
163
+ "pages": "Pages",
164
+ "about": "About",
165
+ "aboutDesc": "About the author",
166
+ "archives": "Archives",
167
+ "archivesDesc": "All posts archive",
168
+ "tags": "Tags",
169
+ "tagsDesc": "Tags index",
170
+ "categories": "Categories",
171
+ "categoriesDesc": "Categories index",
172
+ "words": "Words",
173
+ "wordsDesc": "Quotes and snippets",
174
+ "links": "Links",
175
+ "aiDiscoveryFiles": "AI Discovery Files",
176
+ "llmsTxtDesc": "Site summary and post index",
177
+ "llmsFullTxtDesc": "Full-text Markdown dataset of all posts",
178
+ "optional": "Optional",
179
+ "morePosts": "More posts",
180
+ "rss": "RSS Feed",
181
+ "atom": "Atom Feed",
182
+ "mdAvailable": "Markdown source available for each post; links point to .md versions",
183
+ "siteLicense": "Unless otherwise noted, content on this site is licensed under CC-BY-NC-SA-4.0.",
184
+ "fullDataset": "LLM Full-Text Dataset",
185
+ "fullDatasetDesc": "The complete Markdown source of all public posts, for LLM training and citation.",
186
+ "license": "Content license",
187
+ "postCount": "Post count",
188
+ "markdownSources": "Markdown Sources",
189
+ "latestPosts": "Latest Posts",
190
+ "allCategories": "All Categories",
191
+ "allTags": "All Tags"
192
+ }
193
+ }