Commit 84410c3f authored by edy's avatar edy

feat(ui): align homepage showcase, expert navigation, and status chip with Windows client

- Homepage: replace starter prompts with 5 digital-employee showcase cards
  (geo/xiaohongshu/douyin/planner/wechat) that navigate to expert projects
- Status chip: change "已就绪" to "运行中" with green dot, green text, green border
- switchExpert: set viewMode after switchProject completes (safer ordering)
- manifest.json: upgrade geo & wechat from home-chat-shortcut to standalone,
  add xhs & douyin standalone entries with full projectMatchKeywords
- Resolve expert key/visual key for planner, wechat, geo, zhihu
- Add ProjectRole shared type; enrich geo expert guide with 4 default roles
- CSS: remove conversation-workspace border/background/shadow, add overflow
  visible for card hover scale, match StatusChip positive tone to Windows client
- Fix /geo/ regex to word-boundary to avoid false substring matches
- Fix activeExpertGuide roles not falling back to guide defaults
- Fix expertMatchesCategory missing wechat/公众号 in isContent regex
- Fix resolveExpertKey planner regex missing 内容账号规划 keyword
Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
parent 0ddb0a2e
[
{
"id": "content-account-planning",
"name": "内容账号规划专家",
"name": "内容创作者",
"entryMode": "standalone",
"projectMatchKeywords": ["内容账号规划", "账号规划", "content account planning"],
"promptFile": "content-account-planning.md",
......@@ -9,12 +9,44 @@
},
{
"id": "zhihu",
"name": "知乎专家",
"name": "知乎策略师",
"entryMode": "standalone",
"projectMatchKeywords": ["知乎", "zhihu"],
"promptFile": "zhihu.md",
"description": "负责知乎回答、文章、选题与知乎平台表达方式优化。"
},
{
"id": "xhs",
"name": "小红书运营",
"entryMode": "standalone",
"projectMatchKeywords": ["小红书", "红书", "rednote", "xhs", "种草", "笔记"],
"promptFile": "xhs.md",
"description": "负责小红书笔记、图文、种草内容、选题与平台表达方式优化。"
},
{
"id": "douyin",
"name": "抖音专家",
"entryMode": "standalone",
"projectMatchKeywords": ["抖音", "douyin", "短视频", "直播", "口播", "带货"],
"promptFile": "douyin.md",
"description": "负责抖音短视频脚本、选题、带货内容与平台运营策略。"
},
{
"id": "geo",
"name": "AI推荐引擎优化",
"entryMode": "standalone",
"projectMatchKeywords": ["geo", "aeo", "生成引擎优化", "geo策略"],
"promptFile": "geo.md",
"description": "负责 GEO/AEO 策略、生成引擎优化、AI 搜索可见性与内容结构优化。"
},
{
"id": "wechat-official-account",
"name": "微信公众号运营",
"entryMode": "standalone",
"projectMatchKeywords": ["公众号", "微信公众号", "推文", "排版", "公众号文章"],
"promptFile": "wechat-official-account.md",
"description": "负责公众号选题、文章结构、排版优化与粉丝运营转化。"
},
{
"id": "sales-champion",
"name": "销售冠军",
......@@ -22,13 +54,6 @@
"description": "跳转到首页对话,继续处理销售目标拆解、成交话术、异议处理与转化推进。",
"starterPrompt": "我想以销售冠军的方式推进成交,请帮我梳理目标客户、沟通话术、异议处理和转化动作。"
},
{
"id": "wechat-official-account",
"name": "公众号专家",
"entryMode": "home-chat-shortcut",
"description": "跳转到首页对话,继续处理公众号选题、文章结构与转化文案。",
"starterPrompt": "我想做公众号内容,请按公众号文章的写法帮我规划选题、结构和表达。"
},
{
"id": "x-platform",
"name": "X专家",
......@@ -50,13 +75,6 @@
"description": "跳转到首页对话,继续处理海报主题、卖点提炼与文案结构。",
"starterPrompt": "我想做海报内容,请帮我先整理主题、卖点层级、标题和版面文案。"
},
{
"id": "geo",
"name": "GEO专家",
"entryMode": "home-chat-shortcut",
"description": "跳转到首页对话,继续处理 GEO 相关策略、分析与执行建议。",
"starterPrompt": "我想做 GEO 方向内容,请先帮我明确目标、策略框架和执行重点。"
},
{
"id": "precision-leads",
"name": "平台精准线索专家",
......
......@@ -94,6 +94,7 @@ import type { SmokeStreamSnapshot } from "./features/smoke/types";
import {
buildDouyinVideoStatusCard,
resolveExpertKey,
resolveExpertVisualKey,
} from "./features/experts/expertDisplay";
import { StatusChip } from "./components/ui/StatusChip";
import {
......@@ -1256,17 +1257,32 @@ export default function App() {
setPrompt(nextPrompt);
}
function handleExpertCardClick(visualKey: string) {
const entry = sidebarExpertEntries.find(
(entry) => resolveExpertVisualKey(entry.project, entry.definition) === visualKey
)
if (entry) {
handleExpertSelect(entry)
return
}
// Fallback: fill starter prompt if expert not found
const employee = homeChatCopy.employees.find((emp) => emp.visualKey === visualKey)
if (employee) {
applyStarterPrompt(employee.prompt)
}
}
function clearSelectedSkill() {
setSelectedSkillId(DEFAULT_SKILL.id);
setSkillMenuOpen(false);
}
async function switchExpert(projectId: string) {
setViewMode("experts");
if (workspace?.currentProjectId === projectId && viewMode === "experts") {
return;
}
await switchProject(projectId);
setViewMode("experts");
}
// 处理分类弹出层中的专家选择
......@@ -1392,7 +1408,7 @@ export default function App() {
noExpertsLabel: expertsPageCopy.noExperts,
starterQuestionsHint: ui.starterQuestionsHint,
homeEmptyTitle: homeChatCopy.emptyTitle,
homePrompts: homeChatCopy.prompts
homeShowcaseEmployees: homeChatCopy.employees
},
messages: {
messageListRef,
......@@ -1441,6 +1457,7 @@ export default function App() {
onContinueHomeIntent: continuePendingHomePromptInHome,
onSwitchHomeIntent: switchExpertAndContinuePendingHomePrompt,
onStarterPrompt: applyStarterPrompt,
onExpertCardClick: handleExpertCardClick,
renderThumbIcon: (direction) => <ThumbIcon direction={direction} />,
renderMarkdownContent,
buildDouyinVideoStatusCard,
......
......@@ -114,27 +114,37 @@ export const ui = {
export const homeChatCopy = {
title: "首页对话",
microcopy: "从一个实用小任务开始:整理 Excel、汇总资料、检索信息,或把需求拆成执行清单。",
emptyTitle: "准备好了,随时开始",
prompts: [
emptyTitle: "选择一位数字员工,开始协作",
employees: [
{
title: "海报内容",
description: "整理主题、卖点层级、标题和版面文案",
prompt: "我想做海报内容,请帮我先整理主题、卖点层级、标题和版面文案"
visualKey: "geo",
title: "AI推荐引擎优化",
description: "搜索与AI引擎可见性优化",
prompt: "你好,我想了解 AI推荐引擎优化 策略,请帮我梳理目标、策略框架和执行重点。"
},
{
title: "GEO 内容策略",
description: "明确目标、策略框架和执行重点",
prompt: "我想做 GEO 方向内容,请先帮我明确目标、策略框架和执行重点"
visualKey: "xiaohongshu",
title: "小红书运营",
description: "种草笔记、爆文策划与图文",
prompt: "你好,我想做小红书内容运营,请帮我策划一篇种草笔记。"
},
{
title: "精准线索获取",
description: "梳理目标人群、线索标准、触达话术和转化路径",
prompt: "我想做平台精准线索获取,请帮我梳理目标人群、线索标准、触达话术和转化路径"
visualKey: "douyin",
title: "抖音视频专家",
description: "短视频脚本、口播与分镜",
prompt: "你好,我想做抖音视频内容,请帮我策划短视频脚本。"
},
{
title: "销售成交推进",
description: "梳理目标客户、沟通话术、异议处理和转化动作",
prompt: "我想以销售冠军的方式推进成交,请帮我梳理目标客户、沟通话术、异议处理和转化动作"
visualKey: "planner",
title: "内容创作者",
description: "多平台内容策划与图文创作",
prompt: "你好,我想做内容创作,请帮我策划多平台内容。"
},
{
visualKey: "wechat",
title: "微信公众号运营",
description: "公众号文章策划与涨粉策略",
prompt: "你好,我想做微信公众号运营,请帮我策划公众号内容。"
}
]
} as const;
......
......@@ -28,6 +28,13 @@ interface HomeStarterPrompt {
prompt: string
}
interface HomeShowcaseEmployee {
visualKey: ExpertVisualKey
title: string
description: string
prompt: string
}
interface ConversationWorkspaceStatusProps {
panelActions: ReactNode
showInlineStartupNotice: boolean
......@@ -66,7 +73,7 @@ interface ConversationWorkspaceEmptyStateProps {
noExpertsLabel: string
starterQuestionsHint: string
homeEmptyTitle: string
homePrompts: readonly HomeStarterPrompt[]
homeShowcaseEmployees: readonly HomeShowcaseEmployee[]
}
interface ConversationWorkspaceMessagesProps {
......@@ -118,6 +125,7 @@ interface ConversationWorkspaceActionsProps {
onContinueHomeIntent: () => void | Promise<unknown>
onSwitchHomeIntent: () => void | Promise<unknown>
onStarterPrompt: (prompt: string) => void
onExpertCardClick: (visualKey: string) => void
renderThumbIcon: (direction: MessageReaction) => ReactNode
renderMarkdownContent: (
content: string,
......@@ -225,18 +233,21 @@ export function ConversationWorkspaceView({
<div className="home-empty-copy">
<strong className="home-empty-title">{emptyState.homeEmptyTitle}</strong>
</div>
<div className="starter-prompt-list" aria-label="可选任务入口">
{emptyState.homePrompts.map((item) => (
<div className="home-showcase-grid" aria-label="数字员工展示">
{emptyState.homeShowcaseEmployees.map((emp) => (
<button
key={item.prompt}
key={emp.visualKey}
type="button"
className="starter-prompt"
title={item.prompt}
aria-label={item.prompt}
onClick={() => actions.onStarterPrompt(item.prompt)}
className="home-showcase-card"
data-visual={emp.visualKey}
onClick={() => actions.onExpertCardClick(emp.visualKey)}
aria-label={emp.title}
title={emp.description}
>
<span className="starter-prompt-title">{item.title}</span>
<span className="starter-prompt-desc">{item.description}</span>
<span className="home-showcase-icon" data-visual={emp.visualKey}>
{actions.renderExpertIcon(emp.visualKey)}
</span>
<span className="home-showcase-name">{emp.title}</span>
</button>
))}
</div>
......
......@@ -6,7 +6,7 @@ import type { MessageTraceState } from "./useMessageTraces"
type ViewMode = "chat" | "experts" | "tasks" | "automation" | "plugins" | "settings" | "knowledge"
type MessageReaction = "up" | "down"
export type ExpertKey = "xiaohongshu" | "douyin" | "browser" | "general"
export type ExpertKey = "xiaohongshu" | "douyin" | "browser" | "general" | "planner" | "wechat" | "geo" | "zhihu"
export type MessageListMessage = ChatMessage & {
streamState?: "streaming" | "error"
......
import type { ReactNode, RefObject } from "react"
import type { ProjectRole } from "@qjclaw/shared-types"
import { Panel } from "../../components/ui/Panel"
import { ChatWorkspace } from "../chat/ChatWorkspace"
export type ExpertKey = "xiaohongshu" | "douyin" | "browser" | "general"
export type ExpertKey = "xiaohongshu" | "douyin" | "browser" | "general" | "planner" | "wechat" | "geo" | "zhihu"
export type ExpertVisualKey =
| "xiaohongshu"
......@@ -40,6 +41,7 @@ export interface ExpertGuideContent {
placeholder?: string
prompts: string[]
starterPrompts?: ExpertStarterPrompt[]
roles?: ProjectRole[]
}
interface ExpertsViewProps {
......
......@@ -84,7 +84,7 @@ export function ExpertCategoryIcon({ kind }: { kind: ExpertCategoryId }) {
function expertMatchesCategory(entry: SidebarExpertEntry, categoryId: ExpertCategoryId): boolean {
const expertSeed = `${entry.definition.id} ${entry.displayName}`.toLowerCase()
const isContent = /xiaohongshu|xhs|rednote|小红书|douyin|抖音|content-account|内容账号规划|zhihu|知乎/.test(expertSeed)
const isContent = /xiaohongshu|xhs|rednote|小红书|douyin|抖音|content-account|内容账号规划|zhihu|知乎|wechat|公众号|微信/.test(expertSeed)
const isAcquisition = /geo|precision-leads|精准线索|线索/.test(expertSeed)
const isSales = /sales-champion|销售冠军|销售/.test(expertSeed)
......
......@@ -34,7 +34,7 @@ export function getWorkspaceStatusLabel(chatLaunchState: ChatLaunchState, isBoun
return "\u5f85\u7ed1\u5b9a"
}
if (chatLaunchState === "ready") {
return "\u5df2\u5c31\u7eea"
return "\u8fd0\u884c\u4e2d"
}
if (chatLaunchState === "error") {
return "\u5f02\u5e38"
......
......@@ -69,8 +69,15 @@ export function useSidebarModel({
}, [expertPageProjects, viewMode, visibleProjects, workspace?.currentProjectId])
const activeExpertKey = useMemo(() => resolveExpertKey(activeProject), [activeProject])
const activeExpertVisualKey = useMemo(() => resolveExpertVisualKey(activeProject), [activeProject])
const activeExpertName = useMemo(() => getProjectDisplayName(activeProject, defaultChatName), [activeProject, defaultChatName])
const activeExpertGuide = useMemo(() => getExpertGuideContent(activeProject), [activeProject])
const activeExpertName = useMemo(() => {
const entry = [...standaloneExpertEntries, ...projectExpertEntries]
.find((e) => e.project?.id === activeProject?.id)
return entry?.displayName ?? getProjectDisplayName(activeProject, defaultChatName)
}, [activeProject, defaultChatName, standaloneExpertEntries, projectExpertEntries])
const activeExpertGuide = useMemo(() => {
const guide = getExpertGuideContent(activeProject)
return { ...guide, roles: activeProject?.roles ?? guide.roles }
}, [activeProject])
return {
projects,
......
......@@ -49,7 +49,7 @@ export const mockTaskPanelItems: TaskPanelItem[] = [
{
id: "mock-task-content-plan",
date: getDefaultTaskPanelDate(),
expertName: "内容账号规划专家",
expertName: "内容创作者",
taskTitle: "整理本周选题方向与发布节奏",
status: "running",
statusDetail: "正在汇总账号定位、目标人群和栏目节奏",
......@@ -67,7 +67,7 @@ export const mockTaskPanelItems: TaskPanelItem[] = [
{
id: "mock-task-content-review",
date: getDefaultTaskPanelDate(),
expertName: "内容账号规划专家",
expertName: "内容创作者",
taskTitle: "复盘昨日内容表现",
status: "completed",
statusDetail: "已完成互动数据摘要与优化建议",
......@@ -113,7 +113,7 @@ export const mockTaskPanelItems: TaskPanelItem[] = [
{
id: "mock-task-zhihu-research",
date: getDefaultTaskPanelDate(),
expertName: "知乎专家",
expertName: "知乎策略师",
taskTitle: "整理竞品问答素材",
status: "running",
statusDetail: "正在提取高赞回答结构和关键词",
......
......@@ -39,7 +39,7 @@ function emitMockChatStreamEvent(event: ChatStreamEvent) {
const mockExpertDefinitions: ExpertDefinition[] = [
{
id: "content-account-planner",
name: "内容账号规划专家",
name: "内容创作者",
entryMode: "standalone",
description: "聚焦内容账号定位、栏目规划与选题节奏。",
starterPrompt: "我想做一个内容账号,请先帮我梳理定位、目标人群、栏目结构和更新节奏。",
......@@ -49,7 +49,7 @@ const mockExpertDefinitions: ExpertDefinition[] = [
},
{
id: "zhihu",
name: "知乎专家",
name: "知乎策略师",
entryMode: "standalone",
description: "聚焦知乎回答选题、结构设计与持续输出。",
starterPrompt: "我想做知乎内容,请先帮我梳理选题方向、回答结构和更新节奏。",
......@@ -59,10 +59,11 @@ const mockExpertDefinitions: ExpertDefinition[] = [
},
{
id: "wechat-official-account",
name: "公众号专家",
entryMode: "home-chat-shortcut",
description: "从首页对话继续处理公众号选题、排版与发布需求。",
starterPrompt: "我现在要做公众号内容,请按公众号的结构和语气协助我。",
name: "微信公众号运营",
entryMode: "standalone",
description: "聚焦公众号文章策划、涨粉策略与内容运营。",
starterPrompt: "我想做微信公众号运营,请先帮我梳理选题方向、文章结构和涨粉策略。",
promptFile: "wechat.md",
promptAvailable: true,
projectMatchKeywords: ["公众号", "wechat"]
},
......@@ -95,10 +96,11 @@ const mockExpertDefinitions: ExpertDefinition[] = [
},
{
id: "geo",
name: "GEO专家",
entryMode: "home-chat-shortcut",
description: "从首页对话继续处理 GEO 相关策略与内容。",
starterPrompt: "我现在要处理 GEO 相关需求,请先帮我梳理目标、约束和执行方向。",
name: "AI推荐引擎优化",
entryMode: "standalone",
description: "搜索与AI引擎可见性优化",
starterPrompt: "你好,我想了解 AI推荐引擎优化 策略,请帮我梳理目标、策略框架和执行重点。",
promptFile: "geo.md",
promptAvailable: true,
projectMatchKeywords: ["geo"]
},
......@@ -123,10 +125,12 @@ const mockExpertDefinitions: ExpertDefinition[] = [
];
const mockProjects: WorkspaceSummary["projects"] = [
{ id: "content-account-planner", name: "content-account-planner", displayName: "内容账号规划专家", version: "demo-project", updatedAt: new Date().toISOString(), skillCount: 2, ready: true, platform: "planner" },
{ id: "zhihu", name: "zhihu-expert", displayName: "知乎专家", version: "demo-project", updatedAt: new Date().toISOString(), skillCount: 2, ready: true, platform: "zhihu" },
{ id: "xiaohongshu", name: "openclaw-xiaohongshu-skills-delivery", displayName: "小红书专家", version: "demo-project", updatedAt: new Date().toISOString(), skillCount: 2, ready: true, platform: "xiaohongshu" },
{ id: "content-account-planner", name: "content-account-planner", displayName: "内容创作者", version: "demo-project", updatedAt: new Date().toISOString(), skillCount: 2, ready: true, platform: "planner" },
{ id: "zhihu", name: "zhihu-expert", displayName: "知乎策略师", version: "demo-project", updatedAt: new Date().toISOString(), skillCount: 2, ready: true, platform: "zhihu" },
{ id: "xiaohongshu", name: "openclaw-xiaohongshu-skills-delivery", displayName: "小红书运营专家", version: "demo-project", updatedAt: new Date().toISOString(), skillCount: 2, ready: true, platform: "xiaohongshu" },
{ id: "douyin", name: "openclaw-douyin-skills-delivery", displayName: "抖音专家", version: "demo-project", updatedAt: new Date().toISOString(), skillCount: 2, ready: true, platform: "douyin" },
{ id: "wechat-official-account", name: "wechat-official-account", displayName: "微信公众号运营", version: "demo-project", updatedAt: new Date().toISOString(), skillCount: 2, ready: true, platform: "wechat" },
{ id: "geo", name: "geo", displayName: "AI推荐引擎优化", version: "demo-project", updatedAt: new Date().toISOString(), skillCount: 2, ready: true, platform: "geo" },
{ id: "browser-ops", name: "browser-ops", displayName: "浏览器自动化专家", version: "demo-project", updatedAt: new Date().toISOString(), skillCount: 3, ready: true, platform: "browser" }
];
......@@ -150,7 +154,7 @@ const mockProjectIntentSuggestions: Record<string, ProjectIntentSuggestion> = {
xiaohongshu: {
projectId: "xiaohongshu",
projectName: "openclaw-xiaohongshu-skills-delivery",
projectDisplayName: "小红书专家",
projectDisplayName: "小红书运营",
score: 0.96,
confidence: "high",
reason: "命中了小红书内容创作与发布场景",
......@@ -173,7 +177,7 @@ let mockAutomationTasks: AutomationTask[] = [
title: "每日内容复盘",
prompt: "整理昨天内容表现,并给出今天的优化建议。",
expertId: "content-account-planner",
expertName: "内容账号规划专家",
expertName: "内容创作者",
enabled: true,
schedule: { kind: "daily", time: "09:30" },
nextRunAt: new Date(Date.now() + 60 * 60 * 1000).toISOString(),
......
......@@ -1173,7 +1173,22 @@
color: #4f6965;
box-shadow: inset 0 0 0 1px rgba(214, 228, 223, 0.92);
}
.status-chip.positive { background: rgba(16, 185, 129, 0.12); color: #0f7f59; }
.status-chip.positive {
background: rgba(16, 185, 129, 0.14);
color: #0f7f59;
border: 1px solid rgba(16, 185, 129, 0.28);
box-shadow: none;
}
.status-chip.positive::before {
content: "";
display: inline-block;
width: 6px;
height: 6px;
border-radius: 50%;
background: #10b981;
margin-right: 6px;
flex-shrink: 0;
}
.status-chip.warning { background: rgba(245, 158, 11, 0.14); color: #b46f0a; }
.tabs {
......
......@@ -35,7 +35,7 @@
cursor: not-allowed;
}
.shell.openclaw-theme button:hover:not(:disabled),
.shell.openclaw-theme button:not(.home-showcase-card):hover:not(:disabled),
.shell.openclaw-theme .catalog-item:hover,
.shell.openclaw-theme .plugin-flat-card:hover,
.shell.openclaw-theme .settings-section-card:hover,
......@@ -741,26 +741,14 @@
.shell.openclaw-theme .status-chip {
border-color: var(--revamp-border);
padding: 3px 9px;
background: rgba(248, 250, 252, 0.72);
background: var(--revamp-surface-soft);
color: var(--revamp-text-muted);
font-weight: 500;
box-shadow: inset 0 0 0 1px rgba(203, 213, 225, 0.7);
}
.conversation-shell .conversation-panel-actions {
gap: 6px;
}
.conversation-shell .conversation-panel-actions .status-chip {
min-height: 24px;
opacity: 0.82;
box-shadow: none;
}
.conversation-shell .conversation-panel-actions .status-chip.warning {
color: #9a5f08;
background: rgba(255, 251, 235, 0.76);
box-shadow: inset 0 0 0 1px rgba(253, 230, 138, 0.68);
.shell.openclaw-theme .status-chip.positive {
background: rgba(16, 185, 129, 0.14);
color: #0f7f59;
border: 1px solid rgba(16, 185, 129, 0.28);
}
.conversation-shell .conversation-panel-kicker {
......@@ -772,14 +760,10 @@
.conversation-shell .conversation-workspace {
padding: 18px 20px 20px;
border-radius: 18px;
border: 1px solid rgba(147, 197, 253, 0.32);
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.82), rgba(248, 251, 255, 0.74)),
var(--revamp-gradient-soft);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.86),
var(--revamp-shadow-soft);
border-radius: 22px;
border: none;
background: transparent;
box-shadow: none;
}
.conversation-shell .message-list {
......@@ -797,6 +781,20 @@
align-content: center;
}
.conversation-shell :is(
.message-list-home,
.conversation-panel-body,
.conversation-workspace,
.chat-panel,
.conversation-panel,
.content-area,
.conversation-content-area,
.main-shell,
.conversation-main-layout
):has(.home-empty-state) {
overflow: visible;
}
.conversation-shell .message-list-xiaohongshu-empty:has(.expert-empty-state-xiaohongshu) {
align-content: center;
justify-items: center;
......@@ -811,7 +809,9 @@
gap: 26px;
padding: 16px 4px;
border: 0;
border-radius: 0;
background: transparent;
box-shadow: none;
}
.conversation-shell .home-empty-copy {
......@@ -1022,6 +1022,146 @@
}
}
/* ── Home Showcase Grid (Digital Employees) ──────────────────── */
.conversation-shell .home-showcase-grid {
width: min(100%, 780px);
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 14px;
}
/* Brand colour tokens per expert */
.conversation-shell .home-showcase-card[data-visual="geo"] {
--brand: #3b82f6;
--brand-soft: rgba(59, 130, 246, 0.12);
--brand-glow: rgba(59, 130, 246, 0.18);
}
.conversation-shell .home-showcase-card[data-visual="xiaohongshu"] {
--brand: #f43f5e;
--brand-soft: rgba(244, 63, 94, 0.1);
--brand-glow: rgba(244, 63, 94, 0.16);
}
.conversation-shell .home-showcase-card[data-visual="douyin"] {
--brand: #06b6d4;
--brand-soft: rgba(6, 182, 212, 0.1);
--brand-glow: rgba(6, 182, 212, 0.16);
}
.conversation-shell .home-showcase-card[data-visual="planner"] {
--brand: #a855f7;
--brand-soft: rgba(168, 85, 247, 0.1);
--brand-glow: rgba(168, 85, 247, 0.16);
}
.conversation-shell .home-showcase-card[data-visual="wechat"] {
--brand: #22c55e;
--brand-soft: rgba(34, 197, 94, 0.1);
--brand-glow: rgba(34, 197, 94, 0.16);
}
.conversation-shell .home-showcase-card {
position: relative;
z-index: 0;
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
padding: 22px 10px;
border-radius: 24px;
border: 1px solid color-mix(in srgb, var(--brand) 18%, #cbd5e1);
background: #ffffff;
color: #243044;
text-align: center;
cursor: pointer;
transition:
transform 0.28s cubic-bezier(0.34, 1.56, 0.64, 1),
box-shadow 0.28s ease,
border-color 0.28s ease;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
}
/* Bottom micro-glow bar */
.conversation-shell .home-showcase-card::after {
content: "";
position: absolute;
bottom: 1px;
left: 0;
right: 0;
height: 2px;
border-radius: 1px;
background: linear-gradient(90deg, transparent, var(--brand), transparent);
opacity: 0.38;
transition: opacity 0.3s ease, transform 0.3s ease;
transform: scaleX(0.52);
transform-origin: center;
}
.conversation-shell .home-showcase-card:hover {
transform: scale(1.2);
z-index: 10;
border-color: color-mix(in srgb, var(--brand) 52%, transparent);
background: #ffffff;
box-shadow: 0 16px 40px var(--brand-glow), 0 0 0 1px var(--brand-soft);
}
.conversation-shell .home-showcase-card:hover::after {
opacity: 0.72;
transform: scaleX(0.76);
}
.conversation-shell .home-showcase-card:focus-visible {
outline: 2px solid var(--brand);
outline-offset: 3px;
box-shadow: 0 0 0 5px var(--brand-soft);
}
/* ── Icon ──────────────────────────────────────────────────── */
.conversation-shell .home-showcase-icon {
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 16px;
background: linear-gradient(135deg, rgba(239, 246, 255, 0.96), rgba(219, 234, 254, 0.9));
transition: box-shadow 0.28s ease;
}
.conversation-shell .home-showcase-icon svg {
width: 28px;
height: 28px;
}
/* Per-brand icon backgrounds */
.conversation-shell .home-showcase-card[data-visual="xiaohongshu"] .home-showcase-icon {
background: linear-gradient(135deg, rgba(255, 241, 242, 0.97), rgba(255, 228, 230, 0.92));
}
.conversation-shell .home-showcase-card[data-visual="douyin"] .home-showcase-icon {
background: linear-gradient(135deg, rgba(236, 254, 255, 0.97), rgba(207, 250, 254, 0.92));
}
.conversation-shell .home-showcase-card[data-visual="wechat"] .home-showcase-icon {
background: linear-gradient(135deg, rgba(240, 253, 244, 0.97), rgba(220, 252, 231, 0.92));
}
.conversation-shell .home-showcase-card[data-visual="planner"] .home-showcase-icon {
background: linear-gradient(135deg, rgba(245, 243, 255, 0.97), rgba(237, 233, 254, 0.92));
}
/* Icon glow on card hover */
.conversation-shell .home-showcase-card:hover .home-showcase-icon {
box-shadow: 0 0 18px var(--brand-glow);
}
/* ── Name ──────────────────────────────────────────────────── */
.conversation-shell .home-showcase-name {
font-size: 13px;
font-weight: 600;
color: #1e293b;
line-height: 1.3;
letter-spacing: -0.01em;
}
/* ─────────────────────────────────────────────────────────── */
@media (max-width: 720px) {
.conversation-shell .home-empty-state {
width: min(100%, 620px);
......@@ -1032,6 +1172,15 @@
grid-template-columns: 1fr;
width: min(100%, 520px);
}
.conversation-shell .home-showcase-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
}
.conversation-shell .home-showcase-card {
padding: 18px 8px;
}
}
@media (max-height: 820px) {
......@@ -1066,6 +1215,10 @@
min-height: 84px;
padding: 12px 14px;
}
.conversation-shell .home-showcase-grid {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
@media (prefers-reduced-motion: reduce) {
......
......@@ -383,6 +383,14 @@ export interface ProjectPackageConfig {
entries: ProjectPackageEntry[];
}
export interface ProjectRole {
id: string;
name: string;
icon: string;
skillIds: string[];
description: string;
}
export interface ProjectSummary {
id: string;
name: string;
......@@ -397,6 +405,7 @@ export interface ProjectSummary {
platform?: string;
defaultEntryId?: string;
defaultEntryType?: ProjectPackageEntryType;
roles?: ProjectRole[];
}
export interface ProjectIntentSuggestion {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment