@xingwangzhe/stalux 1.28.4 → 1.29.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 (42) hide show
  1. package/.release/cli.mjs +1 -0
  2. package/README.md +6 -0
  3. package/package.json +1 -1
  4. package/src/components/stalux/analytics/umami.astro +1 -19
  5. package/src/components/stalux/archives/archivesList.astro +3 -3
  6. package/src/components/stalux/categories/categoriesList.astro +1 -1
  7. package/src/components/stalux/common/date.astro +21 -0
  8. package/src/components/stalux/posts/PostTaxonomy.astro +1 -6
  9. package/src/components/stalux/posts/postCard.astro +5 -1
  10. package/src/components/stalux/posts/postContent.astro +3 -4
  11. package/src/components/stalux/posts/postToc.astro +1 -6
  12. package/src/index.ts +6 -1
  13. package/src/internal/image-dimensions.ts +122 -0
  14. package/src/layouts/PostLayout.astro +3 -0
  15. package/src/layouts/Stalux.astro +4 -1
  16. package/src/pages/posts/[post].astro +1 -0
  17. package/src/schemas/config.ts +5 -0
  18. package/src/scripts/after-load.ts +27 -0
  19. package/src/scripts/clarity.ts +23 -16
  20. package/src/scripts/umami.ts +28 -0
  21. package/src/scripts/vercount.ts +20 -13
  22. package/src/scripts/waline-client.ts +23 -0
  23. package/src/scripts/waline.ts +29 -25
  24. package/src/styles/base/init.css +30 -17
  25. package/src/styles/components/archives/archivesList.module.css +11 -112
  26. package/src/styles/components/categories/categoriesCard.module.css +13 -7
  27. package/src/styles/components/categories/categoriesList.module.css +2 -5
  28. package/src/styles/components/layout/navs.module.css +4 -3
  29. package/src/styles/components/links/linkCard.module.css +13 -12
  30. package/src/styles/components/links/linkList.module.css +0 -13
  31. package/src/styles/components/posts/postCard.module.css +19 -21
  32. package/src/styles/components/posts/postContent.module.css +19 -11
  33. package/src/styles/components/posts/postLayout.module.css +5 -5
  34. package/src/styles/components/posts/postList.module.css +2 -1
  35. package/src/styles/components/posts/postNavigation.module.css +2 -3
  36. package/src/styles/components/posts/postSidebar.module.css +6 -4
  37. package/src/styles/components/posts/waline.css +3 -4
  38. package/src/styles/components/words/wordCard.module.css +9 -3
  39. package/src/styles/components/words/wordList.module.css +0 -13
  40. package/src/styles/shared/view-transitions.css +54 -234
  41. package/src/utils/accent-color.ts +24 -0
  42. package/src/utils/view-transitions.ts +30 -20
@@ -30,11 +30,13 @@
30
30
  .sidebarContainer {
31
31
  display: flex;
32
32
  flex-direction: column;
33
- background: var(--black-10p);
34
- border-radius: var(--radius-lg);
33
+ background: var(--surface-glass);
34
+ border-radius: 14px;
35
35
  padding: var(--space-md);
36
- box-shadow: var(--shadow-sidebar);
37
- transition: all var(--transition-normal) ease;
36
+ box-shadow: var(--surface-glass-shadow);
37
+ transition:
38
+ box-shadow 220ms ease,
39
+ background-color 220ms ease;
38
40
  }
39
41
 
40
42
  @media (max-width: 1400px) {
@@ -1,5 +1,5 @@
1
1
  :root {
2
- --waline-accent-rgb: 100, 255, 218;
2
+ --waline-accent-rgb: var(--stalux-accent-rgb);
3
3
  }
4
4
 
5
5
  /* 评论区容器与博客风格一致 */
@@ -8,9 +8,8 @@
8
8
  margin-bottom: 1rem;
9
9
  padding: 2rem;
10
10
  border-radius: 12px;
11
- animation: fadeInUpComment 0.8s ease-in-out forwards;
12
- opacity: 0;
13
- animation-delay: 0.3s;
11
+ animation: fadeInUpComment 0.35s ease-out both;
12
+ animation-delay: 0.08s;
14
13
  }
