diff --git a/public/favicon.svg b/public/favicon.svg
new file mode 100644
index 0000000..8491f54
--- /dev/null
+++ b/public/favicon.svg
@@ -0,0 +1,14 @@
+
diff --git a/src/components/contributors.tsx b/src/components/contributors.tsx
index aeb674e..606fdaf 100644
--- a/src/components/contributors.tsx
+++ b/src/components/contributors.tsx
@@ -29,6 +29,9 @@ export function Contributors() {
感谢这些为 Mix Space 开源社区做出贡献的开发者
+
+ 共 {humans.length + bots.length} 位贡献者
+
{/* Human contributors */}
diff --git a/src/components/docs.tsx b/src/components/docs.tsx
index bd4a486..7d5dec0 100644
--- a/src/components/docs.tsx
+++ b/src/components/docs.tsx
@@ -15,7 +15,7 @@ import {
ViewOptionsPopover,
} from 'fumadocs-ui/layouts/docs/page';
import {
- Rocket,
+ Compass,
Server,
Settings,
PaintRoller,
@@ -27,7 +27,7 @@ import { baseOptions } from '@/lib/layout.shared';
import { RootProvider } from '@/components/root-provider';
const iconMap: Record = {
- '新手入门': ,
+ '新手入门': ,
'部署': ,
'配置': ,
'前端主题': ,
diff --git a/src/components/features.tsx b/src/components/features.tsx
index 87d27f6..8429153 100644
--- a/src/components/features.tsx
+++ b/src/components/features.tsx
@@ -5,8 +5,8 @@ import {
Bot,
Cpu,
FileEdit,
+ Palette,
Settings,
- Sparkles,
} from 'lucide-react';
const p = 'text-sm leading-relaxed text-neutral-600 dark:text-neutral-400';
@@ -57,8 +57,8 @@ export function Features() {
后端 API 和前端界面分开部署,可以各自升级、各自扩容。
-
-
+
+
提供 Yohaku、Shiro 等官方主题,界面简洁现代,支持深色模式。
diff --git a/src/components/footer.tsx b/src/components/footer.tsx
index 2188501..27abe36 100644
--- a/src/components/footer.tsx
+++ b/src/components/footer.tsx
@@ -27,7 +27,19 @@ export function Footer() {
>
GitHub
- © 2021-{new Date().getFullYear()} Mix Space Team
+
+ 隐私政策
+
+
+ 服务条款
+
+ © 2021-{new Date().getFullYear()} Mix Space Team
diff --git a/src/components/preview-images.tsx b/src/components/preview-images.tsx
index d41ea09..401f2ca 100644
--- a/src/components/preview-images.tsx
+++ b/src/components/preview-images.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useRef, useState } from 'react';
+import { useCallback, useEffect, useRef, useState } from 'react';
import { Image } from '@/components/image';
import type { ResponsiveImage } from '@/lib/images';
import { cn } from '@/lib/cn';
@@ -9,18 +9,32 @@ interface PreviewItem extends ResponsiveImage {
export function PreviewImages({ previews }: { previews: PreviewItem[] }) {
const [active, setActive] = useState(0);
+ const [paused, setPaused] = useState(false);
+ const [reducedMotion, setReducedMotion] = useState(false);
const btnRefs = useRef<(HTMLButtonElement | null)[]>([]);
+ const tablistRef = useRef(null);
const [indicator, setIndicator] = useState({ left: 0, width: 0 });
- // Auto-rotate every 4s
+ // Auto-rotate every 4s;reduced-motion 用户不自动播放,悬停时暂停
useEffect(() => {
+ if (paused || reducedMotion) return;
const timer = setInterval(() => {
setActive((prev) => (prev + 1) % previews.length);
}, 4000);
return () => clearInterval(timer);
- }, []);
+ }, [paused, reducedMotion, previews.length]);
+ // prefers-reduced-motion:响应式监听系统设置变化
useEffect(() => {
+ const mq = window.matchMedia('(prefers-reduced-motion: reduce)');
+ const onChange = () => setReducedMotion(mq.matches);
+ onChange();
+ mq.addEventListener('change', onChange);
+ return () => mq.removeEventListener('change', onChange);
+ }, []);
+
+ // 指示条位置以 tab 相对容器的偏移计算,切换或尺寸变化时重算
+ const updateIndicator = useCallback(() => {
const btn = btnRefs.current[active];
if (!btn) return;
const parent = btn.parentElement;
@@ -33,8 +47,25 @@ export function PreviewImages({ previews }: { previews: PreviewItem[] }) {
});
}, [active]);
+ useEffect(() => {
+ updateIndicator();
+ }, [updateIndicator]);
+
+ // 容器尺寸变化(窗口 resize、字体加载等)时重算指示条位置
+ useEffect(() => {
+ const el = tablistRef.current;
+ if (!el) return;
+ const ro = new ResizeObserver(updateIndicator);
+ ro.observe(el);
+ return () => ro.disconnect();
+ }, [updateIndicator]);
+
return (
-
+ setPaused(true)}
+ onMouseLeave={() => setPaused(false)}
+ >
{/* 环境辉光:品牌色弥散阴影;内收一圈,避免模糊溢出到卡片外被误认为图片漏底 */}
(
-
+
{ btnRefs.current[i] = el; }}
+ role="tab"
+ id={`preview-tab-${i}`}
+ aria-selected={active === i}
+ aria-controls={`preview-panel-${i}`}
onClick={() => setActive(i)}
className={cn(
'relative z-10 h-8 px-3 sm:px-5 text-[13px] sm:text-sm font-medium rounded-full transition-colors whitespace-nowrap',
diff --git a/src/components/theme-card.tsx b/src/components/theme-card.tsx
index 404bac0..bd4f3f3 100644
--- a/src/components/theme-card.tsx
+++ b/src/components/theme-card.tsx
@@ -101,6 +101,7 @@ export function ThemeCard({
width={24}
height={24}
className="rounded-full bg-neutral-200 dark:bg-neutral-800"
+ loading="lazy"
/>
{author}
diff --git a/src/layouts/base.astro b/src/layouts/base.astro
index f6345e0..6fd9155 100644
--- a/src/layouts/base.astro
+++ b/src/layouts/base.astro
@@ -47,11 +47,15 @@ const ogImage = new URL(image ?? '/og.png', Astro.site).href;
{pageTitle}
+
+
{markdownUrl && }
-
+
+
+
-
+
+
+ 跳到主要内容
+
+
+
+
+