@zmzai/theme 0.6.1 → 0.8.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/package.json +7 -9
- package/src/brand/logo/favicon.png +0 -0
- package/src/components/app-shell/AppShell.tsx +211 -0
- package/src/components/app-shell/CommandPalette.tsx +126 -0
- package/src/components/app-shell/index.ts +4 -0
- package/src/components/artifact-card/artifact-card.css +3 -3
- package/src/components/badge/Badge.tsx +1 -1
- package/src/components/button/button.styles.ts +2 -1
- package/src/components/index.ts +1 -0
- package/src/components/landing/landing.css +4 -4
- package/src/components/model-selector/model-selector.css +3 -3
- package/src/components/navbar/Navbar.tsx +2 -2
- package/src/components/reasoning/reasoning.css +1 -1
- package/src/components/sticky-banner/StickyBanner.tsx +1 -1
- package/src/components/subtask/subtask.css +2 -2
- package/src/components/todo-checklist/todo-checklist.css +3 -3
- package/src/components/tool-card/tool-card.css +3 -3
- package/src/components/vanish-input/VanishInput.tsx +1 -1
- package/src/tokens/theme.css +118 -53
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zmzai/theme",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "zmzai 全品牌设计系统 — Radix UI + framer-motion + Tailwind v4 + MiSans",
|
|
@@ -60,12 +60,6 @@
|
|
|
60
60
|
"README.md",
|
|
61
61
|
"LICENSE"
|
|
62
62
|
],
|
|
63
|
-
"scripts": {
|
|
64
|
-
"build": "tsc -p tsconfig.json --noEmit",
|
|
65
|
-
"typecheck": "tsc --noEmit",
|
|
66
|
-
"playground": "vite playground --port 5174",
|
|
67
|
-
"prepublishOnly": "pnpm typecheck"
|
|
68
|
-
},
|
|
69
63
|
"peerDependencies": {
|
|
70
64
|
"react": "^19.0.0",
|
|
71
65
|
"react-dom": "^19.0.0",
|
|
@@ -105,5 +99,9 @@
|
|
|
105
99
|
"remark-gfm": "^4.0.1",
|
|
106
100
|
"tailwind-merge": "^2.5.0"
|
|
107
101
|
},
|
|
108
|
-
"
|
|
109
|
-
|
|
102
|
+
"scripts": {
|
|
103
|
+
"build": "tsc -p tsconfig.json --noEmit",
|
|
104
|
+
"typecheck": "tsc --noEmit",
|
|
105
|
+
"playground": "vite playground --port 5174"
|
|
106
|
+
}
|
|
107
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useMemo, useState } from "react";
|
|
4
|
+
import type { ComponentType, ReactNode } from "react";
|
|
5
|
+
|
|
6
|
+
import { Badge } from "../badge/Badge";
|
|
7
|
+
import { Icon, type IconName } from "../icon/Icon";
|
|
8
|
+
import { CommandPalette, type AppPaletteItem } from "./CommandPalette";
|
|
9
|
+
|
|
10
|
+
export type AppNavItem = { label: string; href: string; icon: IconName; keywords?: string };
|
|
11
|
+
export type AppNavSection = { label: string; items: AppNavItem[] };
|
|
12
|
+
export type { AppPaletteItem } from "./CommandPalette";
|
|
13
|
+
|
|
14
|
+
/** 消费端注入的链接组件(Next.js 传 next/link 以保持 SPA 导航;缺省用 <a>)。 */
|
|
15
|
+
export type AppShellLink = ComponentType<{
|
|
16
|
+
href: string;
|
|
17
|
+
className?: string;
|
|
18
|
+
children?: ReactNode;
|
|
19
|
+
onClick?: () => void;
|
|
20
|
+
"aria-current"?: "page" | undefined;
|
|
21
|
+
}>;
|
|
22
|
+
|
|
23
|
+
export function appShellIsActive(pathname: string, href: string): boolean {
|
|
24
|
+
return pathname === href || pathname.startsWith(`${href}/`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** 无 SPA 需求时的缺省链接:普通 <a>。 */
|
|
28
|
+
function DefaultAnchor({ href, className, children, onClick, "aria-current": ariaCurrent }: { href: string; className?: string; children?: ReactNode; onClick?: () => void; "aria-current"?: "page" }) {
|
|
29
|
+
return (
|
|
30
|
+
<a href={href} className={className} onClick={onClick} aria-current={ariaCurrent}>
|
|
31
|
+
{children}
|
|
32
|
+
</a>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function SidebarSections({
|
|
37
|
+
sections,
|
|
38
|
+
pathname,
|
|
39
|
+
link: Link,
|
|
40
|
+
onNavigate,
|
|
41
|
+
}: {
|
|
42
|
+
sections: AppNavSection[];
|
|
43
|
+
pathname: string;
|
|
44
|
+
link: AppShellLink;
|
|
45
|
+
onNavigate?: () => void;
|
|
46
|
+
}) {
|
|
47
|
+
return (
|
|
48
|
+
<>
|
|
49
|
+
{sections.map((section) => (
|
|
50
|
+
<div key={section.label} className="flex flex-col gap-0.5">
|
|
51
|
+
<p className="px-2 pb-1 font-mono text-[10px] uppercase tracking-wider text-ink-3">{section.label}</p>
|
|
52
|
+
{section.items.map((item) => {
|
|
53
|
+
const active = appShellIsActive(pathname, item.href);
|
|
54
|
+
return (
|
|
55
|
+
<Link
|
|
56
|
+
key={item.href}
|
|
57
|
+
href={item.href}
|
|
58
|
+
onClick={onNavigate}
|
|
59
|
+
aria-current={active ? "page" : undefined}
|
|
60
|
+
className={`flex items-center gap-2 rounded-md px-2 py-1.5 text-[13px] transition-colors ${
|
|
61
|
+
active ? "bg-surface font-medium text-accent-readable" : "text-muted hover:bg-surface hover:text-accent"
|
|
62
|
+
}`}
|
|
63
|
+
>
|
|
64
|
+
<Icon name={item.icon} size={13} />
|
|
65
|
+
{item.label}
|
|
66
|
+
</Link>
|
|
67
|
+
);
|
|
68
|
+
})}
|
|
69
|
+
</div>
|
|
70
|
+
))}
|
|
71
|
+
</>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 全站统一应用外壳(OpenRouter 式交互内核):
|
|
77
|
+
* 全高左侧 sidebar(分组导航)+ 轻顶栏(标题 + ⌘K)+ 侧栏底部账户块。
|
|
78
|
+
* 站点无关:导航分组、品牌区、账户块、链接组件均由调用方注入;
|
|
79
|
+
* relay 的 RelayShell / sandbox 的 SandboxShell 是它的薄封装。
|
|
80
|
+
*/
|
|
81
|
+
export function AppShell({
|
|
82
|
+
brand,
|
|
83
|
+
sections,
|
|
84
|
+
pathname,
|
|
85
|
+
link,
|
|
86
|
+
account = null,
|
|
87
|
+
headerExtras = null,
|
|
88
|
+
paletteItems,
|
|
89
|
+
children,
|
|
90
|
+
}: {
|
|
91
|
+
/** 品牌区:侧栏顶部与顶栏兜底标题 */
|
|
92
|
+
brand: { label: string; suffix?: string; href?: string };
|
|
93
|
+
/** 侧栏分组导航 */
|
|
94
|
+
sections: AppNavSection[];
|
|
95
|
+
/** 当前路径(消费端传 usePathname() ?? "/";theme 不依赖 next) */
|
|
96
|
+
pathname: string;
|
|
97
|
+
/** 链接组件(Next.js 站传 next/link) */
|
|
98
|
+
link?: AppShellLink;
|
|
99
|
+
/** 侧栏底部账户块(登录按钮 / 用户信息) */
|
|
100
|
+
account?: ReactNode;
|
|
101
|
+
/** 顶栏右侧扩展(余额、状态等) */
|
|
102
|
+
headerExtras?: ReactNode;
|
|
103
|
+
/** ⌘K 命令面板条目;为空时顶栏不显示搜索按钮 */
|
|
104
|
+
paletteItems?: AppPaletteItem[];
|
|
105
|
+
children: ReactNode;
|
|
106
|
+
}) {
|
|
107
|
+
const Link = (link ?? DefaultAnchor) as AppShellLink;
|
|
108
|
+
const hasPalette = Boolean(paletteItems?.length);
|
|
109
|
+
const [drawerOpen, setDrawerOpen] = useState(false);
|
|
110
|
+
const [paletteOpen, setPaletteOpen] = useState(false);
|
|
111
|
+
|
|
112
|
+
// 顶栏标题:当前激活项的 label(取最长匹配,/admin/models 优先于 /admin)
|
|
113
|
+
const currentLabel = useMemo(() => {
|
|
114
|
+
const matches = sections.flatMap((s) => s.items).filter((item) => appShellIsActive(pathname, item.href));
|
|
115
|
+
if (!matches.length) return brand.label;
|
|
116
|
+
return matches.sort((a, b) => b.href.length - a.href.length)[0].label;
|
|
117
|
+
}, [pathname, sections, brand.label]);
|
|
118
|
+
|
|
119
|
+
// ⌘K / Ctrl+K 全局快捷键
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
if (!hasPalette) return;
|
|
122
|
+
const onKey = (event: KeyboardEvent) => {
|
|
123
|
+
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") {
|
|
124
|
+
event.preventDefault();
|
|
125
|
+
setPaletteOpen((open) => !open);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
window.addEventListener("keydown", onKey);
|
|
129
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
130
|
+
}, [hasPalette]);
|
|
131
|
+
|
|
132
|
+
const brandBlock = (
|
|
133
|
+
<Link href={brand.href ?? "/"} className="flex items-baseline gap-1.5 px-2">
|
|
134
|
+
<span className="text-sm font-semibold tracking-tight">{brand.label}</span>
|
|
135
|
+
{brand.suffix ? <span className="font-mono text-[11px] text-ink-3">{brand.suffix}</span> : null}
|
|
136
|
+
</Link>
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<div className="min-h-dvh bg-bg">
|
|
141
|
+
{hasPalette ? <CommandPalette open={paletteOpen} onClose={() => setPaletteOpen(false)} items={paletteItems!} /> : null}
|
|
142
|
+
|
|
143
|
+
{/* 移动端抽屉 */}
|
|
144
|
+
{drawerOpen ? (
|
|
145
|
+
<div className="fixed inset-0 z-40 lg:hidden" role="presentation">
|
|
146
|
+
<div className="absolute inset-0 bg-black/30" onClick={() => setDrawerOpen(false)} />
|
|
147
|
+
<nav className="absolute inset-y-0 left-0 flex w-64 flex-col gap-5 overflow-y-auto border-r border-line bg-bg px-3 py-4">
|
|
148
|
+
{brandBlock}
|
|
149
|
+
<SidebarSections sections={sections} pathname={pathname} link={Link} onNavigate={() => setDrawerOpen(false)} />
|
|
150
|
+
{account}
|
|
151
|
+
</nav>
|
|
152
|
+
</div>
|
|
153
|
+
) : null}
|
|
154
|
+
|
|
155
|
+
<div className="lg:grid lg:grid-cols-[13.5rem_minmax(0,1fr)]">
|
|
156
|
+
{/* 桌面固定侧栏 */}
|
|
157
|
+
<aside className="sticky top-0 hidden h-dvh flex-col gap-5 overflow-y-auto border-r border-line px-3 py-4 lg:flex">
|
|
158
|
+
{brandBlock}
|
|
159
|
+
<SidebarSections sections={sections} pathname={pathname} link={Link} />
|
|
160
|
+
{account}
|
|
161
|
+
</aside>
|
|
162
|
+
|
|
163
|
+
<div className="flex min-h-dvh min-w-0 flex-col">
|
|
164
|
+
{/* 轻顶栏 */}
|
|
165
|
+
<header className="sticky top-0 z-30 flex items-center gap-3 border-b border-line bg-bg/95 px-4 py-2.5 backdrop-blur lg:px-8">
|
|
166
|
+
<button
|
|
167
|
+
type="button"
|
|
168
|
+
aria-label="打开导航"
|
|
169
|
+
onClick={() => setDrawerOpen(true)}
|
|
170
|
+
className="rounded-md p-1 text-muted hover:bg-surface hover:text-accent lg:hidden"
|
|
171
|
+
>
|
|
172
|
+
<Icon name="menu" size={16} />
|
|
173
|
+
</button>
|
|
174
|
+
<span className="text-sm font-medium text-ink-2">{currentLabel}</span>
|
|
175
|
+
<div className="ml-auto flex items-center gap-3">
|
|
176
|
+
{headerExtras}
|
|
177
|
+
{hasPalette ? (
|
|
178
|
+
<button
|
|
179
|
+
type="button"
|
|
180
|
+
onClick={() => setPaletteOpen(true)}
|
|
181
|
+
className="flex items-center gap-2 rounded-md border border-line px-2.5 py-1 font-mono text-[11px] text-muted transition-colors hover:border-accent hover:text-accent"
|
|
182
|
+
aria-label="打开命令面板"
|
|
183
|
+
>
|
|
184
|
+
<Icon name="search" size={12} />
|
|
185
|
+
<span className="hidden sm:inline">搜索</span>
|
|
186
|
+
<kbd className="hidden sm:inline">⌘K</kbd>
|
|
187
|
+
</button>
|
|
188
|
+
) : null}
|
|
189
|
+
</div>
|
|
190
|
+
</header>
|
|
191
|
+
|
|
192
|
+
{/* 内容区 */}
|
|
193
|
+
<main className="page-shell flex-1 py-8">{children}</main>
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** 侧栏账户块通用样式辅助:登录后的用户信息行。 */
|
|
201
|
+
export function AppShellAccountRow({ name, badge, children }: { name: string; badge?: string; children?: ReactNode }) {
|
|
202
|
+
return (
|
|
203
|
+
<div className="mt-auto flex flex-col gap-2 border-t border-line px-2 pt-3">
|
|
204
|
+
<div className="flex items-center justify-between gap-2">
|
|
205
|
+
<span className="truncate font-mono text-xs text-ink-2">{name}</span>
|
|
206
|
+
{badge ? <Badge variant="outline" size="sm">{badge}</Badge> : null}
|
|
207
|
+
</div>
|
|
208
|
+
{children ? <div className="flex items-center justify-between gap-2 font-mono text-[11px]">{children}</div> : null}
|
|
209
|
+
</div>
|
|
210
|
+
);
|
|
211
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
|
|
5
|
+
import { Icon, type IconName } from "../icon/Icon";
|
|
6
|
+
|
|
7
|
+
export type AppPaletteItem = {
|
|
8
|
+
label: string;
|
|
9
|
+
hint?: string;
|
|
10
|
+
group: string;
|
|
11
|
+
icon?: IconName;
|
|
12
|
+
keywords?: string;
|
|
13
|
+
run: () => void;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* ⌘K / Ctrl+K 命令面板:纯手写 overlay(无新依赖)。
|
|
18
|
+
* 键盘:↑↓ 选择、Enter 确认、Esc 关闭;输入即时过滤(label/hint/keywords 子串匹配)。
|
|
19
|
+
*/
|
|
20
|
+
export function CommandPalette({ open, onClose, items, placeholder = "搜索页面或执行动作…" }: { open: boolean; onClose: () => void; items: AppPaletteItem[]; placeholder?: string }) {
|
|
21
|
+
const [query, setQuery] = useState("");
|
|
22
|
+
const [cursor, setCursor] = useState(0);
|
|
23
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
24
|
+
const listRef = useRef<HTMLDivElement>(null);
|
|
25
|
+
|
|
26
|
+
const filtered = useMemo(() => {
|
|
27
|
+
const q = query.trim().toLowerCase();
|
|
28
|
+
if (!q) return items;
|
|
29
|
+
return items.filter((item) => `${item.label} ${item.hint ?? ""} ${item.keywords ?? ""}`.toLowerCase().includes(q));
|
|
30
|
+
}, [items, query]);
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (open) {
|
|
34
|
+
setQuery("");
|
|
35
|
+
setCursor(0);
|
|
36
|
+
// 等一帧让 overlay 挂载后再聚焦,避免被浏览器焦点还原打断
|
|
37
|
+
requestAnimationFrame(() => inputRef.current?.focus());
|
|
38
|
+
}
|
|
39
|
+
}, [open]);
|
|
40
|
+
|
|
41
|
+
useEffect(() => setCursor(0), [query]);
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (!open) return;
|
|
45
|
+
const onKey = (event: KeyboardEvent) => {
|
|
46
|
+
if (event.key === "Escape") {
|
|
47
|
+
event.preventDefault();
|
|
48
|
+
onClose();
|
|
49
|
+
} else if (event.key === "ArrowDown") {
|
|
50
|
+
event.preventDefault();
|
|
51
|
+
setCursor((c) => Math.min(c + 1, filtered.length - 1));
|
|
52
|
+
} else if (event.key === "ArrowUp") {
|
|
53
|
+
event.preventDefault();
|
|
54
|
+
setCursor((c) => Math.max(c - 1, 0));
|
|
55
|
+
} else if (event.key === "Enter") {
|
|
56
|
+
event.preventDefault();
|
|
57
|
+
const item = filtered[cursor];
|
|
58
|
+
if (item) {
|
|
59
|
+
onClose();
|
|
60
|
+
item.run();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
window.addEventListener("keydown", onKey);
|
|
65
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
66
|
+
}, [open, filtered, cursor, onClose]);
|
|
67
|
+
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
listRef.current?.querySelector<HTMLElement>(`[data-index="${cursor}"]`)?.scrollIntoView({ block: "nearest" });
|
|
70
|
+
}, [cursor]);
|
|
71
|
+
|
|
72
|
+
if (!open) return null;
|
|
73
|
+
|
|
74
|
+
const execute = (index: number) => {
|
|
75
|
+
const item = filtered[index];
|
|
76
|
+
if (!item) return;
|
|
77
|
+
onClose();
|
|
78
|
+
item.run();
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<div className="fixed inset-0 z-50 flex items-start justify-center bg-black/30 px-4 pt-[12vh]" onClick={onClose} role="presentation">
|
|
83
|
+
<div
|
|
84
|
+
className="w-full max-w-xl overflow-hidden rounded-xl border border-line bg-bg shadow-lg"
|
|
85
|
+
onClick={(event) => event.stopPropagation()}
|
|
86
|
+
role="dialog"
|
|
87
|
+
aria-modal="true"
|
|
88
|
+
aria-label="命令面板"
|
|
89
|
+
>
|
|
90
|
+
<div className="flex items-center gap-2 border-b border-line px-4 py-3">
|
|
91
|
+
<Icon name="search" size={14} className="text-muted" />
|
|
92
|
+
<input
|
|
93
|
+
ref={inputRef}
|
|
94
|
+
value={query}
|
|
95
|
+
onChange={(event) => setQuery(event.target.value)}
|
|
96
|
+
placeholder={placeholder}
|
|
97
|
+
className="w-full bg-transparent text-sm outline-none placeholder:text-ink-3"
|
|
98
|
+
/>
|
|
99
|
+
<kbd className="rounded border border-line px-1.5 py-0.5 font-mono text-[10px] text-ink-3">esc</kbd>
|
|
100
|
+
</div>
|
|
101
|
+
<div ref={listRef} className="max-h-[50vh] overflow-y-auto py-1.5">
|
|
102
|
+
{filtered.length === 0 ? (
|
|
103
|
+
<p className="px-4 py-6 text-center font-mono text-xs text-ink-3">没有匹配结果</p>
|
|
104
|
+
) : (
|
|
105
|
+
filtered.map((item, index) => (
|
|
106
|
+
<button
|
|
107
|
+
key={`${item.group}-${item.label}`}
|
|
108
|
+
type="button"
|
|
109
|
+
data-index={index}
|
|
110
|
+
onClick={() => execute(index)}
|
|
111
|
+
onMouseEnter={() => setCursor(index)}
|
|
112
|
+
className={`flex w-full items-center gap-2.5 px-4 py-2 text-left text-sm transition-colors ${
|
|
113
|
+
index === cursor ? "bg-surface text-accent-readable" : "text-ink-2"
|
|
114
|
+
}`}
|
|
115
|
+
>
|
|
116
|
+
{item.icon ? <Icon name={item.icon} size={13} className="shrink-0 text-muted" /> : null}
|
|
117
|
+
<span className="min-w-0 flex-1 truncate">{item.label}</span>
|
|
118
|
+
{item.hint ? <span className="shrink-0 font-mono text-[11px] text-ink-3">{item.hint}</span> : null}
|
|
119
|
+
</button>
|
|
120
|
+
))
|
|
121
|
+
)}
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/* ArtifactCard — 产物卡。token 依赖消费端 @theme。 */
|
|
2
2
|
.zmz-artifact-card { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 0.15rem 0.6rem; align-items: center; text-align: left; border: 1px solid var(--color-line); border-radius: var(--radius); background: var(--color-bg); padding: 0.6rem 0.75rem; cursor: pointer; font: inherit; color: inherit; transition: border-color 0.15s var(--ease-out), box-shadow 0.15s var(--ease-out); }
|
|
3
|
-
.zmz-artifact-card:hover { border-color: var(--color-
|
|
3
|
+
.zmz-artifact-card:hover { border-color: var(--color-ink-3); box-shadow: var(--shadow-md); }
|
|
4
4
|
/* 显式 grid 定位:name/meta 固定在左列(1fr),actions 固定右列跨两行。
|
|
5
5
|
自动放置曾把超长 MIME 文本(无空格、无断行)排进右列 auto 轨道,
|
|
6
6
|
撑爆布局把名字列压成 0 → 文件名竖排逐字折行。 */
|
|
7
7
|
.zmz-artifact-name { grid-column: 1; grid-row: 1; font-family: var(--font-mono); font-size: 0.75rem; overflow-wrap: anywhere; }
|
|
8
8
|
.zmz-artifact-meta { grid-column: 1; grid-row: 2; font-size: 0.6875rem; color: var(--color-muted); overflow-wrap: anywhere; }
|
|
9
9
|
.zmz-artifact-actions { grid-column: 2; grid-row: 1 / span 2; display: flex; align-items: center; gap: 0.5rem; }
|
|
10
|
-
.zmz-artifact-preview-hint { font-family: var(--font-mono); font-size: 0.625rem; letter-spacing: 0.07em; text-transform: uppercase; color: var(--color-
|
|
10
|
+
.zmz-artifact-preview-hint { font-family: var(--font-mono); font-size: 0.625rem; letter-spacing: 0.07em; text-transform: uppercase; color: var(--color-ink-3); }
|
|
11
11
|
.zmz-artifact-download { display: inline-flex; color: var(--color-muted); }
|
|
12
|
-
.zmz-artifact-download:hover { color: var(--color-
|
|
12
|
+
.zmz-artifact-download:hover { color: var(--color-ink); }
|
|
@@ -9,7 +9,7 @@ export const badgeVariants = cva(
|
|
|
9
9
|
{
|
|
10
10
|
variants: {
|
|
11
11
|
variant: {
|
|
12
|
-
solid: "bg-ink text-
|
|
12
|
+
solid: "bg-ink text-bg",
|
|
13
13
|
outline: "border border-line text-ink-2",
|
|
14
14
|
success: "border border-success/30 bg-success/10 text-success",
|
|
15
15
|
warning: "border border-warning/30 bg-warning/10 text-warning",
|
|
@@ -26,7 +26,8 @@ export const buttonVariants = cva(
|
|
|
26
26
|
variants: {
|
|
27
27
|
variant: {
|
|
28
28
|
primary:
|
|
29
|
-
|
|
29
|
+
// 反色按钮:bg-ink 上用 bg 色作文字(浅色=白、深色=黑),硬编码 text-white 在深色主题下不可读
|
|
30
|
+
"bg-ink text-bg hover:shadow-md active:scale-[0.98]",
|
|
30
31
|
secondary:
|
|
31
32
|
"border border-line text-ink hover:border-ink hover:bg-surface-2 active:scale-[0.98]",
|
|
32
33
|
ghost:
|
package/src/components/index.ts
CHANGED
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
.btn-solid:hover {
|
|
43
|
-
background:
|
|
44
|
-
border-color:
|
|
43
|
+
background: var(--color-ink);
|
|
44
|
+
border-color: var(--color-ink);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
/* Arrow slide on hover(按钮/链接内的 → 箭头位移) */
|
|
@@ -125,7 +125,7 @@
|
|
|
125
125
|
|
|
126
126
|
.product-card:hover {
|
|
127
127
|
transform: translateY(-4px);
|
|
128
|
-
box-shadow:
|
|
128
|
+
box-shadow: var(--shadow-lg);
|
|
129
129
|
border-color: var(--color-muted);
|
|
130
130
|
}
|
|
131
131
|
|
|
@@ -195,7 +195,7 @@
|
|
|
195
195
|
border: 1px solid var(--color-line);
|
|
196
196
|
border-radius: var(--radius-xl);
|
|
197
197
|
overflow: hidden;
|
|
198
|
-
box-shadow:
|
|
198
|
+
box-shadow: var(--shadow-xl);
|
|
199
199
|
position: relative;
|
|
200
200
|
}
|
|
201
201
|
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
border-radius: var(--radius-sm, 2px);
|
|
49
49
|
border: 1px solid var(--color-line, oklch(0.85 0.02 80));
|
|
50
50
|
background: var(--color-bg, oklch(0.97 0.01 80));
|
|
51
|
-
box-shadow:
|
|
51
|
+
box-shadow: var(--shadow-xl);
|
|
52
52
|
padding: 0.25rem;
|
|
53
53
|
}
|
|
54
54
|
|
|
@@ -146,7 +146,7 @@
|
|
|
146
146
|
.ms-featured-check {
|
|
147
147
|
flex-shrink: 0;
|
|
148
148
|
margin-top: 0.25rem;
|
|
149
|
-
color: var(--color-accent
|
|
149
|
+
color: var(--color-accent);
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
/* Separator */
|
|
@@ -257,7 +257,7 @@
|
|
|
257
257
|
|
|
258
258
|
.ms-model-check {
|
|
259
259
|
flex-shrink: 0;
|
|
260
|
-
color: var(--color-accent
|
|
260
|
+
color: var(--color-accent);
|
|
261
261
|
}
|
|
262
262
|
|
|
263
263
|
.ms-model-meta {
|
|
@@ -9,7 +9,7 @@ import { Icon } from "../icon/Icon";
|
|
|
9
9
|
/**
|
|
10
10
|
* navItemClass — 导航项统一样式(全域导航一致)。
|
|
11
11
|
*
|
|
12
|
-
* active 时深墨 pill 高亮(bg-ink text-
|
|
12
|
+
* active 时深墨 pill 高亮(bg-ink text-bg,深浅主题自适应),其余 hover 浮现。
|
|
13
13
|
* 链接本身由调用方渲染(Next.js 用 next/link,保持 SPA 导航),
|
|
14
14
|
* 只要都套这个 className,任何站点的导航观感就完全一致。
|
|
15
15
|
*/
|
|
@@ -17,7 +17,7 @@ export function navItemClass(active: boolean): string {
|
|
|
17
17
|
return cn(
|
|
18
18
|
"inline-flex items-center gap-1.5 rounded-full px-3 py-1.5 text-sm font-medium transition-colors",
|
|
19
19
|
active
|
|
20
|
-
? "bg-ink text-
|
|
20
|
+
? "bg-ink text-bg"
|
|
21
21
|
: "text-ink-2 hover:bg-surface-2 hover:text-ink"
|
|
22
22
|
);
|
|
23
23
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
.zmz-reasoning { border-left: 2px solid var(--color-line); padding-left: 0.6rem; }
|
|
5
5
|
.zmz-reasoning-toggle { display: inline-flex; align-items: center; gap: 0.3rem; background: none; border: 0; cursor: pointer; font-family: var(--font-mono); font-size: 0.625rem; letter-spacing: 0.07em; text-transform: uppercase; color: var(--color-muted); padding: 0.15rem 0; }
|
|
6
6
|
.zmz-reasoning-dot { display: inline-block; width: 0.4rem; height: 0.4rem; border-radius: 50%; background: var(--color-muted); }
|
|
7
|
-
.zmz-reasoning-dot.live { background: var(--color-
|
|
7
|
+
.zmz-reasoning-dot.live { background: var(--color-live); animation: zmz-reasoning-pulse 1.2s ease-in-out infinite; }
|
|
8
8
|
.zmz-chevron { transition: transform 0.15s var(--ease-out); }
|
|
9
9
|
.zmz-chevron.open { transform: rotate(180deg); }
|
|
10
10
|
.zmz-reasoning-body { margin: 0.4rem 0 0; overflow: hidden; font-size: 0.75rem; color: var(--color-muted); white-space: pre-wrap; }
|
|
@@ -16,7 +16,7 @@ export interface StickyBannerProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
16
16
|
|
|
17
17
|
const variantStyles: Record<BannerVariant, string> = {
|
|
18
18
|
info: "bg-surface text-ink border-line",
|
|
19
|
-
warning: "bg-ink text-
|
|
19
|
+
warning: "bg-ink text-bg border-ink",
|
|
20
20
|
danger: "bg-bg text-ink border-ink border-2",
|
|
21
21
|
};
|
|
22
22
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/* SubtaskPart — 执行树中的子任务行。token 依赖消费端 @theme
|
|
2
|
-
(--color-line、--color-
|
|
2
|
+
(--color-line、--color-live、--color-muted、--font-mono)。
|
|
3
3
|
执行树分支线(::before)等上下文增强由消费端自己的 CSS 叠加。 */
|
|
4
4
|
|
|
5
5
|
.subtask-row { position: relative; display: flex; align-items: center; gap: 0.5rem; min-height: 2.15rem; padding: 0.38rem 0.4rem; background: transparent; }
|
|
6
6
|
/* 执行树分支线(挂在左侧时间轴上;独立使用时容器需留出左侧空间) */
|
|
7
7
|
.subtask-row::before { position: absolute; top: 1.08rem; left: -0.81rem; width: 0.81rem; height: 1px; background: var(--color-line); content: ""; }
|
|
8
|
-
.subtask-icon { display: grid; width: 1rem; height: 1rem; place-items: center; color: var(--color-
|
|
8
|
+
.subtask-icon { display: grid; width: 1rem; height: 1rem; place-items: center; color: var(--color-live); }
|
|
9
9
|
.subtask-copy { display: grid; min-width: 0; flex: 1; gap: 0.1rem; }
|
|
10
10
|
.subtask-copy strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.75rem; font-weight: 600; }
|
|
11
11
|
.subtask-copy small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--color-muted); font-family: var(--font-mono); font-size: 0.6rem; }
|
|
@@ -20,13 +20,13 @@
|
|
|
20
20
|
.zmz-task-node-trigger:disabled { cursor: default; }
|
|
21
21
|
.zmz-task-node-trigger:disabled:hover { background: transparent; }
|
|
22
22
|
.zmz-task-node-marker { display: inline-flex; width: 0.9rem; height: 0.9rem; align-items: center; justify-content: center; border: 1px solid var(--color-line); border-radius: 50%; background: var(--color-surface); color: var(--color-success); }
|
|
23
|
-
.zmz-task-node.in_progress .zmz-task-node-marker { border-color: var(--color-
|
|
23
|
+
.zmz-task-node.in_progress .zmz-task-node-marker { border-color: var(--color-live); }
|
|
24
24
|
.zmz-task-node-copy { display: flex; min-width: 0; align-items: baseline; gap: 0.45rem; }
|
|
25
25
|
.zmz-task-node-copy strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.76rem; font-weight: 600; }
|
|
26
26
|
.zmz-task-node-index { flex: 0 0 auto; color: var(--color-muted); font-family: var(--font-mono); font-size: 0.55rem; }
|
|
27
27
|
.zmz-task-node.completed .zmz-task-node-copy strong { color: var(--color-muted); text-decoration: line-through; }
|
|
28
|
-
.zmz-task-node.in_progress .zmz-task-node-copy strong { color: var(--color-
|
|
29
|
-
.zmz-todo-spinner { width: 0.45rem; height: 0.45rem; border-radius: 50%; background: var(--color-
|
|
28
|
+
.zmz-task-node.in_progress .zmz-task-node-copy strong { color: var(--color-live); }
|
|
29
|
+
.zmz-todo-spinner { width: 0.45rem; height: 0.45rem; border-radius: 50%; background: var(--color-live); animation: zmz-pulse 1s var(--ease-out) infinite; }
|
|
30
30
|
.zmz-chevron { transition: transform 0.15s var(--ease-out); }
|
|
31
31
|
.zmz-chevron.open { transform: rotate(180deg); }
|
|
32
32
|
.zmz-task-executions { position: relative; display: grid; gap: 0; margin: 0 0 0.15rem 1.3rem; padding: 0.05rem 0 0.1rem 1rem; }
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
.tool-card-trigger { display: flex; width: 100%; min-height: 2.15rem; align-items: center; gap: 0.5rem; border: 0; border-radius: var(--radius-sm); background: transparent; color: var(--color-ink); padding: 0.38rem 0.4rem; text-align: left; cursor: pointer; transition: background 0.15s var(--ease-out); }
|
|
9
9
|
.tool-card-trigger:hover { background: var(--color-surface); }
|
|
10
10
|
.tool-card-glyph { display: grid; width: 1rem; height: 1rem; flex: 0 0 1rem; place-items: center; border-radius: 50%; color: var(--color-muted); }
|
|
11
|
-
.tool-card.running .tool-card-glyph { color: var(--color-
|
|
11
|
+
.tool-card.running .tool-card-glyph { color: var(--color-live); animation: zmz-tool-pulse 1s ease-in-out infinite; }
|
|
12
12
|
.tool-card.completed .tool-card-glyph { color: var(--color-success); }
|
|
13
|
-
.tool-card.failed .tool-card-glyph { color:
|
|
13
|
+
.tool-card.failed .tool-card-glyph { color: var(--color-danger); }
|
|
14
14
|
.tool-card-label { min-width: 0; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.75rem; }
|
|
15
15
|
.tool-card-duration { flex: 0 0 auto; color: var(--color-muted); font-family: var(--font-mono); font-size: 0.6rem; }
|
|
16
16
|
.tool-card-chevron { flex: 0 0 auto; color: var(--color-muted); transition: transform 0.15s var(--ease-out); }
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
.tool-card-detail-section { display: grid; gap: 0.25rem; }
|
|
20
20
|
.tool-card-detail-label { color: var(--color-muted); font-family: var(--font-mono); font-size: 0.58rem; letter-spacing: 0.08em; text-transform: uppercase; }
|
|
21
21
|
.tool-card-detail pre { margin: 0; color: var(--color-muted); font-family: var(--font-mono); font-size: 0.68rem; line-height: 1.5; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
22
|
-
.tool-card-live-note { color: var(--color-
|
|
22
|
+
.tool-card-live-note { color: var(--color-live); font-family: var(--font-mono); font-size: 0.62rem; }
|
|
23
23
|
|
|
24
24
|
.zmz-tool-group { display: grid; gap: 0.5rem; margin: 0.4rem 0; }
|
|
25
25
|
.zmz-tool-group-trigger { display: flex; align-items: center; gap: 0.5rem; width: 100%; border: 1px solid var(--color-line); border-radius: var(--radius-sm); background: color-mix(in oklab, var(--color-surface) 92%, var(--color-bg)); color: var(--color-ink); padding: 0.4rem 0.6rem; cursor: pointer; transition: border-color 0.15s var(--ease-out); }
|
|
@@ -145,7 +145,7 @@ export function VanishInput({
|
|
|
145
145
|
<button
|
|
146
146
|
type="submit"
|
|
147
147
|
aria-label="提交"
|
|
148
|
-
className="ml-2 flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-ink text-
|
|
148
|
+
className="ml-2 flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-ink text-bg transition-transform hover:scale-105 active:scale-95"
|
|
149
149
|
>
|
|
150
150
|
<svg
|
|
151
151
|
className="h-3.5 w-3.5"
|
package/src/tokens/theme.css
CHANGED
|
@@ -6,69 +6,134 @@
|
|
|
6
6
|
* @import "@zmzai/theme/tokens";
|
|
7
7
|
* @import "@zmzai/theme/fonts";
|
|
8
8
|
*
|
|
9
|
-
* v0.
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* v0.7.0 「墨骨 Ink Frame」—— 结构色与语义色分离
|
|
10
|
+
* · accent 从荧光绿改为墨黑,只承担 UI 结构(按钮 / 焦点 / 选中 / 硬线)
|
|
11
|
+
* · 绿退为品牌色 + 语义色(成功 / 运行中),不再当万能强调色
|
|
12
|
+
* · 中性灰阶统一色相 hue 265、彩度压到 0.008 以下,不再泛脏
|
|
13
|
+
* · 补齐 elevation 阴影体系(此前包内一个 shadow token 都没有)
|
|
14
|
+
*
|
|
15
|
+
* 沿革:
|
|
16
|
+
* v0.1.x 黑白 Monochrome
|
|
17
|
+
* v0.2~0.5 暖纸 + 印章红
|
|
18
|
+
* v0.6.x 纯白 + 荧光绿(accent 一色包打天下,装饰过载 → 观感 demo)
|
|
19
|
+
* v0.7.0 纯白 + 墨黑结构 + 绿色语义(现行)
|
|
13
20
|
*/
|
|
14
21
|
|
|
15
22
|
@theme {
|
|
16
|
-
/*
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
--color-
|
|
23
|
-
--color-
|
|
24
|
-
--color-
|
|
25
|
-
--color-
|
|
26
|
-
--color-
|
|
27
|
-
--color-
|
|
28
|
-
--color-
|
|
29
|
-
--color-
|
|
30
|
-
--color-
|
|
31
|
-
--color-
|
|
32
|
-
|
|
33
|
-
/*
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
23
|
+
/* ══════════════════════════════════════════════════════
|
|
24
|
+
1. Neutral — 中性灰阶
|
|
25
|
+
统一色相 hue 265(冷中性),彩度随明度轻微起伏但全部 ≤ 0.017。
|
|
26
|
+
v0.6 的 ink(30°) / ink-2(55°) / line(88°) / surface(90°) 四个色相
|
|
27
|
+
混用,是"说不出哪里怪"的根源。
|
|
28
|
+
══════════════════════════════════════════════════════ */
|
|
29
|
+
--color-bg: oklch(1 0 0); /* 页面底,纯白 */
|
|
30
|
+
--color-surface: oklch(0.985 0.001 265); /* 卡片 / 面板 */
|
|
31
|
+
--color-surface-2: oklch(0.968 0.002 265); /* 嵌套 / 表头 */
|
|
32
|
+
--color-surface-3: oklch(0.94 0.003 265); /* hover / 按下 */
|
|
33
|
+
--color-ink: oklch(0.21 0.008 265); /* 标题 / 正文 */
|
|
34
|
+
--color-ink-2: oklch(0.442 0.017 265); /* 次级文字 */
|
|
35
|
+
--color-ink-3: oklch(0.617 0.014 265); /* 辅助 / 占位 · 3.69:1 */
|
|
36
|
+
--color-line: oklch(0.92 0.004 265); /* 常规边框 */
|
|
37
|
+
--color-line-strong: oklch(0.87 0.005 265); /* 强调边框 */
|
|
38
|
+
--color-rule: oklch(0.21 0.008 265); /* 硬分隔线 = ink */
|
|
39
|
+
|
|
40
|
+
/* ══════════════════════════════════════════════════════
|
|
41
|
+
2. Structure accent — 墨黑
|
|
42
|
+
只干结构的活:主按钮、焦点环、选中态、硬线、链接 hover。
|
|
43
|
+
高饱和彩色的活它不干——那正是 v0.6 显得 demo 的原因。
|
|
44
|
+
══════════════════════════════════════════════════════ */
|
|
45
|
+
--color-accent: oklch(0.21 0.008 265);
|
|
46
|
+
--color-accent-ink: oklch(1 0 0);
|
|
47
|
+
--color-accent-strong: oklch(0.09 0.004 265);
|
|
48
|
+
--color-accent-tint: oklch(0.968 0.002 265);
|
|
49
|
+
--color-accent-readable: var(--color-accent);
|
|
50
|
+
|
|
51
|
+
/* ══════════════════════════════════════════════════════
|
|
52
|
+
3. Brand green — 品牌资产专用
|
|
53
|
+
只给 favicon / logo / 印章 / 极少数品牌位。
|
|
54
|
+
不承担交互语义,不进按钮、不进边框。
|
|
55
|
+
══════════════════════════════════════════════════════ */
|
|
56
|
+
--color-brand: oklch(0.499 0.14 145); /* 5.67:1 */
|
|
57
|
+
--color-brand-tint: oklch(0.955 0.035 145);
|
|
58
|
+
|
|
59
|
+
/* ══════════════════════════════════════════════════════
|
|
60
|
+
4. Semantic — 语义色
|
|
61
|
+
只表达状态,不做装饰。每色配一档浅底 tint 用于 badge / 提示条。
|
|
62
|
+
全部经 WCAG 校验:文字用途 ≥ 4.5:1(白底)。
|
|
63
|
+
对照:v0.6 的荧光绿在白底只有 1.8:1,21 处 text-accent 全部不达标。
|
|
64
|
+
══════════════════════════════════════════════════════ */
|
|
65
|
+
--color-live: oklch(0.499 0.17 145); /* 运行中 / 活跃 · 5.55:1 */
|
|
66
|
+
--color-success: oklch(0.499 0.13 145); /* 成功 · 5.70:1 */
|
|
67
|
+
--color-success-tint: oklch(0.955 0.035 145);
|
|
68
|
+
--color-warning: oklch(0.499 0.12 75); /* 告警 · 6.14:1 */
|
|
69
|
+
--color-warning-tint: oklch(0.955 0.045 90);
|
|
70
|
+
--color-danger: oklch(0.499 0.18 25); /* 错误 · 6.62:1 */
|
|
71
|
+
--color-danger-tint: oklch(0.955 0.03 25);
|
|
72
|
+
|
|
73
|
+
/* ══════════════════════════════════════════════════════
|
|
74
|
+
5. Dark surface — 仅代码块 / preview,不是全局 dark mode
|
|
75
|
+
══════════════════════════════════════════════════════ */
|
|
76
|
+
--color-dark-bg: oklch(0.17 0.006 265);
|
|
77
|
+
--color-dark-surface: oklch(0.23 0.007 265);
|
|
78
|
+
--color-dark-ink: oklch(0.97 0.002 265);
|
|
79
|
+
--color-dark-line: oklch(0.32 0.008 265);
|
|
80
|
+
|
|
81
|
+
/* ══════════════════════════════════════════════════════
|
|
82
|
+
6. Elevation — 阴影
|
|
83
|
+
v0.6 完全没有 shadow token,各站只能手写 oklch(... / 0.12) 硬编码。
|
|
84
|
+
统一用 ink 色做阴影基色,避免出现彩虹灰。
|
|
85
|
+
══════════════════════════════════════════════════════ */
|
|
86
|
+
--shadow-xs: 0 1px 2px oklch(0.21 0.008 265 / 0.05);
|
|
87
|
+
--shadow-sm: 0 1px 3px oklch(0.21 0.008 265 / 0.06),
|
|
88
|
+
0 1px 2px oklch(0.21 0.008 265 / 0.04);
|
|
89
|
+
--shadow-md: 0 4px 12px -2px oklch(0.21 0.008 265 / 0.08),
|
|
90
|
+
0 2px 4px -2px oklch(0.21 0.008 265 / 0.05);
|
|
91
|
+
--shadow-lg: 0 12px 32px -8px oklch(0.21 0.008 265 / 0.12),
|
|
92
|
+
0 4px 8px -4px oklch(0.21 0.008 265 / 0.06);
|
|
93
|
+
--shadow-xl: 0 24px 56px -12px oklch(0.21 0.008 265 / 0.16);
|
|
94
|
+
|
|
95
|
+
/* ══════════════════════════════════════════════════════
|
|
96
|
+
7. Fonts
|
|
97
|
+
MiSans 主字体;fallback 全部为无衬线。
|
|
98
|
+
v0.6 把 Noto Serif SC 排在 sans 第二顺位——MiSans CDN 一挂整站
|
|
99
|
+
掉宋体。此处移除。
|
|
100
|
+
══════════════════════════════════════════════════════ */
|
|
101
|
+
--font-sans: "MiSans", -apple-system, BlinkMacSystemFont, "PingFang SC",
|
|
102
|
+
"Hiragino Sans GB", "Microsoft YaHei", system-ui, sans-serif;
|
|
44
103
|
--font-serif: "Noto Serif SC", "Source Han Serif SC", "Songti SC", serif;
|
|
45
|
-
--font-mono: "JetBrains Mono", ui-monospace, "SFMono-Regular", monospace;
|
|
46
|
-
|
|
47
|
-
/*
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
--radius-
|
|
52
|
-
--radius-
|
|
104
|
+
--font-mono: "JetBrains Mono", ui-monospace, "SFMono-Regular", "Menlo", monospace;
|
|
105
|
+
|
|
106
|
+
/* ══════════════════════════════════════════════════════
|
|
107
|
+
8. Radius — 锐利但不寒酸
|
|
108
|
+
v0.6 的 2/2/4/6 太扁,大容器用 2px 像表格不成卡片。
|
|
109
|
+
══════════════════════════════════════════════════════ */
|
|
110
|
+
--radius-xs: 2px;
|
|
111
|
+
--radius-sm: 3px;
|
|
112
|
+
--radius: 4px;
|
|
113
|
+
--radius-md: 4px;
|
|
114
|
+
--radius-lg: 6px;
|
|
115
|
+
--radius-xl: 8px;
|
|
116
|
+
--radius-2xl: 12px;
|
|
53
117
|
--radius-pill: 9999px;
|
|
54
118
|
|
|
55
|
-
/*
|
|
119
|
+
/* ══════════════════════════════════════════════════════
|
|
120
|
+
9. Motion
|
|
121
|
+
══════════════════════════════════════════════════════ */
|
|
56
122
|
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
|
|
57
123
|
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
58
124
|
|
|
59
|
-
/*
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
--color-
|
|
64
|
-
--color-
|
|
125
|
+
/* ══════════════════════════════════════════════════════
|
|
126
|
+
10. Backward-compat aliases
|
|
127
|
+
v1 token 名 → v0.7 值,让既有 267 个 class 无需改动。
|
|
128
|
+
══════════════════════════════════════════════════════ */
|
|
129
|
+
--color-paper: var(--color-bg);
|
|
130
|
+
--color-muted: var(--color-ink-2);
|
|
65
131
|
--color-surface-strong: var(--color-surface-2);
|
|
66
|
-
--
|
|
67
|
-
--animate-dur-
|
|
68
|
-
--animate-dur-
|
|
69
|
-
--
|
|
70
|
-
--radius-
|
|
71
|
-
--radius-input: var(--radius-sm);
|
|
132
|
+
--animate-dur-fast: 150ms;
|
|
133
|
+
--animate-dur-base: 250ms;
|
|
134
|
+
--animate-dur-slow: 500ms;
|
|
135
|
+
--radius-card: var(--radius);
|
|
136
|
+
--radius-input: var(--radius-sm);
|
|
72
137
|
}
|
|
73
138
|
|
|
74
139
|
:root {
|