@xingwangzhe/stalux 1.56.0 → 1.58.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 (44) hide show
  1. package/package.json +4 -12
  2. package/src/components/stalux/archives/archivesList.astro +1 -1
  3. package/src/components/stalux/categories/categoriesCard.astro +1 -1
  4. package/src/components/stalux/posts/PostTaxonomy.astro +1 -1
  5. package/src/components/stalux/posts/postCard.astro +3 -3
  6. package/src/components/stalux/tags/tagHeader.astro +2 -2
  7. package/src/components/stalux/tags/tagsCloud.astro +2 -2
  8. package/src/index.ts +39 -13
  9. package/src/internal/html-output.ts +18 -0
  10. package/src/internal/modern-css.ts +1 -0
  11. package/src/internal/route-hrefs.ts +47 -0
  12. package/src/internal/social-card-data.ts +1 -1
  13. package/src/internal/style-names.generated.ts +87 -94
  14. package/src/internal/swup-options.ts +98 -0
  15. package/src/layouts/Stalux.astro +0 -1
  16. package/src/pages/404.astro +1 -1
  17. package/src/pages/api/posts.json.ts +1 -1
  18. package/src/pages/atom.xml.ts +2 -0
  19. package/src/pages/posts/[post].md.ts +2 -2
  20. package/src/pages/rss.xml.ts +2 -0
  21. package/src/scripts/analytics-runtime.ts +4 -9
  22. package/src/scripts/list-reveal.ts +3 -15
  23. package/src/scripts/module-motion.ts +2 -3
  24. package/src/scripts/navigation-scripts.ts +12 -7
  25. package/src/scripts/route-guards.ts +200 -0
  26. package/src/scripts/swup-internals.d.ts +15 -0
  27. package/src/styles/base.css +0 -8
  28. package/src/styles/generated-prose.css +1 -1
  29. package/src/styles/generated-waline.css +1 -1
  30. package/src/styles/generated.css +1 -1
  31. package/src/styles/integrations.css +14 -16
  32. package/src/styles/math.css +3 -1
  33. package/src/styles/motion.css +1 -22
  34. package/src/utils/accent-color.ts +52 -0
  35. package/src/utils/ai-discovery.ts +1 -1
  36. package/src/utils/feed.ts +1 -1
  37. package/src/utils/public-routes.ts +14 -4
  38. package/src/utils/seo-document.ts +3 -2
  39. package/src/scripts/route-motion.ts +0 -340
  40. package/src/scripts/route-out.ts +0 -63
  41. package/src/scripts/smooth-scroll.ts +0 -139
  42. package/src/scripts/swup-router.ts +0 -580
  43. package/src/scripts/view-transitions.ts +0 -4
  44. package/src/utils/route-motion-policy.ts +0 -72
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xingwangzhe/stalux",
3
- "version": "1.56.0",
3
+ "version": "1.58.0",
4
4
  "description": "A powerful, modern Astro blog theme — use as template or install as plugin",
5
5
  "keywords": [
6
6
  "astro",
@@ -22,6 +22,8 @@
22
22
  "engines": {
23
23
  "bun": ">=1.4.0"
24
24
  },
25
+ "// browserslist": "现代基线,与 src/internal/modern-css.ts 的 lightningcss targets 保持一致;当前构建不使用 autoprefixer/postcss,此字段用于声明目标并约束后续工具链。",
26
+ "browserslist": ["chrome >= 154", "firefox >= 157", "safari >= 26.6"],
25
27
  "bin": {
26
28
  "stalux": ".release/cli.mjs"
27
29
  },
@@ -110,17 +112,7 @@
110
112
  "@lucide/astro": "^1.71.0",
111
113
  "@microsoft/clarity": "^1.0.2",
112
114
  "@pagefind/component-ui": "^1.6.0",
113
- "@swup/a11y-plugin": "^5.2.1",
114
- "@swup/body-class-plugin": "^3.3.0",
115
- "@swup/fragment-plugin": "^1.3.1",
116
- "@swup/head-plugin": "^2.3.1",
117
- "@swup/js-plugin": "^3.2.0",
118
- "@swup/parallel-plugin": "^0.4.0",
119
- "@swup/preload-plugin": "^3.2.12",
120
- "@swup/progress-plugin": "^3.2.0",
121
- "@swup/route-name-plugin": "^4.1.0",
122
- "@swup/scripts-plugin": "^2.1.0",
123
- "@swup/scroll-plugin": "^4.0.0",
115
+ "@swup/astro": "^1.8.0",
124
116
  "@takumi-rs/core": "^2.20.0",
125
117
  "@w3c/webmcp-polyfill": "^0.1.0",
126
118
  "@waline/client": "^3.16.2",
@@ -98,7 +98,7 @@ const archiveYears = [...archivesByYearMonth.entries()]
98
98
  )}
