feat(home): 首页开场语改成一行问候,占位符改「想聊什么都可以」
Independent Staging Quality Gate / validate (push) Failing after 6m23s
Independent Staging Quality Gate / publish (push) Skipped

开场语从「称呼行 + 追问句」收成 claude.ai 式的单行问候:时段池、
星期池、回访池按顺序拼成一个数组,按 variantSelection 确定性取一条;
名字为空时跳过带称呼的条目。副标题行与 .starter-salutation 下线,
h1 降到 clamp(24px, 2.8vw, 30px) / line-height 1.25。追问改由输入框
占位符承担。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JUei7K13cYxLHE3Axe4A45
This commit is contained in:
Jesse_Chen
2026-09-17 15:54:20 +00:00
co-authored by Claude Fable 5.1
parent dd281dd9c9
commit ff0427cf08
13 changed files with 318 additions and 97 deletions
+2 -1
View File
@@ -721,7 +721,8 @@ export default function Home() {
);
const onboardingFormActive = !profileComplete && onboardingStep !== "name";
const birthTimeContinueHint = onboardingStep === "birth" ? birthTimeDraftReadyHint(profileDraft) : "";
const starterGreeting = createStartGreetingParts(profile.name, new Date(), starterGreetingSelection);
const starterHasPriorSessions = sessions.some((session) => session.id !== activeSession?.id);
const starterGreeting = createStartGreetingParts(profile.name, new Date(), starterGreetingSelection, starterHasPriorSessions);
const conversationAnchor = useConversationScrollAnchor(
conversation,
!rectificationSurfaceOpen && !starterHomeVisible,
+6 -10
View File
@@ -2089,29 +2089,25 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class
padding: var(--space-8) 0 var(--space-5);
}
/* The greeting: one line of salutation, one question. It used to be an h1 at
/* The greeting: one line, two to five words. It used to be an h1 at
clamp(34px, 5vw, 52px) inside a bordered hero card, with two 132px product
cards under it — together about 800px sitting on top of the composer. */
cards under it — together about 800px sitting on top of the composer; then a
salutation line above a question. The question moved into the composer
placeholder, so there is no second line to size against. */
.starter-greeting-block {
width: 100%;
min-width: 0;
text-align: center;
}
.starter-salutation {
margin: 0 0 var(--space-2);
color: var(--color-ink-tertiary);
font-size: var(--type-body-sm);
}
.starter-greeting-block h1 {
margin: 0;
color: var(--color-ink);
font-family: var(--font-display);
font-size: clamp(26px, 3.2vw, 34px);
font-size: clamp(24px, 2.8vw, 30px);
font-weight: 500;
letter-spacing: -.02em;
line-height: 1.3;
line-height: 1.25;
text-wrap: balance;
}
+4 -3
View File
@@ -19,14 +19,15 @@ import type { RectificationEntrySummary } from "@/lib/rectification-entry";
*/
export type StarterGreetingProps = {
readonly starterGreeting: { readonly salutation: string; readonly question: string };
readonly starterGreeting: { readonly text: string };
};
/* One line. The salutation/question pair it replaces asked the user's question
for them; the composer placeholder asks now. */
export function StarterGreeting({ starterGreeting }: StarterGreetingProps) {
return (
<section className="starter-greeting-block" aria-labelledby="starter-heading">
<p className="starter-salutation">{starterGreeting.salutation}</p>
<h1 id="starter-heading">{starterGreeting.question}</h1>
<h1 id="starter-heading">{starterGreeting.text}</h1>
</section>
);
}
@@ -69,7 +69,7 @@ export function ordinaryComposerPlaceholder(input: {
return input.presetMessageFinished ? "输入你的称呼" : "Jyotisha 正在输入…";
}
if (input.creditsEmpty) return "余额不足,可在账户与点数中补充";
return "例如:未来半年是否适合换工作?";
return "想聊什么都可以";
}
export function rectificationOrdinaryComposerReopen(input: {
+61 -47
View File
@@ -1,72 +1,86 @@
type GreetingPeriod = "morning" | "noon" | "afternoon" | "evening" | "late-night";
type GreetingVariant = {
readonly salutation: (name: string) => string;
readonly question: string;
/**
* One line, two to five words, no follow-up question.
*
* The homepage opening used to be a salutation plus a question ("
* {}") an office voice that answered for the user
* before they had said anything. Asking is the composer placeholder's job now;
* the greeting only greets. No metaphors ( / / ), no emoji, no period.
*/
const NAME_TOKEN = "{名字}";
const periodGreetings: Record<GreetingPeriod, readonly string[]> = {
morning: ["早上好", "早上好,{名字}", "早呀,{名字}"],
noon: ["中午好", "中午好,{名字}"],
afternoon: ["下午好", "下午好,{名字}"],
evening: ["晚上好", "晚上好,{名字}", "今天过得怎么样?"],
"late-night": ["夜猫子,晚上好", "还没睡呀,{名字}", "晚安前再聊一会"],
};
const greetingVariants: Record<GreetingPeriod, readonly GreetingVariant[]> = {
morning: [
{ salutation: (name) => `早上好,${name}`, question: "今天最想先看什么?" },
{ salutation: (name) => `${name},早安。`, question: "今天最该关注哪件事?" },
{ salutation: (name) => `早上好,${name}`, question: "想从哪个问题开始?" },
],
noon: [
{ salutation: (name) => `中午好,${name}`, question: "现在最想理清哪件事?" },
{ salutation: (name) => `${name},中午好。`, question: "什么问题最需要方向?" },
{ salutation: (name) => `午间好,${name}`, question: "事业、关系或选择,想先聊哪个?" },
],
afternoon: [
{ salutation: (name) => `${name},下午好。`, question: "现在最想推进哪件事?" },
{ salutation: (name) => `下午好,${name}`, question: "今天想先理清什么?" },
{ salutation: (name) => `${name},下午好。`, question: "事业、关系或选择,想先聊哪个?" },
],
evening: [
{ salutation: (name) => `晚上好,${name}`, question: "今天最挂心的是哪件事?" },
{ salutation: (name) => `${name},晚上好。`, question: "此刻最想聊哪件事?" },
{ salutation: (name) => `晚上好,${name}`, question: "把心里的问题告诉我吧。" },
],
"late-night": [
{ salutation: (name) => `夜深了,${name}`, question: "此刻最想问什么?" },
{ salutation: (name) => `${name},还没休息吗?`, question: "想从哪件事说起?" },
{ salutation: (name) => `这么晚还醒着,${name}`, question: "直接说说最在意的问题吧。" },
],
/** Keyed by `Date#getDay()`. Days with nothing worth saying stay out of the pool. */
const weekdayGreetings: Readonly<Record<number, readonly string[]>> = {
0: ["周日,慢一点"],
1: ["周一了,{名字}"],
5: ["周五快乐"],
6: ["周末愉快,{名字}"],
};
/** Only reachable once the account has at least one session behind it. */
const returningGreetings: readonly string[] = [
"又见面了,{名字}",
"欢迎回来",
"嗨,{名字}",
"最近还好吗,{名字}",
];
function greetingPeriod(hour: number): GreetingPeriod {
if (hour >= 5 && hour < 11) return "morning";
if (hour >= 11 && hour < 14) return "noon";
if (hour >= 14 && hour < 18) return "afternoon";
if (hour >= 18 && hour < 23) return "evening";
return "late-night";
}
/**
* Every line this moment can produce, in pool order: daypart, weekday, returning.
* A blank name drops the lines that would print an empty one; the daypart pool
* always keeps a nameless line, so the result is never empty.
*/
function starterGreetingPool(name: string, now: Date, hasSessions: boolean): readonly string[] {
const templates = [
...periodGreetings[greetingPeriod(now.getHours())],
...(weekdayGreetings[now.getDay()] ?? []),
...(hasSessions ? returningGreetings : []),
];
return name ? templates : templates.filter((template) => !template.includes(NAME_TOKEN));
}
export type StartGreetingParts = {
readonly salutation: string;
readonly question: string;
readonly text: string;
};
export function createStartGreetingParts(
name: string,
now = new Date(),
variantSelection = Math.random(),
hasSessions = false,
): StartGreetingParts {
const displayName = name.trim() || "你好";
const hour = now.getHours();
const period: GreetingPeriod = hour >= 5 && hour < 11
? "morning"
: hour >= 11 && hour < 14
? "noon"
: hour >= 14 && hour < 18
? "afternoon"
: hour >= 18 && hour < 23 ? "evening" : "late-night";
const variants = greetingVariants[period];
const index = Math.min(Math.max(Math.floor(variantSelection * variants.length), 0), variants.length - 1);
const variant = variants[index]!;
return { salutation: variant.salutation(displayName), question: variant.question };
const displayName = name.trim();
const pool = starterGreetingPool(displayName, now, hasSessions);
const index = Math.min(Math.max(Math.floor(variantSelection * pool.length), 0), pool.length - 1);
return { text: pool[index]!.replaceAll(NAME_TOKEN, displayName) };
}
export function createStartGreeting(
name: string,
now = new Date(),
variantSelection = Math.random(),
hasSessions = false,
): string {
const parts = createStartGreetingParts(name, now, variantSelection);
return `${parts.salutation}${parts.question}`;
return createStartGreetingParts(name, now, variantSelection, hasSessions).text;
}
export function createOnboardingFallbackGreeting(name: string): string {
return `${name.trim()},从你此刻最关心的问题开始吧。`;
return `欢迎,${name.trim()}`;
}