@xingwangzhe/stalux 1.30.3 → 1.31.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.
- package/README.md +2 -0
- package/package.json +1 -1
- package/src/components/stalux/categories/categoriesCard.astro +33 -31
- package/src/components/stalux/layout/BackToTop.astro +69 -0
- package/src/components/stalux/links/linkCard.astro +15 -13
- package/src/components/stalux/posts/postCard.astro +1 -1
- package/src/internal/html-images.ts +65 -0
- package/src/internal/image-dimensions.ts +10 -20
- package/src/layouts/Stalux.astro +2 -0
- package/src/plugins/feature-flags.ts +20 -38
- package/src/scripts/back-to-top.ts +23 -0
- package/src/styles/base.css +96 -9
- package/src/styles/content.css +88 -18
- package/src/styles/generated-article.css +1 -1
- package/src/styles/generated-categories.css +1 -1
- package/src/styles/generated-links.css +1 -1
- package/src/styles/generated-prose.css +1 -1
- package/src/styles/generated-tags.css +1 -1
- package/src/styles/generated-waline.css +1 -1
- package/src/styles/generated-words.css +1 -1
- package/src/styles/generated.css +1 -1
- package/src/styles/integrations.css +130 -62
- package/src/styles/motion.css +2 -2
package/README.md
CHANGED
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
|
|
11
11
|
A dark-themed, high-performance Astro blog theme with elegant glassmorphism design, native per-page CJK font subsets, and a focus on content-first reading experience.
|
|
12
12
|
|
|
13
|
+
All HTML routes include a circular back-to-top button at the bottom right. It appears after scrolling 240px and scrolls smoothly to the top, respecting reduced motion preferences. A 64px right gutter keeps it clear of content; it is hidden when printing. No configuration is required.
|
|
14
|
+
|
|
13
15
|
---
|
|
14
16
|
|
|
15
17
|
## 🚀 Quick Start
|
package/package.json
CHANGED
|
@@ -20,35 +20,37 @@ const siteConfig = (configEntries.find((e) => e.id === "site")?.data ?? {}) as R
|
|
|
20
20
|
const { t } = createTranslator((siteConfig.lang as string) || "zh-CN");
|
|
21
21
|
---
|
|
22
22
|
|
|
23
|
-
<
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
<div class="stalux-category-content">
|
|
48
|
-
<h2 class="categoryName">{props.category.name}</h2>
|
|
49
|
-
<div class="categoryCount">
|
|
50
|
-
{t("categories.postCount", { n: props.category.count })}
|
|
23
|
+
<div class="stalux-category-shell">
|
|
24
|
+
<article class="stalux-category-card">
|
|
25
|
+
<a
|
|
26
|
+
href={`/categories/${props.category.name}/`}
|
|
27
|
+
class="stalux-categoryheader a-none"
|
|
28
|
+
data-astro-prefetch
|
|
29
|
+
>
|
|
30
|
+
<div class="stalux-category-icon">
|
|
31
|
+
<svg
|
|
32
|
+
aria-hidden="true"
|
|
33
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
34
|
+
viewBox="0 0 24 24"
|
|
35
|
+
width="24"
|
|
36
|
+
height="24"
|
|
37
|
+
fill="none"
|
|
38
|
+
stroke="currentColor"
|
|
39
|
+
stroke-width="2"
|
|
40
|
+
stroke-linecap="round"
|
|
41
|
+
stroke-linejoin="round"
|
|
42
|
+
>
|
|
43
|
+
<path
|
|
44
|
+
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"
|
|
45
|
+
></path>
|
|
46
|
+
</svg>
|
|
51
47
|
</div>
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
48
|
+
<div class="stalux-category-content">
|
|
49
|
+
<h2 class="categoryName">{props.category.name}</h2>
|
|
50
|
+
<div class="categoryCount">
|
|
51
|
+
{t("categories.postCount", { n: props.category.count })}
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
</a>
|
|
55
|
+
</article>
|
|
56
|
+
</div>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { ArrowUp } from "@lucide/astro";
|
|
3
|
+
|
|
4
|
+
const { lang = "zh-CN" } = Astro.props;
|
|
5
|
+
const label = lang.startsWith("zh") ? "回到顶部" : "Back to top";
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
<button
|
|
9
|
+
type="button"
|
|
10
|
+
class="stalux-back-to-top"
|
|
11
|
+
data-stalux-back-to-top
|
|
12
|
+
aria-label={label}
|
|
13
|
+
title={label}
|
|
14
|
+
hidden
|
|
15
|
+
>
|
|
16
|
+
<ArrowUp size={20} aria-hidden="true" />
|
|
17
|
+
</button>
|
|
18
|
+
|
|
19
|
+
<script>
|
|
20
|
+
import { mountBackToTop } from "../../../scripts/back-to-top";
|
|
21
|
+
import { registerPageLifecycle } from "../../../scripts/page-runtime";
|
|
22
|
+
|
|
23
|
+
registerPageLifecycle("back-to-top", mountBackToTop);
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<style is:global>
|
|
27
|
+
/* Reserve a rail outside content, including on narrow screens. */
|
|
28
|
+
.stalux-root {
|
|
29
|
+
box-sizing: border-box;
|
|
30
|
+
padding-right: calc(64px + env(safe-area-inset-right, 0px));
|
|
31
|
+
}
|
|
32
|
+
.stalux-back-to-top {
|
|
33
|
+
position: fixed;
|
|
34
|
+
right: calc(10px + env(safe-area-inset-right, 0px));
|
|
35
|
+
bottom: calc(20px + env(safe-area-inset-bottom, 0px));
|
|
36
|
+
width: 44px;
|
|
37
|
+
height: 44px;
|
|
38
|
+
display: grid;
|
|
39
|
+
place-items: center;
|
|
40
|
+
padding: 0;
|
|
41
|
+
border: 1px solid var(--stalux-accent-color);
|
|
42
|
+
border-radius: 50%;
|
|
43
|
+
color: var(--stalux-accent-color);
|
|
44
|
+
background: #202020;
|
|
45
|
+
box-shadow: 0 2px 12px #0006;
|
|
46
|
+
cursor: pointer;
|
|
47
|
+
z-index: 20;
|
|
48
|
+
}
|
|
49
|
+
.stalux-back-to-top[hidden] {
|
|
50
|
+
display: none;
|
|
51
|
+
}
|
|
52
|
+
.stalux-back-to-top:focus-visible {
|
|
53
|
+
outline: 2px solid white;
|
|
54
|
+
outline-offset: 3px;
|
|
55
|
+
}
|
|
56
|
+
@media (hover: hover) {
|
|
57
|
+
.stalux-back-to-top:hover {
|
|
58
|
+
background: #353535;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
@media print {
|
|
62
|
+
.stalux-root {
|
|
63
|
+
padding-right: 0;
|
|
64
|
+
}
|
|
65
|
+
.stalux-back-to-top {
|
|
66
|
+
display: none;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
</style>
|
|
@@ -29,18 +29,20 @@ const desc = link.description ?? "";
|
|
|
29
29
|
const icon = link.icon ?? link.avatar ?? "/favicon.ico";
|
|
30
30
|
---
|
|
31
31
|
|
|
32
|
-
<a class="stalux-link-
|
|
33
|
-
<div class="stalux-link-card
|
|
34
|
-
<
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
<
|
|
44
|
-
|
|
32
|
+
<a class="stalux-link-shell a-none" href={href} target="_blank" rel="noopener">
|
|
33
|
+
<div class="stalux-link-card">
|
|
34
|
+
<div class="stalux-link-card-avatar">
|
|
35
|
+
<img
|
|
36
|
+
src={icon}
|
|
37
|
+
alt={t("links.iconAlt", { name: title })}
|
|
38
|
+
loading="lazy"
|
|
39
|
+
width="80"
|
|
40
|
+
height="80"
|
|
41
|
+
>
|
|
42
|
+
</div>
|
|
43
|
+
<div class="stalux-link-card-content">
|
|
44
|
+
<h2 class="stalux-link-card-title">{title}</h2>
|
|
45
|
+
{desc && <p class="stalux-link-card-description">{desc}</p>}
|
|
46
|
+
</div>
|
|
45
47
|
</div>
|
|
46
48
|
</a>
|
|
@@ -20,7 +20,7 @@ const timezone = siteConfig.timezone;
|
|
|
20
20
|
const description = post.data.desc || "";
|
|
21
21
|
---
|
|
22
22
|
|
|
23
|
-
<article class="block h-full text-inherit no-underline">
|
|
23
|
+
<article class="stalux-post-card-shell block h-full text-inherit no-underline">
|
|
24
24
|
<div class="stalux-post-card stalux-u-flex stalux-u-flex-col stalux-u-w-full stalux-u-min-w-0">
|
|
25
25
|
{post.data.cover && (
|
|
26
26
|
<a
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { type DefaultTreeAdapterMap, parse } from "parse5";
|
|
2
|
+
|
|
3
|
+
export interface HtmlImage {
|
|
4
|
+
start: number;
|
|
5
|
+
end: number;
|
|
6
|
+
tag: string;
|
|
7
|
+
attributes: Map<string, string>;
|
|
8
|
+
locations: Map<string, { startOffset: number; endOffset: number }>;
|
|
9
|
+
body?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Source ranges preserve scripts and code-copy attributes byte-for-byte. */
|
|
13
|
+
export function findHtmlImages(html: string): HtmlImage[] {
|
|
14
|
+
const images: HtmlImage[] = [];
|
|
15
|
+
function visit(node: DefaultTreeAdapterMap["node"], body?: number) {
|
|
16
|
+
if ("tagName" in node) {
|
|
17
|
+
if (node.tagName === "template") return;
|
|
18
|
+
const location = node.sourceCodeLocation;
|
|
19
|
+
if (
|
|
20
|
+
node.tagName === "section" &&
|
|
21
|
+
node.attrs.some((attr) => attr.name === "data-pagefind-body")
|
|
22
|
+
)
|
|
23
|
+
body = location?.startOffset;
|
|
24
|
+
if (node.tagName === "img" && location?.startTag) {
|
|
25
|
+
const { startOffset: start, endOffset: end } = location.startTag;
|
|
26
|
+
images.push({
|
|
27
|
+
start,
|
|
28
|
+
end,
|
|
29
|
+
tag: html.slice(start, end),
|
|
30
|
+
attributes: new Map(node.attrs.map((attr) => [attr.name, attr.value])),
|
|
31
|
+
locations: new Map(Object.entries(location.attrs ?? {})),
|
|
32
|
+
body,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if ("childNodes" in node) for (const child of node.childNodes) visit(child, body);
|
|
37
|
+
}
|
|
38
|
+
visit(parse(html, { sourceCodeLocationInfo: true }));
|
|
39
|
+
return images.sort((left, right) => left.start - right.start);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function updateImageTag(image: HtmlImage, updates: Record<string, string>): string {
|
|
43
|
+
let tag = image.tag;
|
|
44
|
+
const edits: Array<{ start: number; end: number; value: string }> = [];
|
|
45
|
+
const additions: string[] = [];
|
|
46
|
+
for (const [name, value] of Object.entries(updates)) {
|
|
47
|
+
const attribute = `${name}="${value.replaceAll("&", "&").replaceAll('"', """)}"`;
|
|
48
|
+
const location = image.locations.get(name);
|
|
49
|
+
if (location)
|
|
50
|
+
edits.push({
|
|
51
|
+
start: location.startOffset - image.start,
|
|
52
|
+
end: location.endOffset - image.start,
|
|
53
|
+
value: attribute,
|
|
54
|
+
});
|
|
55
|
+
else additions.push(attribute);
|
|
56
|
+
}
|
|
57
|
+
for (const edit of edits.sort((left, right) => right.start - left.start))
|
|
58
|
+
tag = tag.slice(0, edit.start) + edit.value + tag.slice(edit.end);
|
|
59
|
+
if (additions.length)
|
|
60
|
+
tag = tag.replace(
|
|
61
|
+
/\s*\/?\s*>$/,
|
|
62
|
+
(ending) => ` ${additions.join(" ")}${ending.trimStart()}`,
|
|
63
|
+
);
|
|
64
|
+
return tag;
|
|
65
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { findHtmlImages, updateImageTag } from "./html-images";
|
|
3
4
|
|
|
4
5
|
export type ImageDimensions = { width: number; height: number };
|
|
5
6
|
|
|
@@ -74,12 +75,11 @@ export function readImageDimensions(data: Buffer): ImageDimensions | undefined {
|
|
|
74
75
|
export async function addLocalImageDimensions(html: string, outputDir: string): Promise<string> {
|
|
75
76
|
const dimensionsCache = new Map<string, Promise<ImageDimensions | undefined>>();
|
|
76
77
|
const root = path.resolve(outputDir);
|
|
77
|
-
const tags =
|
|
78
|
+
const tags = findHtmlImages(html);
|
|
78
79
|
|
|
79
|
-
for (const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const src = tag.match(/\ssrc="([^"]+)"/i)?.[1];
|
|
80
|
+
for (const image of tags.reverse()) {
|
|
81
|
+
if (image.attributes.has("width") && image.attributes.has("height")) continue;
|
|
82
|
+
const src = image.attributes.get("src");
|
|
83
83
|
if (!src) continue;
|
|
84
84
|
|
|
85
85
|
let filePath: string;
|
|
@@ -102,21 +102,11 @@ export async function addLocalImageDimensions(html: string, outputDir: string):
|
|
|
102
102
|
const size = await dimensions;
|
|
103
103
|
if (!size || size.width <= 0 || size.height <= 0) continue;
|
|
104
104
|
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
: hasHeight && !hasWidth
|
|
111
|
-
? tag.replace(
|
|
112
|
-
/\sheight="[^"]*"/i,
|
|
113
|
-
` width="${size.width}" height="${size.height}"`,
|
|
114
|
-
)
|
|
115
|
-
: tag.replace(
|
|
116
|
-
/\s*\/?\s*>$/,
|
|
117
|
-
(ending) => ` width="${size.width}" height="${size.height}"${ending}`,
|
|
118
|
-
);
|
|
119
|
-
html = html.replace(tag, updated);
|
|
105
|
+
const updates: Record<string, string> = {};
|
|
106
|
+
if (!image.attributes.has("width")) updates.width = String(size.width);
|
|
107
|
+
if (!image.attributes.has("height")) updates.height = String(size.height);
|
|
108
|
+
const updated = updateImageTag(image, updates);
|
|
109
|
+
html = html.slice(0, image.start) + updated + html.slice(image.end);
|
|
120
110
|
}
|
|
121
111
|
return html;
|
|
122
112
|
}
|
package/src/layouts/Stalux.astro
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { getCollection } from "astro:content";
|
|
3
|
+
import BackToTop from "@components/stalux/layout/BackToTop.astro";
|
|
3
4
|
import Footer from "@components/stalux/layout/footer.astro";
|
|
4
5
|
import Head from "@components/stalux/layout/head.astro";
|
|
5
6
|
import { getAccentColorStyle } from "@utils/accent-color";
|
|
@@ -103,5 +104,6 @@ const routeAnimation = getRouteAnimation(routeKind);
|
|
|
103
104
|
<slot />
|
|
104
105
|
</main>
|
|
105
106
|
<Footer />
|
|
107
|
+
<BackToTop lang={htmlLang} />
|
|
106
108
|
</body>
|
|
107
109
|
</html>
|
|
@@ -16,6 +16,7 @@ import type { HastVisitorContext, MdastNode, MdastVisitorContext } from "satteri
|
|
|
16
16
|
* - 数学公式由 satteri-temml 插件直接输出 MathML,无需额外标记
|
|
17
17
|
*/
|
|
18
18
|
import { defineHastPlugin, defineMdastPlugin } from "satteri";
|
|
19
|
+
import { findHtmlImages, updateImageTag } from "../internal/html-images";
|
|
19
20
|
import { logDetail } from "../utils/diagnostics";
|
|
20
21
|
|
|
21
22
|
declare module "satteri" {
|
|
@@ -246,44 +247,25 @@ export function applyImageLoadingPolicy(
|
|
|
246
247
|
}
|
|
247
248
|
|
|
248
249
|
export function applyHtmlImageLoadingPolicy(html: string): string {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
(
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
updates.fetchpriority = "auto";
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
let result = tag;
|
|
272
|
-
for (const [name, value] of Object.entries(updates)) {
|
|
273
|
-
const pattern = new RegExp(`\\s${name}(?:="[^"]*")?`, "i");
|
|
274
|
-
if (pattern.test(result)) {
|
|
275
|
-
result = result.replace(pattern, ` ${name}="${value}"`);
|
|
276
|
-
} else {
|
|
277
|
-
result = result.replace(
|
|
278
|
-
/\s*\/?>$/,
|
|
279
|
-
(ending) => ` ${name}="${value}"${ending.trimStart()}`,
|
|
280
|
-
);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
return result;
|
|
284
|
-
});
|
|
285
|
-
},
|
|
286
|
-
);
|
|
250
|
+
const firstSeen = new Set<number>();
|
|
251
|
+
const changes = findHtmlImages(html)
|
|
252
|
+
.filter((image) => image.body !== undefined)
|
|
253
|
+
.map((image) => {
|
|
254
|
+
const body = image.body as number;
|
|
255
|
+
const first = !firstSeen.has(body);
|
|
256
|
+
firstSeen.add(body);
|
|
257
|
+
const updates: Record<string, string> = {
|
|
258
|
+
decoding: image.attributes.get("decoding") || "async",
|
|
259
|
+
loading: first ? "eager" : "lazy",
|
|
260
|
+
};
|
|
261
|
+
if (first) updates.fetchpriority = "high";
|
|
262
|
+
else if (image.attributes.get("fetchpriority") === "high")
|
|
263
|
+
updates.fetchpriority = "auto";
|
|
264
|
+
return { image, tag: updateImageTag(image, updates) };
|
|
265
|
+
});
|
|
266
|
+
for (const { image, tag } of changes.reverse())
|
|
267
|
+
html = html.slice(0, image.start) + tag + html.slice(image.end);
|
|
268
|
+
return html;
|
|
287
269
|
}
|
|
288
270
|
|
|
289
271
|
type FeatureFlagsResult = {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Bind to the current page DOM; the shared lifecycle disposes before a body swap. */
|
|
2
|
+
export function mountBackToTop() {
|
|
3
|
+
const button = document.querySelector<HTMLButtonElement>("[data-stalux-back-to-top]");
|
|
4
|
+
if (!button) return;
|
|
5
|
+
const update = () => {
|
|
6
|
+
button.hidden = window.scrollY < 240;
|
|
7
|
+
};
|
|
8
|
+
const scrollToTop = () => {
|
|
9
|
+
window.scrollTo({
|
|
10
|
+
top: 0,
|
|
11
|
+
behavior: window.matchMedia("(prefers-reduced-motion: reduce)").matches
|
|
12
|
+
? "instant"
|
|
13
|
+
: "smooth",
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
update();
|
|
17
|
+
window.addEventListener("scroll", update, { passive: true });
|
|
18
|
+
button.addEventListener("click", scrollToTop);
|
|
19
|
+
return () => {
|
|
20
|
+
window.removeEventListener("scroll", update);
|
|
21
|
+
button.removeEventListener("click", scrollToTop);
|
|
22
|
+
};
|
|
23
|
+
}
|
package/src/styles/base.css
CHANGED
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/* 下划线动画 */
|
|
51
|
-
a::after {
|
|
51
|
+
a:not(.a-none):not([class*="card"]):not(.stalux-nav-link)::after {
|
|
52
52
|
content: "";
|
|
53
53
|
position: absolute;
|
|
54
54
|
left: 0;
|
|
@@ -61,19 +61,27 @@
|
|
|
61
61
|
transition: transform 0.25s cubic-bezier(0.25, 0.9, 0.2, 1);
|
|
62
62
|
pointer-events: none;
|
|
63
63
|
}
|
|
64
|
-
|
|
65
|
-
a:
|
|
66
|
-
a:focus-visible::after,
|
|
67
|
-
a:active::after {
|
|
64
|
+
a:not(.a-none):not([class*="card"]):not(.stalux-nav-link):focus-visible::after,
|
|
65
|
+
a:not(.a-none):not([class*="card"]):not(.stalux-nav-link):active::after {
|
|
68
66
|
transform: scaleX(1);
|
|
69
67
|
}
|
|
68
|
+
@media (hover: hover) and (pointer: fine) {
|
|
69
|
+
a:not(.a-none):not([class*="card"]):not(.stalux-nav-link):hover::after {
|
|
70
|
+
transform: scaleX(1);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
70
73
|
|
|
71
|
-
a:hover,
|
|
72
74
|
a:active,
|
|
73
75
|
a:focus-visible {
|
|
74
76
|
text-decoration: none;
|
|
75
77
|
/* 保持颜色不变,避免 hover 时颜色跳变 */
|
|
76
78
|
}
|
|
79
|
+
@media (hover: hover) and (pointer: fine) {
|
|
80
|
+
a:hover {
|
|
81
|
+
text-decoration: none;
|
|
82
|
+
/* 保持颜色不变,避免 hover 时颜色跳变 */
|
|
83
|
+
}
|
|
84
|
+
}
|
|
77
85
|
|
|
78
86
|
a:focus {
|
|
79
87
|
outline: none;
|
|
@@ -240,9 +248,10 @@
|
|
|
240
248
|
border-radius: 4px;
|
|
241
249
|
transition: background 0.3s ease-out;
|
|
242
250
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
251
|
+
@media (hover: hover) and (pointer: fine) {
|
|
252
|
+
::-webkit-scrollbar-thumb:hover {
|
|
253
|
+
background: #5a5a5a;
|
|
254
|
+
}
|
|
246
255
|
}
|
|
247
256
|
|
|
248
257
|
::-webkit-scrollbar-corner {
|
|
@@ -285,3 +294,81 @@
|
|
|
285
294
|
-webkit-backdrop-filter: blur(16px);
|
|
286
295
|
}
|
|
287
296
|
}
|
|
297
|
+
|
|
298
|
+
@layer components {
|
|
299
|
+
.stalux-main,
|
|
300
|
+
.stalux-post-page,
|
|
301
|
+
.stalux-post-layout > *,
|
|
302
|
+
.stalux-link-card-content,
|
|
303
|
+
.stalux-word-list-item,
|
|
304
|
+
.stalux-word-card {
|
|
305
|
+
min-width: 0;
|
|
306
|
+
max-width: 100%;
|
|
307
|
+
box-sizing: border-box;
|
|
308
|
+
}
|
|
309
|
+
.stalux-main {
|
|
310
|
+
overflow-wrap: anywhere;
|
|
311
|
+
}
|
|
312
|
+
.stalux-link-shell {
|
|
313
|
+
display: block;
|
|
314
|
+
min-width: 0;
|
|
315
|
+
max-width: 100%;
|
|
316
|
+
}
|
|
317
|
+
.stalux-link-card {
|
|
318
|
+
box-sizing: border-box;
|
|
319
|
+
max-width: 100%;
|
|
320
|
+
width: 100%;
|
|
321
|
+
}
|
|
322
|
+
.stalux-category-shell {
|
|
323
|
+
min-width: 0;
|
|
324
|
+
width: 100%;
|
|
325
|
+
}
|
|
326
|
+
.stalux-author-figure > a {
|
|
327
|
+
display: inline-block;
|
|
328
|
+
flex-shrink: 0;
|
|
329
|
+
}
|
|
330
|
+
.stalux-author-figure img {
|
|
331
|
+
display: block;
|
|
332
|
+
}
|
|
333
|
+
.stalux-post-card-shell,
|
|
334
|
+
.stalux-category-shell,
|
|
335
|
+
.stalux-link-shell {
|
|
336
|
+
position: relative;
|
|
337
|
+
}
|
|
338
|
+
.stalux-post-card,
|
|
339
|
+
.stalux-category-card,
|
|
340
|
+
.stalux-link-card,
|
|
341
|
+
.stalux-word-card {
|
|
342
|
+
transition:
|
|
343
|
+
transform 200ms ease,
|
|
344
|
+
background-color 200ms ease,
|
|
345
|
+
border-color 200ms ease,
|
|
346
|
+
box-shadow 200ms ease;
|
|
347
|
+
}
|
|
348
|
+
:is(.stalux-post-card-shell, .stalux-category-shell):focus-within > *,
|
|
349
|
+
.stalux-link-shell:focus-visible > * {
|
|
350
|
+
box-shadow: var(--card-box-shadow-hover);
|
|
351
|
+
}
|
|
352
|
+
:is(button, summary, [role="button"]):focus-visible {
|
|
353
|
+
outline: 2px solid var(--stalux-accent-color);
|
|
354
|
+
outline-offset: 3px;
|
|
355
|
+
}
|
|
356
|
+
@media (prefers-reduced-motion: reduce) {
|
|
357
|
+
.stalux-main *,
|
|
358
|
+
.stalux-main *::before,
|
|
359
|
+
.stalux-main *::after {
|
|
360
|
+
animation: none !important;
|
|
361
|
+
transition-duration: 0ms !important;
|
|
362
|
+
}
|
|
363
|
+
.stalux-post-card,
|
|
364
|
+
.stalux-category-card,
|
|
365
|
+
.stalux-link-card,
|
|
366
|
+
.stalux-link-card-content,
|
|
367
|
+
.stalux-card-post-cover img,
|
|
368
|
+
.stalux-category-icon,
|
|
369
|
+
.stalux-author-figure img,
|
|
370
|
+
.stalux-link-card-avatar img {
|
|
371
|
+
transform: none !important;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|