99
99
  </time>
100
100
  <a
101
- href={`/posts/${post.data.abbrlink}/`}
101
+ href={`/posts/${post.data.abbrlink}`}
102
102
  class="stalux-archive-post-link"
103
103
  data-archive-description={post.data.desc || ""}
104
104
  >
@@ -23,7 +23,7 @@ const { t } = createTranslator((siteConfig.lang as string) || "zh-CN");
23
23
  <!-- Modern Web Guidance (html): 分类卡本身即 <article>;外层曾经的 <div class="stalux-category-shell">
24
24
  已上移到列表的 <li>(见 categoriesList.astro),避免多一层无语义容器。 -->
25
25
  <article class="stalux-category-card" data-stalux-control="category-card">
26
- <a href={`/categories/${props.category.name}/`} class="stalux-categoryheader a-none">
26
+ <a href={`/categories/${props.category.name}`} class="stalux-categoryheader a-none">
27
27
  <span class="stalux-category-icon">
28
28
  <svg
29
29
  aria-hidden="true"
@@ -51,7 +51,7 @@ const cfg = {
51
51
  <ul class={cfg.listClass} data-stalux-reveal>
52
52
  {items.map((item) => (
53
53
  <li data-stalux-reveal-item>
54
- <a href={`${cfg.prefix}${item}/`} class={`${cfg.itemClass} a-none`}>
54
+ <a href={`${cfg.prefix}${item}`} class={`${cfg.itemClass} a-none`}>
55
55
  {item}
56
56
  </a>
57
57
  </li>
@@ -27,7 +27,7 @@ const description = post.data.desc || "";
27
27
  class="stalux-post-card-shell stalux-post-card h-full text-inherit no-underline stalux-u-flex stalux-u-flex-col stalux-u-w-full stalux-u-min-w-0"
28
28
  >
29
29
  {post.data.cover && (
30
- <a href={`/posts/${post.data.abbrlink}/`} class="a-none" aria-label={post.data.title}>
30
+ <a href={`/posts/${post.data.abbrlink}`} class="a-none" aria-label={post.data.title}>
31
31
  <span class="stalux-card-post-cover stalux-u-w-full stalux-u-min-w-0">
32
32
  <img
33
33
  src={post.data.cover}
@@ -43,7 +43,7 @@ const description = post.data.desc || "";
43
43
  </a>
44
44
  )}
45
45
  <h2 class="stalux-card-post-title">
46
- <a href={`/posts/${post.data.abbrlink}/`} class="a-none">
46
+ <a href={`/posts/${post.data.abbrlink}`} class="a-none">
47
47
  {post.data.title}
48
48
  </a>
49
49
  </h2>
@@ -90,5 +90,5 @@ const description = post.data.desc || "";
90
90
  ))}
91
91
  </ul>
92
92
  </footer>
93
- <a href={`/posts/${post.data.abbrlink}/`} class="stalux-card-read-more a-none"> Read More </a>
93
+ <a href={`/posts/${post.data.abbrlink}`} class="stalux-card-read-more a-none"> Read More </a>
94
94
  </article>
@@ -21,9 +21,9 @@ const { t } = createTranslator(siteConfig.lang || "zh-CN");
21
21
  const typeKey = props.typeKey || props.type;
22
22
  let typeUrl = "";
23
23
  if (typeKey === "category" || typeKey === "categories" || typeKey === "分类") {
24
- typeUrl = "/categories/";
24
+ typeUrl = "/categories";
25
25
  } else if (typeKey === "tag" || typeKey === "tags" || typeKey === "标签") {
26
- typeUrl = "/tags/";
26
+ typeUrl = "/tags";
27
27
  }
28
28
  ---
29
29
 
@@ -17,7 +17,7 @@ const cloudDensity = Math.sqrt(Math.max(tagList.length, 100) / 100);
17
17
  const tagLinks = tagList.map((t) => ({
18
18
  type: "link" as const,
19
19
  text: t.name,
20
- url: `/tags/${t.name}/`,
20
+ url: `/tags/${t.name}`,
21
21
  }));
22
22
  ---
23
23
 
@@ -36,7 +36,7 @@ const tagLinks = tagList.map((t) => ({
36
36
  {group.map(({ name, count }) => (
37
37
  <li data-stalux-reveal-item>
38
38
  <a
39
- href={`/tags/${name}/`}
39
+ href={`/tags/${name}`}
40
40
  class="stalux-cloud-tag-card"
41
41
  data-stalux-card
42
42
  data-stalux-tag-card
package/src/index.ts CHANGED
@@ -4,6 +4,7 @@ import { compactStylesPlugin } from "./internal/compact-styles.ts";
4
4
  import { modernCssTargets } from "./internal/modern-css.ts";
5
5
  import { styleNames } from "./internal/style-names.generated.ts";
6
6
  import { swupDebugPlugin } from "./internal/swup-debug-plugin.ts";
7
+ import { SWUP_OPTIONS } from "./internal/swup-options.ts";
7
8
  /**
8
9
  * Stalux — Astro 博客主题集成入口
9
10
  *
@@ -18,6 +19,7 @@ import { existsSync, readdirSync, readFileSync } from "node:fs";
18
19
  import { createRequire } from "node:module";
19
20
  import path from "node:path";
20
21
  import sitemap from "@astrojs/sitemap";
22
+ import swup from "@swup/astro";
21
23
  import { mermaidHast, mermaidMdast } from "@xingwangzhe/satteri-mermaid";
22
24
  import { photoswipe } from "@xingwangzhe/satteri-photoswipe";
23
25
  import type { AstroIntegration } from "astro";
@@ -53,6 +55,7 @@ import { createPageOutputCache } from "./internal/page-output-cache";
53
55
  import { loadCoverEncoder, restoreCoverFiles, upgradeCoverImages } from "./internal/post-cover";
54
56
  import { isPluginProject } from "./internal/project-mode";
55
57
  import { syncBackgroundSvgs, syncFontAssets } from "./internal/public-assets";
58
+ import { normalizeRouteHrefs } from "./internal/route-hrefs";
56
59
  import { createRuntimeCacheKey } from "./internal/runtime-cache-key";
57
60
  import {
58
61
  appendUniquePlugin,
@@ -180,17 +183,20 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration[] {
180
183
  // Vite re-optimization would otherwise reload the
181
184
  // entire document midway through a Swup navigation.
182
185
  include: [
183
- "swup",
184
- "@swup/a11y-plugin",
185
- "@swup/body-class-plugin",
186
- "@swup/fragment-plugin",
187
- "@swup/parallel-plugin",
188
- "@swup/route-name-plugin",
189
- "@swup/scripts-plugin",
190
- "@swup/head-plugin",
191
- "@swup/js-plugin",
192
- "@swup/preload-plugin",
193
- "@swup/scroll-plugin",
186
+ // The router and its plugins are assembled by
187
+ // @swup/astro and imported through its client
188
+ // entry points, so those are the specifiers Vite
189
+ // has to see up front.
190
+ "@swup/astro/serialise",
191
+ "@swup/astro/client/Swup",
192
+ "@swup/astro/client/SwupA11yPlugin",
193
+ "@swup/astro/client/SwupBodyClassPlugin",
194
+ "@swup/astro/client/SwupFragmentPlugin",
195
+ "@swup/astro/client/SwupHeadPlugin",
196
+ "@swup/astro/client/SwupPreloadPlugin",
197
+ "@swup/astro/client/SwupProgressPlugin",
198
+ "@swup/astro/client/SwupRouteNamePlugin",
199
+ "@swup/astro/client/SwupScrollPlugin",
194
200
  "@microsoft/clarity",
195
201
  "@pagefind/component-ui",
196
202
  "@waline/client",
@@ -244,6 +250,12 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration[] {
244
250
  // 2. 注入全局 CSS
245
251
  injectScript("page-ssr", `import "${srcDir}/styles/generated.css";`);
246
252
  injectScript("page", `import "${srcDir}/scripts/core-runtime.ts";`);
253
+ // 路由层:@swup/astro 负责装配实例与插件,这里只挂三个护栏
254
+ // (脚本去重执行、过期 visit 中止、过期样式剪枝)。
255
+ injectScript(
256
+ "page",
257
+ `import { installRouteGuards } from "${srcDir}/scripts/route-guards.ts"; installRouteGuards();`,
258
+ );
247
259
 
248
260
  // 3. 注入所有页面路由(仅插件模式,源码模式下使用文件路由)
249
261
  if (isPluginMode) {
@@ -529,6 +541,8 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration[] {
529
541
  promoteCodeStyles(document);
530
542
  containMermaidSvgs(document);
531
543
  removePageFontStyles(document);
544
+ // 正文里手写的 /posts/x/ 也统一成路由器实际提供的形态
545
+ normalizeRouteHrefs(document);
532
546
  const images = findHtmlImages(before, document);
533
547
  await updateLocalImageDimensions(images, outDir, {
534
548
  cache: dimensionsCache,
@@ -676,8 +690,10 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration[] {
676
690
  };
677
691
 
678
692
  // 打包内置集成:Astro 的 integrations 配置支持嵌套数组并自动展平(schema 里 val.flat(Infinity)),
679
- // 因此这里返回 [coreIntegration, sitemap, expressiveCode],让消费方一行 `integrations: [stalux()]` 即可完成配置。
680
- const bundled: AstroIntegration[] = [coreIntegration];
693
+ // 因此这里返回 [swup, coreIntegration, sitemap, expressiveCode],让消费方一行 `integrations: [stalux()]` 即可完成配置。
694
+ // 路由器必须排在第一位:@swup/astro 先注入初始化脚本,主题的护栏脚本才能在同一轮
695
+ // 模块求值里直接拿到 window.swup。
696
+ const bundled: AstroIntegration[] = [swup(SWUP_OPTIONS), coreIntegration];
681
697
 
682
698
  if (opt.sitemap !== false) {
683
699
  const userSitemap = opt.sitemap === true ? undefined : opt.sitemap;
@@ -740,6 +756,16 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration[] {
740
756
  ...userSitemap,
741
757
  customPages,
742
758
  filter,
759
+ // Page routes are served without a trailing slash; keep the root
760
+ // and real files (`.md`, `.json`, `.xml`, images) exactly as built.
761
+ async serialize(item) {
762
+ const next = userSitemap?.serialize ? await userSitemap.serialize(item) : item;
763
+ if (!next) return next;
764
+ const url = new URL(next.url);
765
+ if (url.pathname !== "/") url.pathname = url.pathname.replace(/\/+$/u, "");
766
+ next.url = url.href;
767
+ return next;
768
+ },
743
769
  }),
744
770
  {
745
771
  name: "stalux/sitemap-alias",
@@ -1,4 +1,5 @@
1
1
  import { type DefaultTreeAdapterMap, serialize, serializeOuter } from "parse5";
2
+ import { normalizeRouteHref } from "./route-hrefs";
2
3
 
3
4
  type Element = DefaultTreeAdapterMap["element"];
4
5
 
@@ -17,6 +18,9 @@ export function createHtmlMutationEmitter(
17
18
  attrs: Element["attrs"];
18
19
  text: string[];
19
20
  }> = [];
21
+ // Anchor hrefs are rewritten by value range only: reserializing the whole
22
+ // element would re-escape attribute text the emitter never touched.
23
+ const hrefSnapshots: Array<{ node: Element; start: number; end: number }> = [];
20
24
  let head: Element | undefined;
21
25
  function visit(node: DefaultTreeAdapterMap["node"]) {
22
26
  if ("tagName" in node) {
@@ -37,6 +41,10 @@ export function createHtmlMutationEmitter(
37
41
  attrs: node.attrs.map((attribute) => ({ ...attribute })),
38
42
  text: node.childNodes.map((child) => ("value" in child ? child.value : "")),
39
43
  });
44
+ } else if (node.tagName === "a") {
45
+ const href = location?.attrs?.href;
46
+ if (href)
47
+ hrefSnapshots.push({ node, start: href.startOffset, end: href.endOffset });
40
48
  }
41
49
  }
42
50
  if ("childNodes" in node) for (const child of node.childNodes) visit(child);
@@ -45,6 +53,16 @@ export function createHtmlMutationEmitter(
45
53
 
46
54
  return () => {
47
55
  const edits: Array<{ start: number; end: number; value: string }> = [];
56
+ for (const { node, start, end } of hrefSnapshots) {
57
+ const current = node.attrs.find((attribute) => attribute.name === "href")?.value;
58
+ if (current === undefined) continue;
59
+ const raw = source.slice(start, end);
60
+ const parsed = /^(href\s*=\s*["'])([^"']*)(["'])$/u.exec(raw);
61
+ if (!parsed) continue;
62
+ const next = normalizeRouteHref(parsed[2] ?? "");
63
+ if (next === parsed[2]) continue;
64
+ edits.push({ start, end, value: `${parsed[1]}${next}${parsed[3]}` });
65
+ }
48
66
  for (const snapshot of snapshots) {
49
67
  const { node, start, end, attrs, text } = snapshot;
50
68
  if (!node.parentNode) {
@@ -1,5 +1,6 @@
1
1
  // Modern CSS output floor for the 1.35 release. Older fallback expansions are unnecessary.
2
2
  // Acceptance uses current stable browser engines; targets intentionally retain native nesting.
3
+ // Keep in sync with the `browserslist` field in package.json (documentation for other tools).
3
4
  export const modernCssTargets = {
4
5
  chrome: 154 << 16,
5
6
  firefox: 157 << 16,
@@ -0,0 +1,47 @@
1
+ import type { DefaultTreeAdapterMap } from "parse5";
2
+
3
+ type Document = DefaultTreeAdapterMap["document"];
4
+
5
+ /** Endpoints that are real files: the extension identifies them, never a slash. */
6
+ const REAL_FILE =
7
+ /\.(?:md|json|xml|txt|html?|pdf|png|webp|avif|svg|jpe?g|gif|ico|css|js|mjs|map|zip|mp4|webm|woff2?|ttf|otf)$/iu;
8
+
9
+ /**
10
+ * Rewrite a site-absolute href to the form the router actually serves.
11
+ *
12
+ * Page routes carry no trailing slash (`/posts/x`, `/archives`), while real
13
+ * files keep theirs (`/posts/x.md`, `/api/posts.json`). Only rooted paths are
14
+ * touched: relative links, protocol-relative URLs and anchors pass through.
15
+ */
16
+ export function normalizeRouteHref(href: string): string {
17
+ if (!href.startsWith("/") || href.startsWith("//")) return href;
18
+ const match = /^([^?#]*)([?#].*)?$/u.exec(href);
19
+ const path = match?.[1] ?? href;
20
+ const suffix = match?.[2] ?? "";
21
+ if (path === "/" || !path.endsWith("/") || REAL_FILE.test(path)) return href;
22
+ return `${path.replace(/\/+$/u, "")}${suffix}`;
23
+ }
24
+
25
+ /**
26
+ * Content-authored links still use the old trailing-slash form. Normalising the
27
+ * parsed document here keeps hand-written Markdown from reintroducing a URL
28
+ * shape the router no longer serves, without touching the author's source.
29
+ */
30
+ export function normalizeRouteHrefs(document: Document): number {
31
+ let changed = 0;
32
+ const visit = (node: DefaultTreeAdapterMap["node"]): void => {
33
+ if ("tagName" in node && node.tagName === "a") {
34
+ for (const attribute of node.attrs) {
35
+ if (attribute.name !== "href") continue;
36
+ const next = normalizeRouteHref(attribute.value);
37
+ if (next !== attribute.value) {
38
+ attribute.value = next;
39
+ changed++;
40
+ }
41
+ }
42
+ }
43
+ if ("childNodes" in node) for (const child of node.childNodes) visit(child);
44
+ };
45
+ visit(document);
46
+ return changed;
47
+ }
@@ -22,7 +22,7 @@ export function createSocialCardData(
22
22
  siteTitle: site.title,
23
23
  author: author.name,
24
24
  host: siteUrl.host,
25
- postUrl: new URL(`/posts/${encodeURIComponent(post.abbrlink)}/`, siteUrl).href,
25
+ postUrl: new URL(`/posts/${encodeURIComponent(post.abbrlink)}`, siteUrl).href,
26
26
  tags: post.tags ?? [],
27
27
  date: formatInTimeZone(
28
28
  parseDate(post.date, site.timezone || "Asia/Shanghai"),
@@ -247,101 +247,94 @@ export const styleNames: { classes: Record<string, string>; variables: Record<st
247
247
  "--border-thick": "--sh",
248
248
  "--border-thin": "--si",
249
249
  "--card-bg": "--sj",
250
- "--card-border": "--sk",
251
- "--card-box-shadow": "--sl",
252
- "--card-box-shadow-hover": "--sm",
253
- "--category-bg": "--sn",
254
- "--category-bg-hover": "--so",
255
- "--cloud-base-diameter": "--sp",
256
- "--cloud-density": "--sq",
257
- "--date-bg": "--sr",
258
- "--date-border": "--ss",
259
- "--dot": "--st",
260
- "--easing-bounce": "--su",
261
- "--font-size-base": "--sv",
262
- "--font-size-small": "--sw",
263
- "--font-size-smaller": "--sx",
264
- "--font-size-subtitle": "--sy",
265
- "--font-size-tiny": "--sz",
266
- "--muted-text": "--s10",
267
- "--nav-scale": "--s11",
268
- "--radius-full": "--s12",
269
- "--radius-lg": "--s13",
270
- "--radius-md": "--s14",
271
- "--radius-sm": "--s15",
272
- "--shadow-dark": "--s16",
273
- "--shadow-lg": "--s17",
274
- "--shadow-md": "--s18",
275
- "--shadow-raised-hover": "--s19",
276
- "--shadow-sidebar": "--s1a",
277
- "--shadow-sm": "--s1b",
278
- "--shadow-xl": "--s1c",
279
- "--space-2xl": "--s1d",
280
- "--space-2xs": "--s1e",
281
- "--space-3xl": "--s1f",
282
- "--space-lg": "--s1g",
283
- "--space-md": "--s1h",
284
- "--space-sm": "--s1i",
285
- "--space-xl": "--s1j",
286
- "--space-xs": "--s1k",
287
- "--stalux-accent-color": "--s1l",
288
- "--stalux-accent-rgb": "--s1m",
289
- "--stalux-bg-color": "--s1n",
290
- "--stalux-bg-filter": "--s1o",
291
- "--stalux-nav-offset": "--s1p",
292
- "--stalux-scrollbar-thumb": "--s1q",
293
- "--stalux-scrollbar-thumb-hover": "--s1r",
294
- "--stalux-scrollbar-track": "--s1s",
295
- "--stalux-site-header-height": "--s1t",
296
- "--stalux-site-header-min-height": "--s1u",
297
- "--stalux-text-shadow": "--s1v",
298
- "--surface-glass": "--s1w",
299
- "--surface-glass-shadow": "--s1x",
300
- "--tag-bg": "--s1y",
301
- "--tag-bg-hover": "--s1z",
302
- "--tl-line": "--s20",
303
- "--tl-pad": "--s21",
304
- "--transition-fast": "--s22",
305
- "--transition-medium": "--s23",
306
- "--transition-normal": "--s24",
307
- "--transition-slow": "--s25",
308
- "--transition-slower": "--s26",
309
- "--transition-slowest": "--s27",
310
- "--un-rotate": "--s28",
311
- "--un-rotate-x": "--s29",
312
- "--un-rotate-y": "--s2a",
313
- "--un-rotate-z": "--s2b",
314
- "--un-scale-x": "--s2c",
315
- "--un-scale-y": "--s2d",
316
- "--un-scale-z": "--s2e",
317
- "--un-skew-x": "--s2f",
318
- "--un-skew-y": "--s2g",
319
- "--un-translate-x": "--s2h",
320
- "--un-translate-y": "--s2i",
321
- "--un-translate-z": "--s2j",
322
- "--updated-bg": "--s2k",
323
- "--updated-border": "--s2l",
324
- "--white-05p": "--s2m",
325
- "--white-10p": "--s2n",
326
- "--white-15p": "--s2o",
327
- "--white-20p": "--s2p",
328
- "--white-30p": "--s2q",
329
- "--white-50p": "--s2r",
330
- "--white-60p": "--s2s",
331
- "--white-70p": "--s2t",
332
- "--white-75p": "--s2u",
333
- "--white-78p": "--s2v",
334
- "--white-80p": "--s2w",
335
- "--white-85p": "--s2x",
336
- "--white-90p": "--s2y",
337
- "--white-95p": "--s2z",
338
- "--white-full": "--s30"
250
+ "--card-box-shadow": "--sk",
251
+ "--card-box-shadow-hover": "--sl",
252
+ "--category-bg": "--sm",
253
+ "--category-bg-hover": "--sn",
254
+ "--cloud-base-diameter": "--so",
255
+ "--cloud-density": "--sp",
256
+ "--date-bg": "--sq",
257
+ "--date-border": "--sr",
258
+ "--dot": "--ss",
259
+ "--font-size-base": "--st",
260
+ "--font-size-small": "--su",
261
+ "--font-size-smaller": "--sv",
262
+ "--font-size-subtitle": "--sw",
263
+ "--font-size-tiny": "--sx",
264
+ "--muted-text": "--sy",
265
+ "--nav-scale": "--sz",
266
+ "--radius-full": "--s10",
267
+ "--radius-lg": "--s11",
268
+ "--radius-md": "--s12",
269
+ "--radius-sm": "--s13",
270
+ "--shadow-dark": "--s14",
271
+ "--shadow-lg": "--s15",
272
+ "--shadow-md": "--s16",
273
+ "--shadow-sidebar": "--s17",
274
+ "--shadow-sm": "--s18",
275
+ "--shadow-xl": "--s19",
276
+ "--space-2xl": "--s1a",
277
+ "--space-2xs": "--s1b",
278
+ "--space-3xl": "--s1c",
279
+ "--space-lg": "--s1d",
280
+ "--space-md": "--s1e",
281
+ "--space-sm": "--s1f",
282
+ "--space-xl": "--s1g",
283
+ "--space-xs": "--s1h",
284
+ "--stalux-accent-color": "--s1i",
285
+ "--stalux-accent-rgb": "--s1j",
286
+ "--stalux-bg-color": "--s1k",
287
+ "--stalux-bg-filter": "--s1l",
288
+ "--stalux-nav-offset": "--s1m",
289
+ "--stalux-scrollbar-thumb": "--s1n",
290
+ "--stalux-scrollbar-thumb-hover": "--s1o",
291
+ "--stalux-scrollbar-track": "--s1p",
292
+ "--stalux-site-header-height": "--s1q",
293
+ "--stalux-site-header-min-height": "--s1r",
294
+ "--stalux-text-shadow": "--s1s",
295
+ "--surface-glass": "--s1t",
296
+ "--surface-glass-shadow": "--s1u",
297
+ "--tag-bg": "--s1v",
298
+ "--tag-bg-hover": "--s1w",
299
+ "--tl-line": "--s1x",
300
+ "--tl-pad": "--s1y",
301
+ "--transition-fast": "--s1z",
302
+ "--transition-medium": "--s20",
303
+ "--transition-normal": "--s21",
304
+ "--transition-slow": "--s22",
305
+ "--un-rotate": "--s23",
306
+ "--un-rotate-x": "--s24",
307
+ "--un-rotate-y": "--s25",
308
+ "--un-rotate-z": "--s26",
309
+ "--un-scale-x": "--s27",
310
+ "--un-scale-y": "--s28",
311
+ "--un-scale-z": "--s29",
312
+ "--un-skew-x": "--s2a",
313
+ "--un-skew-y": "--s2b",
314
+ "--un-translate-x": "--s2c",
315
+ "--un-translate-y": "--s2d",
316
+ "--un-translate-z": "--s2e",
317
+ "--updated-bg": "--s2f",
318
+ "--updated-border": "--s2g",
319
+ "--white-05p": "--s2h",
320
+ "--white-10p": "--s2i",
321
+ "--white-15p": "--s2j",
322
+ "--white-20p": "--s2k",
323
+ "--white-30p": "--s2l",
324
+ "--white-50p": "--s2m",
325
+ "--white-60p": "--s2n",
326
+ "--white-70p": "--s2o",
327
+ "--white-75p": "--s2p",
328
+ "--white-78p": "--s2q",
329
+ "--white-80p": "--s2r",
330
+ "--white-85p": "--s2s",
331
+ "--white-90p": "--s2t",
332
+ "--white-95p": "--s2u",
333
+ "--white-full": "--s2v"
339
334
  },
340
335
  "animations": {
341
- "spin": "k0",
342
- "stalux-cursor-blink": "k1",
343
- "stalux-link-avatar-spin": "k2",
344
- "stalux-random-spin": "k3",
345
- "stalux-social-stalux-social-arrive": "k4"
336
+ "stalux-cursor-blink": "k0",
337
+ "stalux-random-spin": "k1",
338
+ "stalux-social-stalux-social-arrive": "k2"
346
339
  }
347
340
  };
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Swup configuration handed to `@swup/astro`.
3
+ *
4
+ * `@swup/astro` owns construction of the Swup instance and all plugin
5
+ * assembly; the theme only describes *which* behaviour it wants. Anything that
6
+ * cannot be expressed here lives in `src/scripts/route-guards.ts`.
7
+ */
8
+ import type { Options, Route } from "@swup/astro";
9
+
10
+ /** Named routes shared by the router and the route classifier. */
11
+ export const SWUP_ROUTES: Route[] = [
12
+ { name: "home", path: "/" },
13
+ { name: "article", path: "/posts/:post" },
14
+ { name: "archive", path: "/archives" },
15
+ { name: "tags-index", path: "/tags" },
16
+ { name: "tags-detail", path: "/tags/:tag" },
17
+ { name: "categories-index", path: "/categories" },
18
+ { name: "categories-detail", path: "/categories/:category" },
19
+ ...["about", "links", "words"].map((name) => ({ name, path: `/${name}` })),
20
+ { name: "not-found", path: "/404" },
21
+ ];
22
+
23
+ /**
24
+ * Real files are never pages. `@swup/astro` builds `ignoreVisit` itself and
25
+ * only lets `ignore` extend it, so this list is the whole hook.
26
+ */
27
+ export const SWUP_IGNORE_FILES = /\.(?:md|json|xml|txt|html?|pdf|png|webp|avif|svg|jpe?g)$/iu;
28
+
29
+ /**
30
+ * Paths the theme renders as soft-navigable pages.
31
+ *
32
+ * `@swup/astro` cannot express "only these routes" as a predicate: `ignore`
33
+ * accepts regexes, and a regex is the only form that survives serialisation
34
+ * into the init script. Keeping the alternation here also keeps the rule next
35
+ * to `SWUP_ROUTES` above, which is the same set of pages.
36
+ */
37
+ const SOFT_NAV_PATHS =
38
+ /^\/(?:|archives|links|words|about|404|tags(?:\/[^/?#]+)?|categories(?:\/[^/?#]+)?|posts\/[^/?#]+)\/?(?:[?#].*)?$/u;
39
+
40
+ /** Every path the theme itself renders; anything else is left to the browser. */
41
+ export const SWUP_IGNORE_UNKNOWN_ROUTES = new RegExp(`^(?!${SOFT_NAV_PATHS.source})`, "u");
42
+
43
+ /**
44
+ * `@swup/astro` types most plugin options as booleans, but forwards any object
45
+ * it is given straight into the plugin constructor. Two of these need their
46
+ * real plugin options (body-class prefix, head persistence, scroll behaviour),
47
+ * so the object form is used deliberately and the cast is made once here.
48
+ */
49
+ export const SWUP_OPTIONS = {
50
+ containers: ["#swup-main"],
51
+ // No route animation: no theme, and no `[class*="transition-"]` selector to
52
+ // wait on. The swap is a plain, synchronous DOM replacement.
53
+ theme: false,
54
+ animationClass: false,
55
+ // The init script must run while the first document is still parsing; on
56
+ // idle the first click can arrive before the router is listening.
57
+ loadOnIdle: false,
58
+ // The guard script needs the instance; route-prefetch already reads it.
59
+ globalInstance: true,
60
+ // Page scripts are re-executed by the theme's own once-per-session runner
61
+ // (see route-guards.ts). The plugin's whole-document pass would replay
62
+ // analytics SDKs on every visit.
63
+ reloadScripts: false,
64
+ cache: true,
65
+ native: false,
66
+ morph: false,
67
+ forms: false,
68
+ preload: { hover: true, visible: false },
69
+ progress: { delay: 80, transition: 200 },
70
+ accessibility: true,
71
+ updateBodyClass: { prefix: "page-", attributes: [/^data-stalux-(?!bg-)/u] },
72
+ updateHead: {
73
+ awaitAssets: true,
74
+ // Keep old CSS readable while the incoming sheet loads; route-guards
75
+ // removes the styles the new page does not use once it is in place.
76
+ persistTags: "link[rel=stylesheet], script[src], style",
77
+ },
78
+ routes: SWUP_ROUTES,
79
+ fragments: [
80
+ {
81
+ from: ["/tags/:slug", "/categories/:slug"],
82
+ to: ["/tags/:slug", "/categories/:slug"],
83
+ containers: ["#swup-taxonomy"],
84
+ scroll: true,
85
+ },
86
+ {
87
+ from: "/posts/:post",
88
+ to: "/posts/:post",
89
+ containers: ["#swup-article"],
90
+ scroll: true,
91
+ },
92
+ ],
93
+ smoothScrolling: {
94
+ doScrollingRightAway: false,
95
+ animateScroll: { betweenPages: false, samePageWithHash: true, samePage: true },
96
+ },
97
+ ignore: [SWUP_IGNORE_FILES, SWUP_IGNORE_UNKNOWN_ROUTES],
98
+ } as unknown as Partial<Options>;
@@ -74,7 +74,6 @@ const routeKind = getRouteKind(Astro.url.pathname);
74
74
  />
75
75
  <script>
76
76
  import "../scripts/background.ts";
77
- import "../scripts/view-transitions.ts";
78
77
  // Every page type has at least one list, so the unroll cue is registered
79
78
  // once in the shared layout instead of per page. Page-runtime dedupes it
80
79
  // against the article and archive components that also request it.