15
14
 
16
15
  /* 评论区标题样式与博客风格一致 */
@@ -1,9 +1,10 @@
1
1
  .card {
2
2
  width: 100%;
3
- background: var(--white-10p);
3
+ box-sizing: border-box;
4
+ background: var(--surface-glass);
4
5
  border-radius: var(--radius-lg, 12px);
5
6
  padding: 1.5rem;
6
- box-shadow: var(--shadow-md);
7
+ box-shadow: var(--shadow-sidebar);
7
8
  transition:
8
9
  transform var(--transition-normal) ease,
9
10
  box-shadow var(--transition-normal) ease;
@@ -12,9 +13,14 @@
12
13
  gap: 0.75rem;
13
14
  }
14
15
 
16
+ .body :global(.expressive-code) {
17
+ max-width: 100%;
18
+ overflow-x: auto;
19
+ }
20
+
15
21
  .card:hover {
16
22
  transform: translateY(-4px);
17
- box-shadow: var(--shadow-xl);
23
+ box-shadow: var(--shadow-raised-hover);
18
24
  }
19
25
 
20
26
  /* 被 hash 定位的卡片 — 主题光晕 */
@@ -12,19 +12,6 @@
12
12
  width: 100%;
13
13
  max-width: 900px;
14
14
  display: flex;
15
- animation: fadeInUp 0.5s ease forwards;
16
- opacity: 0;
17
- }
18
-
19
- @keyframes fadeInUp {
20
- from {
21
- opacity: 0;
22
- transform: translateY(20px);
23
- }
24
- to {
25
- opacity: 1;
26
- transform: translateY(0);
27
- }
28
15
  }
29
16
 
