Skip to content

Commit 67c1f0f

Browse files
nieaoclaude
andcommitted
refactor(canvas): 节点全局 hex 颜色入 token (主题色 + 语义色拆分)
17 个 canvas 组件的 hex 字面量入 var() token, 其中: - 背景/边框/文字 等随主题切换的颜色 → var(--surface) / var(--border-subtle) / var(--text-secondary) - 节点身份色板 (概念/技术/资源 等分类色) 保留 hex 并加注释 — 这是封闭语义系统, 不随主题 - 紫色 SynthesisNode 同上 — 蓝+红=紫 是固定语义色, 跨主题保持 .gitignore 新增 .test-*.mjs (临时调试脚本) 和 .claude/ (内部目录) Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
1 parent 031b8db commit 67c1f0f

17 files changed

Lines changed: 269 additions & 241 deletions

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,9 @@ logs/
2323
.test-screenshots/
2424
e2e-orchestra-shots/
2525
e2e-real-run.png
26+
27+
# 临时一次性测试脚本 (会话内调试用, 不进 main)
28+
.test-*.mjs
29+
30+
# Claude Code 工作目录
31+
.claude/

src/components/canvas/BookmarkNode.jsx

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const safeString = (val) => {
1515
return String(val)
1616
}
1717

18-
// 常见网站品牌配色
18+
// 常见网站品牌配色 (语义色: brand 色板, 不随主题切换)
1919
const BRAND_COLORS = {
2020
'google.com': '#4285F4',
2121
'github.com': '#181717',
@@ -52,7 +52,7 @@ const HANDLE_STYLE = {
5252
width: 10, height: 10,
5353
border: '2px solid white',
5454
borderRadius: '50%',
55-
backgroundColor: '#c8a882',
55+
backgroundColor: 'var(--accent)',
5656
opacity: 1,
5757
cursor: 'crosshair',
5858
}
@@ -67,7 +67,7 @@ function BookmarkNode({ id, data, selected }) {
6767

6868
const url = data.url || ''
6969
const domain = extractDomain(url)
70-
const brandColor = BRAND_COLORS[domain] || '#555'
70+
const brandColor = BRAND_COLORS[domain] || 'var(--text-muted)'
7171
const isLoading = data.loading
7272

7373
// 处理链接点击
@@ -109,14 +109,15 @@ function BookmarkNode({ id, data, selected }) {
109109
return (
110110
<div
111111
className={`
112-
relative w-[280px] bg-white rounded-lg border-2 cursor-pointer
112+
relative w-[280px] rounded-lg border-2 cursor-pointer
113113
transition-all duration-300 ease-[cubic-bezier(0.22,1,0.36,1)]
114114
${selected ? 'ring-2 ring-offset-2 scale-[1.02]' : 'hover:shadow-lg'}
115115
`}
116116
style={{
117117
overflow: 'visible',
118-
borderColor: selected ? '#c8a882' : '#e8e8e8',
119-
ringColor: selected ? '#c8a882' : undefined,
118+
backgroundColor: 'var(--surface)',
119+
borderColor: selected ? 'var(--accent)' : 'var(--border-subtle)',
120+
ringColor: selected ? 'var(--accent)' : undefined,
120121
boxShadow: selected ? '0 4px 20px rgba(200, 168, 130, 0.15)' : '0 1px 3px rgba(0,0,0,0.06)',
121122
}}
122123
onClick={handleClick}
@@ -144,23 +145,23 @@ function BookmarkNode({ id, data, selected }) {
144145

145146
{/* 加载状态 */}
146147
{isLoading && (
147-
<div className="absolute inset-0 bg-white/80 rounded-lg flex items-center justify-center z-10">
148+
<div className="absolute inset-0 rounded-lg flex items-center justify-center z-10" style={{ backgroundColor: 'rgba(250,250,250,0.8)' }}>
148149
<div className="flex flex-col items-center gap-2">
149-
<svg className="w-6 h-6 animate-spin" style={{ color: '#c8a882' }} fill="none" viewBox="0 0 24 24">
150+
<svg className="w-6 h-6 animate-spin" style={{ color: 'var(--accent)' }} fill="none" viewBox="0 0 24 24">
150151
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
151152
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
152153
</svg>
153-
<span className="text-xs" style={{ color: '#888' }}>获取链接信息...</span>
154+
<span className="text-xs" style={{ color: 'var(--text-muted)' }}>获取链接信息...</span>
154155
</div>
155156
</div>
156157
)}
157158

158159
{/* 预览图 */}
159160
{data.image && !imageError && (
160-
<div className="relative overflow-hidden rounded-t-lg" style={{ borderBottom: '1px solid #e8e8e8' }}>
161+
<div className="relative overflow-hidden rounded-t-lg" style={{ borderBottom: '1px solid var(--border-subtle)' }}>
161162
{!imageLoaded && (
162-
<div className="w-full h-32 flex items-center justify-center" style={{ backgroundColor: '#f5f0eb' }}>
163-
<div className="animate-pulse text-sm" style={{ color: '#bbb' }}>加载中...</div>
163+
<div className="w-full h-32 flex items-center justify-center" style={{ backgroundColor: 'var(--accent-bg)' }}>
164+
<div className="animate-pulse text-sm" style={{ color: 'var(--text-faint)' }}>加载中...</div>
164165
</div>
165166
)}
166167
<img
@@ -180,7 +181,7 @@ function BookmarkNode({ id, data, selected }) {
180181
<h4
181182
className="font-medium text-sm line-clamp-2 mb-1"
182183
style={{
183-
color: '#1a1a1a',
184+
color: 'var(--text-primary)',
184185
fontFamily: '"Noto Sans SC", system-ui, sans-serif',
185186
}}
186187
>
@@ -192,7 +193,7 @@ function BookmarkNode({ id, data, selected }) {
192193
<p
193194
className="text-xs line-clamp-2 mb-2"
194195
style={{
195-
color: '#888',
196+
color: 'var(--text-muted)',
196197
fontFamily: '"Noto Sans SC", system-ui, sans-serif',
197198
}}
198199
>
@@ -212,9 +213,9 @@ function BookmarkNode({ id, data, selected }) {
212213
onClick={(e) => e.stopPropagation()}
213214
className="w-full px-2 py-1 text-xs border rounded focus:outline-none focus:ring-1"
214215
style={{
215-
borderColor: '#e8d5c0',
216-
color: '#555',
217-
ringColor: '#c8a882',
216+
borderColor: 'var(--accent-soft)',
217+
color: 'var(--text-muted)',
218+
ringColor: 'var(--accent)',
218219
}}
219220
placeholder="输入链接地址..."
220221
/>
@@ -243,8 +244,8 @@ function BookmarkNode({ id, data, selected }) {
243244
<div
244245
className="absolute top-2 left-2 px-1.5 py-0.5 text-[10px] font-medium rounded"
245246
style={{
246-
backgroundColor: '#c8a882',
247-
color: '#fafafa',
247+
backgroundColor: 'var(--accent)',
248+
color: 'var(--surface)',
248249
letterSpacing: '0.1em',
249250
}}
250251
>

src/components/canvas/CategoryNode.jsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const safeString = (val) => {
1515
return String(val)
1616
}
1717

18-
// 默认分类配置:图标 + 颜色
18+
// 默认分类配置:图标 + 颜色 (语义色: 节点身份色板, 不随主题切换)
1919
const CATEGORY_CONFIG = {
2020
'概念': { icon: '💡', color: '#c8a882' },
2121
'技术': { icon: '⚙', color: '#7c9eb2' },
@@ -32,13 +32,13 @@ const HANDLE_STYLE = {
3232
width: 10, height: 10,
3333
border: '2px solid white',
3434
borderRadius: '50%',
35-
backgroundColor: '#fafafa',
35+
backgroundColor: 'var(--surface)',
3636
opacity: 1,
3737
cursor: 'crosshair',
3838
}
3939

4040
function CategoryNode({ data, selected }) {
41-
const config = CATEGORY_CONFIG[data.name] || { icon: '📁', color: data.color || '#c8a882' }
41+
const config = CATEGORY_CONFIG[data.name] || { icon: '📁', color: data.color || 'var(--accent)' }
4242
const categoryColor = data.color || config.color
4343
const childCount = data.childCount || 0
4444

@@ -52,7 +52,7 @@ function CategoryNode({ data, selected }) {
5252
style={{
5353
backgroundColor: categoryColor,
5454
boxShadow: selected
55-
? `0 0 0 4px #fafafa, 0 0 0 6px ${categoryColor}, 0 8px 24px ${categoryColor}40`
55+
? `0 0 0 4px var(--surface), 0 0 0 6px ${categoryColor}, 0 8px 24px ${categoryColor}40`
5656
: `0 4px 12px ${categoryColor}30`,
5757
}}
5858
>
@@ -87,7 +87,7 @@ function CategoryNode({ data, selected }) {
8787
<span
8888
className="text-xs font-bold text-center px-2 truncate max-w-full"
8989
style={{
90-
color: '#fafafa',
90+
color: 'var(--surface)',
9191
fontFamily: '"Noto Sans SC", system-ui, sans-serif',
9292
letterSpacing: '0.05em',
9393
}}

src/components/canvas/ChallengeNode.jsx

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@
1313
import { memo } from 'react'
1414
import { Handle, Position } from 'reactflow'
1515

16+
// severity 色 (语义色: 严重度色板, 跨主题保持语义)
1617
const SEVERITY_META = {
17-
high: { label: '严重', color: '#b27c8b', bg: '#fbf1f3' },
18-
medium: { label: '中等', color: '#c8a882', bg: '#f5f0eb' },
19-
low: { label: '轻微', color: '#888', bg: '#f5f5f5' },
18+
high: { label: '严重', color: '#b27c8b', bg: '#fbf1f3' }, // severity-high 粉灰
19+
medium: { label: '中等', color: 'var(--accent)', bg: 'var(--accent-bg)' }, // severity-medium = accent
20+
low: { label: '轻微', color: 'var(--text-muted)', bg: 'var(--border-subtle)' },
2021
}
2122

2223
function ChallengeNodeImpl({ data, selected }) {
@@ -33,7 +34,7 @@ function ChallengeNodeImpl({ data, selected }) {
3334
className="relative shadow-sm transition-all duration-300"
3435
style={{
3536
width: 280,
36-
background: '#fafafa',
37+
background: 'var(--surface)',
3738
border: `${selected ? '2px' : '1px'} solid ${meta.color}`,
3839
borderRadius: 4,
3940
}}
@@ -64,14 +65,14 @@ function ChallengeNodeImpl({ data, selected }) {
6465
</div>
6566

6667
{/* 攻击角度 */}
67-
<div className="text-[11px] font-medium mb-1.5" style={{ color: '#1a1a1a' }}>
68+
<div className="text-[11px] font-medium mb-1.5" style={{ color: 'var(--text-primary)' }}>
6869
{angle}
6970
</div>
7071

7172
{/* 反驳论点 */}
7273
<div
7374
className="text-[11px] leading-relaxed italic mb-2"
74-
style={{ color: '#3a3a3a', borderLeft: `2px solid ${meta.color}`, paddingLeft: 8 }}
75+
style={{ color: 'var(--text-secondary)', borderLeft: `2px solid ${meta.color}`, paddingLeft: 8 }}
7576
>
7677
{claim}
7778
</div>
@@ -90,7 +91,7 @@ function ChallengeNodeImpl({ data, selected }) {
9091
<li
9192
key={i}
9293
className="text-[10px] leading-snug pl-2"
93-
style={{ color: '#3a3a3a', borderLeft: '2px solid #e8e8e8' }}
94+
style={{ color: 'var(--text-secondary)', borderLeft: '2px solid var(--border-subtle)' }}
9495
>
9596
{e}
9697
</li>
@@ -113,7 +114,7 @@ function ChallengeNodeImpl({ data, selected }) {
113114
<li
114115
key={i}
115116
className="text-[10px] leading-snug flex items-start gap-1.5"
116-
style={{ color: '#1a1a1a' }}
117+
style={{ color: 'var(--text-primary)' }}
117118
>
118119
<span style={{ color: meta.color, fontWeight: 700, flexShrink: 0 }}></span>
119120
<span>{t}</span>
@@ -125,8 +126,8 @@ function ChallengeNodeImpl({ data, selected }) {
125126

126127
{/* 攻击对象 (源节点) */}
127128
{sourceTitle && (
128-
<div className="text-[9px] mt-2.5 pt-1.5" style={{ color: '#888', borderTop: '1px dashed #e8e8e8' }}>
129-
针对: <span className="font-medium" style={{ color: '#555' }}>{sourceTitle}</span>
129+
<div className="text-[9px] mt-2.5 pt-1.5" style={{ color: 'var(--text-muted)', borderTop: '1px dashed var(--border-subtle)' }}>
130+
针对: <span className="font-medium" style={{ color: 'var(--text-muted)' }}>{sourceTitle}</span>
130131
</div>
131132
)}
132133
</div>

src/components/canvas/ConceptNode.jsx

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ const SOURCE_TYPE_ICONS = {
4545
),
4646
}
4747

48-
// 分类颜色映射
48+
// 分类颜色映射 (语义色: 节点身份色板 / 不随主题切换 / 概念=accent)
4949
const CATEGORY_COLORS = {
5050
'概念': '#c8a882',
5151
'技术': '#7c9eb2',
@@ -88,7 +88,7 @@ const HANDLE_STYLE = {
8888
height: 10,
8989
border: '2px solid white',
9090
borderRadius: '50%',
91-
backgroundColor: '#c8a882',
91+
backgroundColor: 'var(--accent)',
9292
opacity: 1,
9393
}
9494

@@ -100,7 +100,7 @@ function ConceptNode({ id, data, selected }) {
100100

101101
const sizeStyle = SIZE_SCALES[data.size] || SIZE_SCALES.medium
102102
const isMarked = data.marked
103-
const markColor = data.markColor || '#c8a882'
103+
const markColor = data.markColor || 'var(--accent)'
104104
const categoryColor = CATEGORY_COLORS[data.category] || '#c8a882'
105105
const sourceIcon = SOURCE_TYPE_ICONS[data.sourceType] || SOURCE_TYPE_ICONS.manual
106106

@@ -164,9 +164,9 @@ function ConceptNode({ id, data, selected }) {
164164
${isExpanded ? 'min-w-[320px] max-w-[400px]' : `${sizeStyle.minW} ${sizeStyle.maxW}`}
165165
`}
166166
style={{
167-
backgroundColor: '#fafafa',
168-
borderColor: selected ? '#c8a882' : '#e8e8e8',
169-
ringColor: selected ? '#c8a882' : undefined,
167+
backgroundColor: 'var(--surface)',
168+
borderColor: selected ? 'var(--accent)' : 'var(--border-subtle)',
169+
ringColor: selected ? 'var(--accent)' : undefined,
170170
boxShadow: isMarked
171171
? `0 0 0 3px ${markColor}40, 0 4px 12px ${markColor}20`
172172
: selected
@@ -197,9 +197,9 @@ function ConceptNode({ id, data, selected }) {
197197
onClick={handleTogglePin}
198198
className="absolute -top-3 right-2 w-5 h-5 flex items-center justify-center rounded-full shadow-sm transition-all z-10"
199199
style={{
200-
backgroundColor: isPinned ? '#c8a882' : '#fafafa',
201-
color: isPinned ? '#fafafa' : '#888',
202-
border: isPinned ? 'none' : '1px solid #e8e8e8',
200+
backgroundColor: isPinned ? 'var(--accent)' : 'var(--surface)',
201+
color: isPinned ? 'var(--surface)' : 'var(--text-faint)',
202+
border: isPinned ? 'none' : '1px solid var(--border-subtle)',
203203
}}
204204
title={isPinned ? '取消钉住' : '钉住展开'}
205205
>
@@ -215,7 +215,7 @@ function ConceptNode({ id, data, selected }) {
215215
className="absolute -top-2.5 right-6 px-2 py-0.5 rounded text-[10px] font-medium tracking-wider"
216216
style={{
217217
backgroundColor: categoryColor,
218-
color: '#fafafa',
218+
color: 'var(--surface)',
219219
fontFamily: '"Noto Sans SC", system-ui, sans-serif',
220220
letterSpacing: '0.15em',
221221
}}
@@ -228,7 +228,7 @@ function ConceptNode({ id, data, selected }) {
228228
<div
229229
className="absolute top-0 left-0 right-0 h-[2px] rounded-t-lg transition-all duration-500"
230230
style={{
231-
backgroundColor: '#c8a882',
231+
backgroundColor: 'var(--accent)',
232232
opacity: selected || isHovered ? 1 : 0.4,
233233
transform: selected || isHovered ? 'scaleX(1)' : 'scaleX(0.3)',
234234
transformOrigin: 'left',
@@ -245,7 +245,7 @@ function ConceptNode({ id, data, selected }) {
245245
<h3
246246
className={`${sizeStyle.titleSize} font-bold truncate`}
247247
style={{
248-
color: '#1a1a1a',
248+
color: 'var(--text-primary)',
249249
fontFamily: '"Noto Serif SC", Georgia, serif',
250250
letterSpacing: '0.02em',
251251
}}
@@ -259,7 +259,7 @@ function ConceptNode({ id, data, selected }) {
259259
}`}>
260260
<p
261261
className={`${sizeStyle.textSize} mt-1 leading-relaxed line-clamp-2`}
262-
style={{ color: '#555', fontFamily: '"Noto Sans SC", system-ui, sans-serif' }}
262+
style={{ color: 'var(--text-muted)', fontFamily: '"Noto Sans SC", system-ui, sans-serif' }}
263263
>
264264
{getShortDescription()}
265265
</p>
@@ -272,7 +272,7 @@ function ConceptNode({ id, data, selected }) {
272272
<div
273273
className="text-xs mt-2 whitespace-pre-wrap leading-relaxed max-h-[380px] overflow-y-auto"
274274
style={{
275-
color: '#2d2d2d',
275+
color: 'var(--text-secondary)',
276276
fontFamily: '"Noto Sans SC", system-ui, sans-serif',
277277
}}
278278
>
@@ -288,17 +288,17 @@ function ConceptNode({ id, data, selected }) {
288288
key={i}
289289
className="text-[10px] px-1.5 py-0.5 rounded"
290290
style={{
291-
backgroundColor: '#f5f0eb',
292-
color: '#c8a882',
293-
border: '1px solid #e8d5c0',
291+
backgroundColor: 'var(--accent-bg)',
292+
color: 'var(--accent)',
293+
border: '1px solid var(--accent-soft)',
294294
fontFamily: '"Noto Sans SC", system-ui, sans-serif',
295295
}}
296296
>
297297
{safeString(tag)}
298298
</span>
299299
))}
300300
{data.tags.length > 3 && (
301-
<span className="text-[10px]" style={{ color: '#bbb' }}>
301+
<span className="text-[10px]" style={{ color: 'var(--text-faint)' }}>
302302
+{data.tags.length - 3}
303303
</span>
304304
)}
@@ -313,9 +313,9 @@ function ConceptNode({ id, data, selected }) {
313313
key={i}
314314
className="text-[10px] px-1.5 py-0.5 rounded"
315315
style={{
316-
backgroundColor: '#f5f0eb',
317-
color: '#c8a882',
318-
border: '1px solid #e8d5c0',
316+
backgroundColor: 'var(--accent-bg)',
317+
color: 'var(--accent)',
318+
border: '1px solid var(--accent-soft)',
319319
fontFamily: '"Noto Sans SC", system-ui, sans-serif',
320320
}}
321321
>
@@ -331,13 +331,13 @@ function ConceptNode({ id, data, selected }) {
331331
<div
332332
className="px-3 py-1.5 flex items-center gap-1.5"
333333
style={{
334-
borderTop: '1px solid #e8e8e8',
334+
borderTop: '1px solid var(--border-subtle)',
335335
}}
336336
>
337-
<span style={{ color: '#bbb' }}>{sourceIcon}</span>
337+
<span style={{ color: 'var(--text-faint)' }}>{sourceIcon}</span>
338338
<span
339339
className="text-[10px] truncate max-w-[120px]"
340-
style={{ color: '#888', fontFamily: '"Noto Sans SC", system-ui, sans-serif' }}
340+
style={{ color: 'var(--text-muted)', fontFamily: '"Noto Sans SC", system-ui, sans-serif' }}
341341
>
342342
{safeString(data.source)}
343343
</span>
@@ -347,7 +347,7 @@ function ConceptNode({ id, data, selected }) {
347347
{/* 悬停展开提示 */}
348348
{hasRichContent && !isExpanded && (
349349
<div className="absolute bottom-1 left-1/2 -translate-x-1/2">
350-
<div className="flex items-center gap-1 text-[10px] opacity-40 hover:opacity-80 transition-opacity" style={{ color: '#888' }}>
350+
<div className="flex items-center gap-1 text-[10px] opacity-40 hover:opacity-80 transition-opacity" style={{ color: 'var(--text-muted)' }}>
351351
<svg className="w-3 h-3 animate-bounce" fill="none" stroke="currentColor" viewBox="0 0 24 24">
352352
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
353353
</svg>

0 commit comments

Comments
 (0)