feat(splash): simplify multi-character home layout
Docker Image / Build and Push Docker Image (push) Successful in 2m1s

This commit is contained in:
Codex
2026-07-23 13:27:16 +08:00
parent 770ce6b8fd
commit 79397af739
10 changed files with 67 additions and 65 deletions
@@ -12,7 +12,7 @@ describe("splash Tailwind components", () => {
expect(html).toContain("z-2");
expect(html).toContain("inline-flex");
expect(html).toContain("block h-auto w-auto");
expect(html).toContain("block h-auto w-[clamp(136px,38vw,158px)]");
expect(html).toContain("%2Fimages%2Fsplash%2Fic-logo-home.png");
});
@@ -30,10 +30,9 @@ describe("splash Tailwind components", () => {
it("renders SplashContent with Tailwind typography classes", () => {
const html = renderToStaticMarkup(<SplashContent characterName="Maya" />);
expect(html).toContain("flex flex-col gap-md");
expect(html).toContain("Welcome to Maya&#x27;s private world.");
expect(html).toContain("Just you and me.");
expect(html).toContain("font-(family-name:--font-athelas)");
expect(html).toContain("whitespace-pre-line");
expect(html).toContain("Welcome to Maya");
});
it("renders SplashButton as an always-ready action", () => {
@@ -12,8 +12,10 @@ import Image from "next/image";
export function SplashBackground({
src,
objectPosition,
}: {
src: string;
objectPosition?: string;
}) {
return (
<div className="absolute inset-0 z-0 overflow-hidden bg-splash-background">
@@ -24,6 +26,7 @@ export function SplashBackground({
priority
sizes="(max-width: 540px) 100vw, 540px"
className="object-cover object-center"
style={{ objectPosition }}
/>
</div>
);
+6 -8
View File
@@ -2,17 +2,15 @@
* Splash 主内容文案
*/
const APOSTROPHE = "'";
export function SplashContent({ characterName }: { characterName: string }) {
return (
<div className="z-2 flex flex-col gap-md">
<h1 className="m-0 whitespace-pre-line text-left font-(family-name:--font-athelas) text-[clamp(28px,7.8vw,30px)] font-bold italic leading-[1.28] text-white">
Welcome to {characterName}{APOSTROPHE}s secret hideout~
{"\n"}It{APOSTROPHE}s just the two of us now.
{"\n"}Feel free to whisper your
{"\n"}little secrets.
<div className="z-2 flex flex-col gap-1.5 text-left text-white">
<h1 className="m-0 max-w-105 font-(family-name:--font-athelas) text-[clamp(29px,8.2vw,35px)] font-bold leading-[1.08] text-white [text-shadow:0_2px_18px_rgba(0,0,0,0.42)]">
Welcome to {characterName}&apos;s private world.
</h1>
<p className="m-0 text-[clamp(15px,4.1vw,18px)] font-semibold leading-[1.4] text-white/92 [text-shadow:0_2px_12px_rgba(0,0,0,0.5)]">
Just you and me.
</p>
</div>
);
}
@@ -1,16 +1,15 @@
.card {
display: grid;
box-sizing: border-box;
width: 100%;
height: 80px;
grid-template-columns: auto 1fr;
align-items: center;
gap: 12px;
margin-top: var(--page-section-gap-lg, 26px);
padding: 12px 14px;
border: 1px solid rgba(255, 255, 255, 0.22);
border-radius: 24px;
background:
linear-gradient(135deg, rgba(255, 255, 255, 0.24), rgba(255, 255, 255, 0.1)),
rgba(28, 19, 30, 0.18);
gap: 10px;
padding: 9px 11px;
border: 1px solid rgba(255, 255, 255, 0.28);
border-radius: 16px;
background: rgba(31, 22, 29, 0.48);
color: #ffffff;
cursor: pointer;
font: inherit;
@@ -19,8 +18,8 @@
touch-action: manipulation;
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.18),
0 18px 42px rgba(46, 22, 38, 0.18);
backdrop-filter: blur(18px);
0 12px 30px rgba(31, 17, 27, 0.2);
backdrop-filter: blur(14px);
animation: messageFloatIn 0.5s ease 0.08s backwards;
transition:
border-color 0.18s ease,
@@ -53,8 +52,8 @@
.avatarWrap {
display: grid;
width: 48px;
height: 48px;
width: 40px;
height: 40px;
place-items: center;
border: 2px solid rgba(255, 255, 255, 0.74);
border-radius: 9999px;
@@ -92,10 +91,15 @@
}
.message {
display: -webkit-box;
min-height: 2.6em;
overflow: hidden;
color: #ffffff;
font-size: clamp(14px, 3.333vw, 17px);
font-weight: 760;
line-height: 1.35;
font-size: clamp(13px, 3.45vw, 16px);
font-weight: 720;
line-height: 1.3;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
.messageLoading {
+1 -1
View File
@@ -9,7 +9,7 @@ export function SplashLogo() {
width={180}
height={60}
priority
className="block h-auto w-auto"
className="block h-auto w-[clamp(136px,38vw,158px)]"
/>
</div>
);
@@ -8,16 +8,13 @@
overflow: hidden;
}
/* 渐变:accent (bottom-left) → transparent (center-right) */
.gradientOverlay {
.imageScrim {
position: absolute;
inset: 0;
background: linear-gradient(
to top right,
var(--color-accent),
transparent 60%
);
opacity: 0.5;
background: rgba(25, 17, 23, 0.08);
box-shadow:
inset 0 -290px 150px -145px rgba(20, 12, 18, 0.78),
inset 0 110px 90px -90px rgba(11, 8, 10, 0.42);
z-index: 1;
pointer-events: none;
}
@@ -49,15 +46,9 @@
flex: 1 1 auto;
}
.buttonArea {
margin-top: var(--page-section-gap-lg, 26px);
}
.bottom {
margin: var(--page-section-gap-lg, 32px) 0 0 0;
font-size: var(--responsive-body, var(--font-size-md));
font-weight: 400;
line-height: 1.5;
color: rgba(255, 255, 255, 0.9);
text-align: left;
.bottomStack {
display: flex;
flex-direction: column;
gap: clamp(12px, 3.6vw, 18px);
padding-bottom: clamp(12px, 3.6vw, 18px);
}
+20 -18
View File
@@ -62,12 +62,17 @@ export function SplashScreen() {
pwaUtil.prepareInstallPrompt();
}, []);
const hasLatestMessage =
latestMessage.isLoading || Boolean(latestMessage.message);
return (
<MobileShell background="var(--color-splash-background)">
<div className={styles.wrapper}>
<SplashBackground src={character.assets.cover} />
{/* 渐变叠加层:accent → transparent (bottom-left → center-right) */}
<div className={styles.gradientOverlay} aria-hidden="true" />
<SplashBackground
src={character.assets.splashCover ?? character.assets.cover}
objectPosition={character.id === "maya-tan" ? "52% center" : undefined}
/>
<div className={styles.imageScrim} aria-hidden="true" />
<div className={styles.favoriteAction}>
<FavoriteEntryButton
characterSlug={character.slug}
@@ -77,26 +82,23 @@ export function SplashScreen() {
<div className={styles.content}>
<SplashLogo />
<div className={styles.spacer} />
<SplashLatestMessage
message={latestMessage.message}
characterName={character.shortName}
avatarUrl={character.assets.avatar}
isLoading={latestMessage.isLoading}
onOpenChat={handleStartChat}
/>
<SplashContent characterName={character.shortName} />
<div className={styles.buttonArea}>
<div className={styles.bottomStack}>
{hasLatestMessage ? (
<SplashLatestMessage
message={latestMessage.message}
characterName={character.shortName}
avatarUrl={character.assets.avatar}
isLoading={latestMessage.isLoading}
onOpenChat={handleStartChat}
/>
) : null}
<SplashContent characterName={character.shortName} />
<SplashButton onStartChat={handleStartChat} />
</div>
<p className={styles.bottom}>
{character.displayName}, {character.tagline}
<br />
24/7 online | Chat | Companion | Heal | Sweet moments
</p>
</div>
<AppBottomNav
activeItem="chat"
privateZoneLabel={character.copy.privateZoneTitle}
privateZoneLabel="Private Zone"
onChatClick={handleOpenSplash}
onPrivateZoneClick={handleOpenPrivateZone}
onMenuClick={handleOpenMenu}