Skip to content

Commit ed8790d

Browse files
dvlin-devCodex
andauthored
feat: homepage redesign, brand color update, and model selector fix (#233)
* feat: homepage redesign, brand color update, and model selector fix - Fix membership model selector reverting to default on selection - Redesign homepage: replace FeatureAgents + FeaturePublish with 6-card Capabilities grid (Agents, Memory, Automations, Skills, Remote Agent, Publishing) - Add real product screenshot to hero section - Update hero slogan: "Agents create / Memory flow" - Update brand color from purple (#7C5CFC) to blue-purple (#455DD3) across www, PC, and mobile * fix: validate membership availability before preserving model selection Use resolveExternalThinkingProfile instead of isMembershipModelId to check if a membership model is still valid. This correctly handles logout and subscription expiry by falling back to a provider model. * fix: remove dead i18n keys and filter unavailable membership models - Remove orphaned home.hero.titlePrefix/titleAccent keys (title is now hardcoded as brand slogan) - Only index available membership models in thinking profile map, so unavailable models (tier downgrade) correctly fall back to a provider model * fix: fall back to available membership model when no provider matches When the current membership model becomes unavailable and no provider model is configured, check if the stored defaultModel is still a valid membership model before clearing the selection. --------- Co-authored-by: Codex <codex@example.com>
1 parent 5dd9c46 commit ed8790d

File tree

19 files changed

+173
-302
lines changed

19 files changed

+173
-302
lines changed

apps/moryflow/mobile/global.css

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@
6060
--color-tier-free-bg: rgba(107, 114, 128, 0.15);
6161
--color-tier-basic: #3b82f6;
6262
--color-tier-basic-bg: rgba(59, 130, 246, 0.15);
63-
--color-tier-pro: #8b5cf6;
64-
--color-tier-pro-bg: rgba(139, 92, 246, 0.15);
63+
--color-tier-pro: #455dd3;
64+
--color-tier-pro-bg: rgba(69, 93, 211, 0.15);
6565

6666
/* Chart colors */
6767
--color-chart-1: #e45a3b;
@@ -120,8 +120,8 @@
120120
--color-tier-free-bg: rgba(156, 163, 175, 0.2);
121121
--color-tier-basic: #60a5fa;
122122
--color-tier-basic-bg: rgba(96, 165, 250, 0.2);
123-
--color-tier-pro: #a78bfa;
124-
--color-tier-pro-bg: rgba(167, 139, 250, 0.2);
123+
--color-tier-pro: #6b7fe0;
124+
--color-tier-pro-bg: rgba(107, 127, 224, 0.2);
125125

126126
/* Chart colors */
127127
--color-chart-1: #3b82f6;

apps/moryflow/mobile/lib/tokens/base.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -303,11 +303,11 @@ const TOKEN_DEFINITIONS = {
303303
},
304304
tierPro: {
305305
cssVariable: 'tier-pro',
306-
values: { light: '#8b5cf6', dark: '#a78bfa' },
306+
values: { light: '#455dd3', dark: '#6b7fe0' },
307307
},
308308
tierProBg: {
309309
cssVariable: 'tier-pro-bg',
310-
values: { light: 'rgba(139, 92, 246, 0.15)', dark: 'rgba(167, 139, 250, 0.2)' },
310+
values: { light: 'rgba(69, 93, 211, 0.15)', dark: 'rgba(107, 127, 224, 0.2)' },
311311
},
312312

313313
// ═══════════════════════════════════════════════════════════

apps/moryflow/pc/src/renderer/components/chat-pane/hooks/use-chat-model-selection.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,12 @@ export const useChatModelSelection = (
147147
setModelGroups(groups);
148148

149149
const currentModelId = selectedModelIdRef.current;
150-
if (hasEnabledModelOption(groups, currentModelId)) {
150+
if (
151+
hasEnabledModelOption(groups, currentModelId) ||
152+
resolveExternalThinkingProfile?.(currentModelId)
153+
) {
151154
const nextLevel = resolveThinkingLevel({
152-
modelId: selectedModelIdRef.current,
155+
modelId: currentModelId,
153156
thinkingByModel: selectedThinkingByModel,
154157
modelGroups: groups,
155158
resolveExternalThinkingProfile,
@@ -158,10 +161,11 @@ export const useChatModelSelection = (
158161
return;
159162
}
160163

161-
const candidate = pickAvailableModelId({
164+
const defaultModelId = settings.model?.defaultModel;
165+
let candidate = pickAvailableModelId({
162166
groups,
163167
candidates: [
164-
settings.model?.defaultModel,
168+
defaultModelId,
165169
...settings.providers
166170
.filter((provider) => provider.enabled)
167171
.map((provider) =>
@@ -179,6 +183,10 @@ export const useChatModelSelection = (
179183
],
180184
});
181185

186+
if (!candidate && defaultModelId && resolveExternalThinkingProfile?.(defaultModelId)) {
187+
candidate = defaultModelId;
188+
}
189+
182190
updateSelection(candidate || '', { syncRemote: false });
183191
const nextLevel = resolveThinkingLevel({
184192
modelId: candidate || '',

apps/moryflow/pc/src/renderer/components/chat-pane/hooks/use-chat-pane-controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export const useChatPaneController = ({
8383
const { models: membershipModels, membershipEnabled, isAuthenticated } = useAuth();
8484
const membershipThinkingProfileByModelId = useMemo(() => {
8585
const entries = membershipModels
86-
.filter((model) => model.thinkingProfile)
86+
.filter((model) => model.available && model.thinkingProfile)
8787
.map((model) => [model.id, model.thinkingProfile] as const);
8888
return new Map(entries);
8989
}, [membershipModels]);

apps/moryflow/pc/src/renderer/styles/_variables.scss

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -62,17 +62,17 @@
6262
--tt-gray-dark-900: rgba(245, 245, 245, 1);
6363

6464
/* Brand colors */
65-
--tt-brand-color-50: rgba(239, 238, 255, 1);
66-
--tt-brand-color-100: rgba(222, 219, 255, 1);
67-
--tt-brand-color-200: rgba(195, 189, 255, 1);
68-
--tt-brand-color-300: rgba(157, 138, 255, 1);
69-
--tt-brand-color-400: rgba(122, 82, 255, 1);
70-
--tt-brand-color-500: rgba(98, 41, 255, 1);
71-
--tt-brand-color-600: rgba(84, 0, 229, 1);
72-
--tt-brand-color-700: rgba(75, 0, 204, 1);
73-
--tt-brand-color-800: rgba(56, 0, 153, 1);
74-
--tt-brand-color-900: rgba(43, 25, 102, 1);
75-
--tt-brand-color-950: hsla(257, 100%, 9%, 1);
65+
--tt-brand-color-50: rgba(238, 240, 251, 1);
66+
--tt-brand-color-100: rgba(217, 222, 250, 1);
67+
--tt-brand-color-200: rgba(179, 189, 240, 1);
68+
--tt-brand-color-300: rgba(141, 156, 230, 1);
69+
--tt-brand-color-400: rgba(105, 123, 220, 1);
70+
--tt-brand-color-500: rgba(69, 93, 211, 1);
71+
--tt-brand-color-600: rgba(55, 73, 185, 1);
72+
--tt-brand-color-700: rgba(42, 56, 158, 1);
73+
--tt-brand-color-800: rgba(33, 49, 131, 1);
74+
--tt-brand-color-900: rgba(26, 38, 104, 1);
75+
--tt-brand-color-950: hsla(230, 65%, 9%, 1);
7676

7777
/* Green */
7878
--tt-color-green-inc-5: hsla(129, 100%, 97%, 1);
@@ -183,7 +183,7 @@
183183
--tt-sidebar-bg-color: var(--tt-gray-light-100);
184184
--tt-scrollbar-color: var(--tt-gray-light-a-200);
185185
--tt-cursor-color: var(--tt-brand-color-500);
186-
--tt-selection-color: rgba(157, 138, 255, 0.2);
186+
--tt-selection-color: rgba(69, 93, 211, 0.2);
187187
--tt-card-bg-color: var(--white);
188188
--tt-card-border-color: var(--tt-gray-light-a-100);
189189
}
@@ -196,7 +196,7 @@
196196
--tt-sidebar-bg-color: var(--tt-gray-dark-100);
197197
--tt-scrollbar-color: var(--tt-gray-dark-a-200);
198198
--tt-cursor-color: var(--tt-brand-color-400);
199-
--tt-selection-color: rgba(122, 82, 255, 0.2);
199+
--tt-selection-color: rgba(105, 123, 220, 0.2);
200200
--tt-card-bg-color: var(--tt-gray-dark-50);
201201
--tt-card-border-color: var(--tt-gray-dark-a-50);
202202

apps/moryflow/www/CLAUDE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,9 @@ docker run -p 3000:3000 moryflow-www
124124

125125
- 消费 `@moryflow/ui/styles` 语义化 Token(`bg-background``text-foreground``bg-card` 等),与 PC 端统一
126126
- 暖中性底色:`background` (#F7F5F2)、`card` (#FCFAF7)
127-
- 品牌色扩展 token:`brand` (#7C5CFC)、`brand-light` (#A78BFA)、`brand-lighter` (#C4B5FD)、`brand-dark` (#622AFF)
127+
- 品牌色扩展 token:`brand` (#455DD3)、`brand-light` (#6B7FE0)、`brand-lighter` (#9AABE8)、`brand-dark` (#213183)
128128
- 字体:Inter 400~800(Google Fonts),通过字重和 tracking 建立层级;禁止 `font-serif`
129-
- 营销渐变:`gradient-hero-glow`紫色径向 glow)、`gradient-section-subtle`极浅紫区块背景
129+
- 营销渐变:`gradient-hero-glow`蓝紫色径向 glow)、`gradient-section-subtle`极浅蓝紫区块背景
130130
- 卡片以 `shadow-sm` + `hover:shadow-lg` 建立层次,而非纯边框
131131
- 动效:`useScrollReveal` / `useScrollRevealGroup` 驱动入场动画(fade-up / scale-up / stagger)
132132
- 禁止 float/glow/particle 等重动效
622 KB
Loading

apps/moryflow/www/src/components/__tests__/homepage-sections.spec.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,12 @@ vi.mock('../landing/AgentFirstHero', () => ({
2121
vi.mock('../landing/TrustStrip', () => ({
2222
TrustStrip: createMockSection('trust-strip'),
2323
}));
24-
vi.mock('../landing/FeatureAgents', () => ({
25-
FeatureAgents: createMockSection('feature-agents'),
24+
vi.mock('../landing/Capabilities', () => ({
25+
Capabilities: createMockSection('capabilities'),
2626
}));
2727
vi.mock('../landing/FeatureLocal', () => ({
2828
FeatureLocal: createMockSection('feature-local'),
2929
}));
30-
vi.mock('../landing/FeaturePublish', () => ({
31-
FeaturePublish: createMockSection('feature-publish'),
32-
}));
3330
vi.mock('../landing/CompareStripSection', () => ({
3431
CompareStripSection: createMockSection('compare'),
3532
}));
@@ -38,13 +35,12 @@ vi.mock('../landing/DownloadCTA', () => ({
3835
}));
3936

4037
describe('homepage section configuration', () => {
41-
test('frozen homepage order is hero → trust-strip → feature-agents → feature-local → feature-publish → compare → download-cta', () => {
38+
test('frozen homepage order is hero → trust-strip → capabilities → feature-local → compare → download-cta', () => {
4239
expect(HOME_SECTION_ORDER).toEqual([
4340
'hero',
4441
'trust-strip',
45-
'feature-agents',
42+
'capabilities',
4643
'feature-local',
47-
'feature-publish',
4844
'compare',
4945
'download-cta',
5046
]);

apps/moryflow/www/src/components/landing/AgentFirstHero.tsx

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
'use client';
88

99
import { Link } from '@tanstack/react-router';
10-
import { Download, Monitor, Star } from 'lucide-react';
10+
import { Download, Star } from 'lucide-react';
1111
import { Button } from '@moryflow/ui';
1212
import { usePlatformDetection } from '@/lib/platform';
1313
import { useLocale } from '@/routes/{-$locale}/route';
@@ -54,10 +54,10 @@ export function AgentFirstHero() {
5454
ref={titleRef}
5555
className="text-4xl sm:text-5xl md:text-6xl lg:text-7xl font-extrabold text-foreground mb-6 leading-[1.1] tracking-tight"
5656
>
57-
{t('home.hero.titlePrefix', locale)}
57+
Agents create
5858
<br />
5959
<span className="bg-gradient-to-r from-brand to-brand-light bg-clip-text text-transparent">
60-
{t('home.hero.titleAccent', locale)}
60+
Memory flow
6161
</span>
6262
</h1>
6363

@@ -107,13 +107,19 @@ export function AgentFirstHero() {
107107
<span className="text-sm text-tertiary">{t('home.hero.freeToStart', locale)}</span>
108108
</div>
109109

110-
{/* Product screenshot placeholder */}
110+
{/* Product screenshot */}
111111
<div
112112
ref={screenshotRef}
113-
className="mt-16 mx-auto max-w-4xl rounded-2xl bg-card border border-border/50 aspect-[16/10] flex flex-col items-center justify-center gap-4 p-8"
113+
className="mt-16 mx-auto max-w-4xl rounded-2xl border border-border/50 overflow-hidden shadow-lg"
114114
>
115-
<Monitor size={48} className="text-muted-foreground/40" />
116-
<p className="text-sm text-muted-foreground">Screenshot: Moryflow workspace overview</p>
115+
<img
116+
src="/home-all-dark.png"
117+
alt="Moryflow workspace overview"
118+
width={1920}
119+
height={1200}
120+
className="w-full h-auto"
121+
loading="eager"
122+
/>
117123
</div>
118124
</div>
119125
</section>
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* [PROPS]: None
3+
* [EMITS]: None
4+
* [POS]: Capabilities grid — 6 icon cards showcasing core product capabilities
5+
*/
6+
7+
'use client';
8+
9+
import { Bot, Brain, Globe, MessageCircle, Puzzle, Zap } from 'lucide-react';
10+
import type { LucideIcon } from 'lucide-react';
11+
import { useLocale } from '@/routes/{-$locale}/route';
12+
import { t } from '@/lib/i18n';
13+
import { useScrollReveal, useScrollRevealGroup } from '@/hooks/useScrollReveal';
14+
15+
const CARDS: { icon: LucideIcon; titleKey: string; descKey: string }[] = [
16+
{ icon: Bot, titleKey: 'home.features.agentTitle', descKey: 'home.features.agentDesc' },
17+
{ icon: Brain, titleKey: 'home.features.memoryTitle', descKey: 'home.features.memoryDesc' },
18+
{
19+
icon: Zap,
20+
titleKey: 'home.features.automationsTitle',
21+
descKey: 'home.features.automationsDesc',
22+
},
23+
{ icon: Puzzle, titleKey: 'home.features.skillsTitle', descKey: 'home.features.skillsDesc' },
24+
{
25+
icon: MessageCircle,
26+
titleKey: 'home.features.telegramTitle',
27+
descKey: 'home.features.telegramDesc',
28+
},
29+
{ icon: Globe, titleKey: 'home.features.publishTitle', descKey: 'home.features.publishDesc' },
30+
];
31+
32+
export function Capabilities() {
33+
const locale = useLocale();
34+
const headingRef = useScrollReveal<HTMLDivElement>({ animation: 'fade-up' });
35+
const gridRef = useScrollRevealGroup<HTMLDivElement>({ animation: 'scale-up', stagger: 80 });
36+
37+
return (
38+
<section
39+
className="py-24 sm:py-32 px-4 sm:px-6"
40+
style={{ background: 'var(--gradient-section-subtle)' }}
41+
>
42+
<div className="container mx-auto max-w-5xl">
43+
<div ref={headingRef} className="text-center mb-14">
44+
<h2 className="text-3xl sm:text-4xl font-bold text-foreground tracking-tight mb-4">
45+
{t('home.features.title', locale)}
46+
</h2>
47+
<p className="text-muted-foreground max-w-2xl mx-auto text-base sm:text-lg leading-relaxed">
48+
{t('home.features.subtitle', locale)}
49+
</p>
50+
</div>
51+
52+
<div ref={gridRef} className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
53+
{CARDS.map((card) => {
54+
const Icon = card.icon;
55+
return (
56+
<div
57+
key={card.titleKey}
58+
data-reveal-item
59+
className="rounded-2xl bg-card p-8 shadow-sm hover:shadow-lg transition-all duration-300"
60+
>
61+
<div
62+
className="w-12 h-12 rounded-xl bg-brand/10 flex items-center justify-center mb-5"
63+
aria-label={t(card.titleKey, locale)}
64+
>
65+
<Icon size={24} className="text-brand" aria-hidden="true" />
66+
</div>
67+
<h3 className="text-lg font-semibold text-foreground mb-2">
68+
{t(card.titleKey, locale)}
69+
</h3>
70+
<p className="text-sm text-muted-foreground leading-relaxed">
71+
{t(card.descKey, locale)}
72+
</p>
73+
</div>
74+
);
75+
})}
76+
</div>
77+
</div>
78+
</section>
79+
);
80+
}

0 commit comments

Comments
 (0)