30
17
  @media (max-width: 768px) {
@@ -1,52 +1,36 @@
1
- /* 页面外壳的路由专属入场动画;列表和卡片继续使用各自的 stagger。 */
2
- @keyframes stalux-sidebar-left-out {
3
- from {
4
- opacity: 1;
5
- transform: translateX(0);
6
- }
1
+ /* Keep route motion on the page shell; named child elements handle continuity. */
2
+ @keyframes stalux-page-exit {
7
3
  to {
8
4
  opacity: 0;
9
- transform: translateX(-34px);
10
5
  }
11
6
  }
12
7
 
13
- @keyframes stalux-sidebar-left-in {
8
+ @keyframes stalux-page-enter {
14
9
  from {
15
10
  opacity: 0;
16
- transform: translateX(-34px);
11
+ transform: translateY(9px);
17
12
  }
18
13
  to {
19
14
  opacity: 1;
20
- transform: translateX(0);
15
+ transform: translateY(0);
21
16
  }
22
17
  }
23
18
 
24
- @keyframes stalux-sidebar-left-back-in {
19
+ @keyframes stalux-page-enter-back {
25
20
  from {
26
21
  opacity: 0;
27
- transform: translateX(34px);
22
+ transform: translateY(-9px);
28
23
  }
29
24
  to {
30
25
  opacity: 1;
31
- transform: translateX(0);
32
- }
33
- }
34
-
35
- @keyframes stalux-sidebar-left-back-out {
36
- from {
37
- opacity: 1;
38
- transform: translateX(0);
39
- }
40
- to {
41
- opacity: 0;
42
- transform: translateX(34px);
26
+ transform: translateY(0);
43
27
  }
44
28
  }
45
29
 
46
- @keyframes stalux-sidebar-right-in {
30
+ @keyframes stalux-taxonomy-enter {
47
31
  from {
48
32
  opacity: 0;
49
- transform: translateX(34px);
33
+ transform: translateX(12px);
50
34
  }
51
35
  to {
52
36
  opacity: 1;
@@ -54,10 +38,10 @@
54
38
  }
55
39
  }
56
40
 
57
- @keyframes stalux-sidebar-right-back-in {
41
+ @keyframes stalux-taxonomy-enter-back {
58
42
  from {
59
43
  opacity: 0;
60
- transform: translateX(-34px);
44
+ transform: translateX(-12px);
61
45
  }
62
46
  to {
63
47
  opacity: 1;
@@ -65,74 +49,10 @@
65
49
  }
66
50
  }
67
51
 
68
- @keyframes stalux-sidebar-right-out {
52
+ @keyframes stalux-article-enter {
69
53
  from {
70
- opacity: 1;
71
- transform: translateX(0);
72
- }
73
- to {
74
54
  opacity: 0;
75
- transform: translateX(34px);
76
- }
77
- }
78
-
79
- @keyframes stalux-sidebar-right-back-out {
80
- from {
81
- opacity: 1;
82
- transform: translateX(0);
83
- }
84
- to {
85
- opacity: 0;
86
- transform: translateX(-34px);
87
- }
88
- }
89
-
90
- @keyframes stalux-page-fade-out {
91
- from {
92
- opacity: 1;
93
- }
94
- to {
95
- opacity: 0;
96
- }
97
- }
98
-
99
- @keyframes stalux-page-home-in {
100
- from {
101
- opacity: 0;
102
- transform: scale(0.98);
103
- }
104
- to {
105
- opacity: 1;
106
- transform: scale(1);
107
- }
108
- }
109
-
110
- @keyframes stalux-page-home-back-in {
111
- from {
112
- opacity: 0;
113
- transform: scale(1.02);
114
- }
115
- to {
116
- opacity: 1;
117
- transform: scale(1);
118
- }
119
- }
120
-
121
- @keyframes stalux-page-vertical-in {
122
- from {
123
- opacity: 0;
124
- transform: translateY(14px);
125
- }
126
- to {
127
- opacity: 1;
128
- transform: translateY(0);
129
- }
130
- }
131
-
132
- @keyframes stalux-page-vertical-back-in {
133
- from {
134
- opacity: 0;
135
- transform: translateY(-14px);
55
+ transform: translateY(6px);
136
56
  }
137
57
  to {
138
58
  opacity: 1;
@@ -140,54 +60,10 @@
140
60
  }
141
61
  }
142
62
 
143
- @keyframes stalux-page-grid-in {
144
- from {
145
- opacity: 0;
146
- transform: translateY(8px) scale(0.975);
147
- }
148
- to {
149
- opacity: 1;
150
- transform: translateY(0) scale(1);
151
- }
152
- }
153
-
154
- @keyframes stalux-page-category-grid-in {
155
- from {
156
- opacity: 0;
157
- transform: translateY(8px) scale(0.975);
158
- }
159
- to {
160
- opacity: 1;
161
- transform: translateY(0) scale(1);
162
- }
163
- }
164
-
165
- @keyframes stalux-page-grid-back-in {
63
+ @keyframes stalux-article-enter-back {
166
64
  from {
167
65
  opacity: 0;
168
- transform: translateY(-8px) scale(0.985);
169
- }
170
- to {
171
- opacity: 1;
172
- transform: translateY(0) scale(1);
173
- }
174
- }
175
-
176
- @keyframes stalux-page-category-grid-back-in {
177
- from {
178
- opacity: 0;
179
- transform: translateY(-8px) scale(0.985);
180
- }
181
- to {
182
- opacity: 1;
183
- transform: translateY(0) scale(1);
184
- }
185
- }
186
-
187
- @keyframes stalux-page-rise-in {
188
- from {
189
- opacity: 0;
190
- transform: translateY(18px);
66
+ transform: translateY(-6px);
191
67
  }
192
68
  to {
193
69
  opacity: 1;
@@ -195,76 +71,46 @@
195
71
  }
196
72
  }
197
73
 
198
- @keyframes stalux-page-rise-back-in {
74
+ @keyframes stalux-sidebar-left-in {
199
75
  from {
200
76
  opacity: 0;
201
- transform: translateY(-18px);
77
+ transform: translateX(-14px);
202
78
  }
203
79
  to {
204
80
  opacity: 1;
205
- transform: translateY(0);
81
+ transform: translateX(0);
206
82
  }
207
83
  }
208
84
 
209
- @keyframes stalux-page-content-in {
210
- from {
211
- opacity: 0;
212
- transform: translateY(10px);
213
- }
85
+ @keyframes stalux-sidebar-left-out {
214
86
  to {
215
- opacity: 1;
216
- transform: translateY(0);
217
- }
218
- }
219
-
220
- @keyframes stalux-page-content-back-in {
221
- from {
222
87
  opacity: 0;
223
- transform: translateY(-10px);
224
- }
225
- to {
226
- opacity: 1;
227
- transform: translateY(0);
88
+ transform: translateX(-10px);
228
89
  }
229
90
  }
230
91
 
231
- @keyframes stalux-page-tag-cloud-in {
92
+ @keyframes stalux-sidebar-left-back-in {
232
93
  from {
233
94
  opacity: 0;
234
- transform: scale(0.985);
95
+ transform: translateX(14px);
235
96
  }
236
97
  to {
237
98
  opacity: 1;
238
- transform: scale(1);
99
+ transform: translateX(0);
239
100
  }
240
101
  }
241
102
 
242
- @keyframes stalux-page-tag-cloud-back-in {
243
- from {
244
- opacity: 0;
245
- transform: scale(1.015);
246
- }
103
+ @keyframes stalux-sidebar-left-back-out {
247
104
  to {
248
- opacity: 1;
249
- transform: scale(1);
250
- }
251
- }
252
-
253
- @keyframes stalux-page-from-right-in {
254
- from {
255
105
  opacity: 0;
256
- transform: translateX(22px);
257
- }
258
- to {
259
- opacity: 1;
260
- transform: translateX(0);
106
+ transform: translateX(10px);
261
107
  }
262
108
  }
263
109
 
264
- @keyframes stalux-page-from-left-in {
110
+ @keyframes stalux-sidebar-right-in {
265
111
  from {
266
112
  opacity: 0;
267
- transform: translateX(-22px);
113
+ transform: translateX(14px);
268
114
  }
269
115
  to {
270
116
  opacity: 1;
@@ -272,65 +118,28 @@
272
118
  }
273
119
  }
274
120
 
275
- @keyframes stalux-page-article-in {
276
- from {
277
- opacity: 0;
278
- transform: translateY(6px);
279
- }
280
- to {
281
- opacity: 1;
282
- transform: translateY(0);
283
- }
284
- }
285
-
286
- @keyframes stalux-page-article-back-in {
287
- from {
288
- opacity: 0;
289
- transform: translateY(-6px);
290
- }
121
+ @keyframes stalux-sidebar-right-out {
291
122
  to {
292
- opacity: 1;
293
- transform: translateY(0);
294
- }
295
- }
296
-
297
- @keyframes stalux-page-error-in {
298
- from {
299
123
  opacity: 0;
300
- transform: scale(0.97);
301
- }
302
- to {
303
- opacity: 1;
304
- transform: scale(1);
124
+ transform: translateX(10px);
305
125
  }
306
126
  }
307
127
 
308
- @keyframes stalux-page-error-back-in {
128
+ @keyframes stalux-sidebar-right-back-in {
309
129
  from {
310
130
  opacity: 0;
311
- transform: scale(0.97);
131
+ transform: translateX(-14px);
312
132
  }
313
133
  to {
314
134
  opacity: 1;
315
- transform: scale(1);
135
+ transform: translateX(0);
316
136
  }
317
137
  }
318
138
 
319
- @keyframes stalux-page-fade-in {
320
- from {
321
- opacity: 0;
322
- }
139
+ @keyframes stalux-sidebar-right-back-out {
323
140
  to {
324
- opacity: 1;
325
- }
326
- }
327
-
328
- @keyframes stalux-page-fade-back-in {
329
- from {
330
141
  opacity: 0;
331
- }
332
- to {
333
- opacity: 1;
142
+ transform: translateX(-10px);
334
143
  }
335
144
  }
336
145
 
@@ -343,14 +152,25 @@
343
152
  transform-origin: center top;
344
153
  }
345
154
 
346
- @media (prefers-reduced-motion: reduce) {
347
- ::view-transition-group(stalux-page),
348
- ::view-transition-old(stalux-page),
349
- ::view-transition-new(stalux-page) {
350
- animation: none !important;
351
- }
155
+ ::view-transition-group(*) {
156
+ animation-duration: 240ms;
157
+ animation-timing-function: cubic-bezier(0.22, 0.72, 0.22, 1);
158
+ }
352
159
 
353
- .stalux-main {
354
- animation: none !important;
160
+ @media (prefers-reduced-motion: reduce) {
161
+ ::view-transition-group(*),
162
+ ::view-transition-old(*),
163
+ ::view-transition-new(*) {
164
+ animation-duration: 1ms !important;
165
+ animation-delay: 0ms !important;
166
+ }
167
+
168
+ .stalux-main,
169
+ .stalux-main *,
170
+ .postLayout * {
171
+ animation-duration: 1ms !important;
172
+ animation-iteration-count: 1 !important;
173
+ transition-duration: 1ms !important;
174
+ scroll-behavior: auto !important;
355
175
  }
356
176
  }
@@ -0,0 +1,24 @@
1
+ const ACCENT_COLOR_PATTERN = /^#[\da-fA-F]{6}$/;
2
+
3
+ const ACCENT_OPACITIES = [90, 85, 80, 70, 60, 50, 40, 30, 20] as const;
4
+
5
+ /** Build the CSS custom properties shared by Stalux and its comment styles. */
6
+ export function getAccentColorStyle(accentColor: string): string {
7
+ if (!ACCENT_COLOR_PATTERN.test(accentColor)) {
8
+ throw new Error(`Invalid Stalux accent color "${accentColor}". Expected #RRGGBB.`);
9
+ }
10
+
11
+ const hex = accentColor.slice(1);
12
+ const red = Number.parseInt(hex.slice(0, 2), 16);
13
+ const green = Number.parseInt(hex.slice(2, 4), 16);
14
+ const blue = Number.parseInt(hex.slice(4, 6), 16);
15
+ const rgb = `${red}, ${green}, ${blue}`;
16
+
17
+ return [
18
+ `--stalux-accent-color: ${accentColor.toLowerCase()}`,
19
+ `--stalux-accent-rgb: ${rgb}`,
20
+ ...ACCENT_OPACITIES.map(
21
+ (opacity) => `--accent-${opacity}p: rgba(${rgb}, ${opacity / 100})`,
22
+ ),
23
+ ].join("; ");
24
+ }
@@ -2,6 +2,16 @@ import { getRouteKind, type RouteKind } from "./public-routes";
2
2
 
3
3
  export { getRouteKind, type RouteKind };
4
4
 
5
+ /** Stable, CSS-safe transition names for matching a post card to its title. */
6
+ export function getPostTitleTransitionName(postId: string | number): string {
7
+ let hash = 2166136261;
8
+ for (const character of String(postId)) {
9
+ hash ^= character.codePointAt(0) ?? 0;
10
+ hash = Math.imul(hash, 16777619);
11
+ }
12
+ return `stalux-post-title-${(hash >>> 0).toString(36)}`;
13
+ }
14
+
5
15
  export type TransitionAnimation = {
6
16
  name: string;
7
17
  delay?: number | string;
@@ -24,10 +34,10 @@ export type RouteAnimation = {
24
34
  };
25
35
  };
26
36
 
27
- const easing = "cubic-bezier(0.25, 0.9, 0.2, 1)";
37
+ const easing = "cubic-bezier(0.22, 0.72, 0.22, 1)";
28
38
  const exitAnimation: TransitionAnimation = {
29
- name: "stalux-page-fade-out",
30
- duration: "0.16s",
39
+ name: "stalux-page-exit",
40
+ duration: "0.14s",
31
41
  easing,
32
42
  fillMode: "both",
33
43
  };
@@ -37,8 +47,8 @@ function createRouteAnimation(name: string, backName = `${name}-back`): RouteAni
37
47
  forwards: {
38
48
  old: exitAnimation,
39
49
  new: {
40
- name: `stalux-page-${name}-in`,
41
- duration: "0.2s",
50
+ name: `stalux-${name}`,
51
+ duration: "0.22s",
42
52
  easing,
43
53
  fillMode: "both",
44
54
  },
@@ -46,8 +56,8 @@ function createRouteAnimation(name: string, backName = `${name}-back`): RouteAni
46
56
  backwards: {
47
57
  old: exitAnimation,
48
58
  new: {
49
- name: `stalux-page-${backName}-in`,
50
- duration: "0.2s",
59
+ name: `stalux-${backName}`,
60
+ duration: "0.22s",
51
61
  easing,
52
62
  fillMode: "both",
53
63
  },
@@ -56,18 +66,18 @@ function createRouteAnimation(name: string, backName = `${name}-back`): RouteAni
56
66
  }
57
67
 
58
68
  const routeAnimations: Record<RouteKind, RouteAnimation> = {
59
- home: createRouteAnimation("home", "home-back"),
60
- archive: createRouteAnimation("vertical", "vertical-back"),
61
- links: createRouteAnimation("grid", "grid-back"),
62
- words: createRouteAnimation("rise", "rise-back"),
63
- about: createRouteAnimation("content", "content-back"),
64
- "tags-index": createRouteAnimation("tag-cloud", "tag-cloud-back"),
65
- "tags-detail": createRouteAnimation("from-right", "from-left"),
66
- "categories-index": createRouteAnimation("category-grid", "category-grid-back"),
67
- "categories-detail": createRouteAnimation("from-left", "from-right"),
68
- article: createRouteAnimation("article", "article-back"),
69
- "not-found": createRouteAnimation("error", "error-back"),
70
- other: createRouteAnimation("fade", "fade-back"),
69
+ home: createRouteAnimation("page-enter"),
70
+ archive: createRouteAnimation("page-enter"),
71
+ links: createRouteAnimation("page-enter"),
72
+ words: createRouteAnimation("page-enter"),
73
+ about: createRouteAnimation("page-enter"),
74
+ "tags-index": createRouteAnimation("page-enter"),
75
+ "tags-detail": createRouteAnimation("taxonomy-enter", "taxonomy-enter-back"),
76
+ "categories-index": createRouteAnimation("page-enter"),
77
+ "categories-detail": createRouteAnimation("taxonomy-enter", "taxonomy-enter-back"),
78
+ article: createRouteAnimation("article-enter", "article-enter-back"),
79
+ "not-found": createRouteAnimation("page-enter"),
80
+ other: createRouteAnimation("page-enter"),
71
81
  };
72
82
 
73
83
  export function getRouteAnimation(routeKind: RouteKind): RouteAnimation {
@@ -81,7 +91,7 @@ function createSidebarAnimation(
81
91
  ): TransitionAnimation {
82
92
  return {
83
93
  name: `stalux-sidebar-${side}-${backwards ? "back-" : ""}${phase}`,
84
- duration: "0.8s",
94
+ duration: "0.22s",
85
95
  easing,
86
96
  fillMode: "both",
87
97
  };