Compare commits

..

2 Commits

Author SHA1 Message Date
admin 9c1c3ec4cf chore(favicon): 使用预发布环境的图标
Docker Image / Quality and Bundle Budgets (push) Successful in 3s
Docker Image / Build and Push Docker Image (push) Successful in 1m52s
2026-07-21 15:51:23 +08:00
admin 36d8fcf2c3 feat(tip): enlarge and simplify payment interface 2026-07-21 15:51:09 +08:00
5 changed files with 96 additions and 124 deletions
@@ -81,7 +81,7 @@ describe("PaymentMethodSelector", () => {
expect(html).toContain('data-density="compact"'); expect(html).toContain('data-density="compact"');
expect(html).toContain("Payment Method"); expect(html).toContain("Payment Method");
expect(html).not.toContain("This caption is hidden"); expect(html).not.toContain("This caption is hidden");
expect(html).toContain("min-h-11"); expect(html).toContain("min-h-13");
}); });
it("does not render when payment method selection is unavailable", () => { it("does not render when payment method selection is unavailable", () => {
@@ -54,7 +54,7 @@ export function PaymentMethodSelector({
return ( return (
<section <section
className={`flex flex-col ${isCompact ? "gap-1.5" : "gap-[clamp(8px,1.852vw,10px)]"} ${className ?? ""}`.trim()} className={`flex flex-col ${isCompact ? "gap-2" : "gap-[clamp(8px,1.852vw,10px)]"} ${className ?? ""}`.trim()}
data-density={density} data-density={density}
aria-labelledby="payment-method-title" aria-labelledby="payment-method-title"
> >
@@ -63,7 +63,7 @@ export function PaymentMethodSelector({
> >
<h2 <h2
id="payment-method-title" id="payment-method-title"
className={`m-0 font-bold leading-[1.2] text-auth-text-primary ${isCompact ? "text-sm" : "text-(length:--responsive-card-title,var(--font-size-lg))"}`} className={`m-0 font-bold leading-[1.2] text-auth-text-primary ${isCompact ? "text-base" : "text-(length:--responsive-card-title,var(--font-size-lg))"}`}
style={{ fontFamily: "var(--font-athelas), var(--font-system)" }} style={{ fontFamily: "var(--font-athelas), var(--font-system)" }}
> >
Payment Method Payment Method
@@ -78,7 +78,7 @@ export function PaymentMethodSelector({
{paymentMethods.map((method) => { {paymentMethods.map((method) => {
const selected = method.channel === value; const selected = method.channel === value;
const classes = [ const classes = [
`flex cursor-pointer items-center justify-center text-center font-[inherit] text-[#3c3b3b] transition-[border-color,box-shadow,transform] duration-150 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent active:enabled:scale-98 disabled:cursor-not-allowed disabled:opacity-72 ${isCompact ? "min-h-11 rounded-2xl px-2 py-1.5" : "min-h-[clamp(54px,11.481vw,62px)] rounded-(--responsive-card-radius-sm,18px) px-[clamp(12px,2.593vw,14px)] py-[clamp(9px,1.852vw,10px)]"}`, `flex cursor-pointer items-center justify-center text-center font-[inherit] text-[#3c3b3b] transition-[border-color,box-shadow,transform] duration-150 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent active:enabled:scale-98 disabled:cursor-not-allowed disabled:opacity-72 ${isCompact ? "min-h-13 rounded-[18px] px-3 py-2" : "min-h-[clamp(54px,11.481vw,62px)] rounded-(--responsive-card-radius-sm,18px) px-[clamp(12px,2.593vw,14px)] py-[clamp(9px,1.852vw,10px)]"}`,
selected selected
? "border-2 border-[#f657a0] bg-[#fff4f9] shadow-[0_8px_18px_rgba(217,47,127,0.24),0_0_0_3px_rgba(217,47,127,0.18)] -translate-y-0.5" ? "border-2 border-[#f657a0] bg-[#fff4f9] shadow-[0_8px_18px_rgba(217,47,127,0.24),0_0_0_3px_rgba(217,47,127,0.18)] -translate-y-0.5"
: "border border-[rgba(246,87,160,0.18)] bg-white shadow-[0_5px_7px_0_rgba(247,89,168,0.08)]", : "border border-[rgba(246,87,160,0.18)] bg-white shadow-[0_5px_7px_0_rgba(247,89,168,0.08)]",
@@ -105,7 +105,7 @@ export function PaymentMethodSelector({
aria-hidden="true" aria-hidden="true"
width={122} width={122}
height={29} height={29}
className={`block object-contain ${isCompact ? "max-h-7 w-[min(100%,80px)]" : "max-h-8.5 w-[min(100%,92px)]"}`} className={`block object-contain ${isCompact ? "max-h-8 w-[min(100%,92px)]" : "max-h-8.5 w-[min(100%,92px)]"}`}
/> />
) : ( ) : (
<span className="text-(length:--responsive-body,16px) font-bold leading-[1.1] text-[#1e1e1e]"> <span className="text-(length:--responsive-body,16px) font-bold leading-[1.1] text-[#1e1e1e]">
@@ -188,7 +188,7 @@ describe("TipScreen checkout", () => {
}); });
}); });
it("renders the compact purchase flow with character atmosphere", () => { it("renders the enlarged purchase flow without a character Cover", () => {
renderScreen(); renderScreen();
expect(container.textContent).toContain("Send Maya a gift"); expect(container.textContent).toContain("Send Maya a gift");
@@ -204,14 +204,10 @@ describe("TipScreen checkout", () => {
).toBe("compact"); ).toBe("compact");
expect(container.textContent).not.toContain("A little sweetness for today"); expect(container.textContent).not.toContain("A little sweetness for today");
expect(container.textContent).not.toContain("Tip Maya"); expect(container.textContent).not.toContain("Tip Maya");
expect( expect(container.querySelector("main")?.getAttribute("style")).toBeNull();
container
.querySelector("main")
?.getAttribute("style"),
).toContain('--tip-cover-image: url("/images/cover/maya.png")');
expect( expect(
container.querySelectorAll('main > div[aria-hidden="true"]'), container.querySelectorAll('main > div[aria-hidden="true"]'),
).toHaveLength(3); ).toHaveLength(2);
}); });
it.each([ it.each([
+84 -101
View File
@@ -1,4 +1,6 @@
.shell { .shell {
--responsive-icon-button-size: 48px;
position: relative; position: relative;
display: flex; display: flex;
width: 100%; width: 100%;
@@ -7,10 +9,10 @@
box-sizing: border-box; box-sizing: border-box;
flex-direction: column; flex-direction: column;
padding: padding:
calc(var(--app-safe-top, 0px) + 14px) calc(var(--app-safe-top, 0px) + 16px)
calc(var(--app-safe-right, 0px) + clamp(14px, 4vw, 22px)) calc(var(--app-safe-right, 0px) + clamp(16px, 4.5vw, 24px))
calc(var(--app-safe-bottom, 0px) + 14px) calc(var(--app-safe-bottom, 0px) + 16px)
calc(var(--app-safe-left, 0px) + clamp(14px, 4vw, 22px)); calc(var(--app-safe-left, 0px) + clamp(16px, 4.5vw, 24px));
overflow-x: hidden; overflow-x: hidden;
overflow-y: auto; overflow-y: auto;
background: background:
@@ -29,7 +31,6 @@
isolation: isolate; isolation: isolate;
} }
.bgImage,
.bgGlowOne, .bgGlowOne,
.bgGlowTwo { .bgGlowTwo {
position: absolute; position: absolute;
@@ -37,21 +38,6 @@
pointer-events: none; pointer-events: none;
} }
.bgImage {
inset: 0 0 auto;
height: clamp(230px, 42vh, 330px);
background:
linear-gradient(
180deg,
rgba(255, 242, 232, 0.3) 0%,
rgba(255, 242, 232, 0.64) 64%,
#fff2e8 96%
),
var(--tip-cover-image) center 18% / cover no-repeat;
filter: saturate(0.95) blur(0.2px);
opacity: 0.36;
}
.bgGlowOne, .bgGlowOne,
.bgGlowTwo { .bgGlowTwo {
border-radius: 999px; border-radius: 999px;
@@ -78,7 +64,7 @@
position: relative; position: relative;
z-index: 1; z-index: 1;
display: flex; display: flex;
min-height: 46px; min-height: 50px;
align-items: center; align-items: center;
} }
@@ -96,14 +82,15 @@
display: flex; display: flex;
width: 100%; width: 100%;
min-width: 0; min-width: 0;
flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: 10px; gap: 8px;
} }
.headerAvatar { .headerAvatar {
width: 42px; width: 48px;
height: 42px; height: 48px;
flex: 0 0 auto; flex: 0 0 auto;
border: 2px solid rgba(255, 255, 255, 0.94); border: 2px solid rgba(255, 255, 255, 0.94);
border-radius: 999px; border-radius: 999px;
@@ -113,16 +100,16 @@
} }
.headerTitle { .headerTitle {
max-width: 220px; max-width: 250px;
margin: 0; margin: 0;
overflow: hidden; overflow: hidden;
color: #2a1a1d; color: #2a1a1d;
font-family: var(--font-athelas), Georgia, serif; font-family: var(--font-athelas), Georgia, serif;
font-size: clamp(18px, 5vw, 24px); font-size: clamp(21px, 5.7vw, 27px);
font-weight: 760; font-weight: 760;
letter-spacing: -0.025em; letter-spacing: -0.025em;
line-height: 1.05; line-height: 1.05;
text-align: left; text-align: center;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
@@ -130,22 +117,17 @@
.supportPrompt { .supportPrompt {
position: relative; position: relative;
z-index: 1; z-index: 1;
min-height: 3.7em; width: min(100%, 340px);
box-sizing: border-box; box-sizing: border-box;
margin: 10px 0 0; margin: 12px auto 0;
padding: 9px 13px; padding: 0 4px;
border: 1px solid rgba(255, 255, 255, 0.64);
border-radius: 18px;
background: rgba(255, 255, 255, 0.7);
color: #684d51; color: #684d51;
font-size: clamp(12.5px, 3.45vw, 14px); font-size: clamp(14px, 3.7vw, 15px);
font-weight: 650; font-weight: 650;
line-height: 1.35; line-height: 1.35;
text-align: center; text-align: center;
text-wrap: balance; text-wrap: balance;
opacity: 0.78; opacity: 0.78;
box-shadow: 0 10px 26px rgba(91, 55, 52, 0.08);
backdrop-filter: blur(12px);
transition: opacity 0.24s ease; transition: opacity 0.24s ease;
} }
@@ -160,8 +142,8 @@
min-height: 0; min-height: 0;
flex: 0 1 auto; flex: 0 1 auto;
flex-direction: column; flex-direction: column;
gap: 12px; gap: 13px;
margin-top: 12px; margin-top: 13px;
} }
.productCard { .productCard {
@@ -169,10 +151,10 @@
flex: 0 1 auto; flex: 0 1 auto;
grid-template-columns: minmax(0, 1fr); grid-template-columns: minmax(0, 1fr);
align-items: center; align-items: center;
gap: 10px; gap: 11px;
padding: 12px; padding: 13px;
border: 1px solid rgba(118, 61, 55, 0.09); border: 1px solid rgba(118, 61, 55, 0.09);
border-radius: 24px; border-radius: 26px;
background: rgba(255, 255, 255, 0.86); background: rgba(255, 255, 255, 0.86);
box-shadow: box-shadow:
0 16px 36px rgba(119, 72, 67, 0.11), 0 16px 36px rgba(119, 72, 67, 0.11),
@@ -182,12 +164,12 @@
.coffeeStage { .coffeeStage {
position: relative; position: relative;
width: clamp(136px, 40vw, 176px); width: clamp(150px, 42vw, 184px);
aspect-ratio: 1; aspect-ratio: 1;
align-self: center; align-self: center;
justify-self: center; justify-self: center;
overflow: hidden; overflow: hidden;
border-radius: 20px; border-radius: 22px;
background: #c59b7d; background: #c59b7d;
box-shadow: box-shadow:
0 10px 24px rgba(102, 53, 39, 0.16), 0 10px 24px rgba(102, 53, 39, 0.16),
@@ -231,8 +213,8 @@
.tierList { .tierList {
display: grid; display: grid;
max-height: 176px; max-height: 190px;
gap: 6px; gap: 7px;
padding: 2px; padding: 2px;
overflow-x: hidden; overflow-x: hidden;
overflow-y: auto; overflow-y: auto;
@@ -243,14 +225,14 @@
.tierOption { .tierOption {
position: relative; position: relative;
display: grid; display: grid;
min-height: 50px; min-height: 54px;
grid-template-columns: minmax(0, 1fr) auto 20px; grid-template-columns: minmax(0, 1fr) auto 22px;
align-items: center; align-items: center;
gap: 7px; gap: 7px;
box-sizing: border-box; box-sizing: border-box;
padding: 7px 8px 7px 10px; padding: 8px 9px 8px 11px;
border: 1px solid rgba(112, 71, 65, 0.12); border: 1px solid rgba(112, 71, 65, 0.12);
border-radius: 15px; border-radius: 17px;
background: rgba(255, 255, 255, 0.72); background: rgba(255, 255, 255, 0.72);
cursor: pointer; cursor: pointer;
transition: transition:
@@ -302,7 +284,7 @@
overflow: hidden; overflow: hidden;
color: #2a1b1e; color: #2a1b1e;
font-family: var(--font-athelas), Georgia, serif; font-family: var(--font-athelas), Georgia, serif;
font-size: clamp(13px, 3.7vw, 16px); font-size: clamp(15px, 4vw, 18px);
font-weight: 750; font-weight: 750;
letter-spacing: -0.015em; letter-spacing: -0.015em;
line-height: 1.05; line-height: 1.05;
@@ -317,15 +299,15 @@
.tierPrice { .tierPrice {
color: #d23f79; color: #d23f79;
font-size: clamp(12px, 3.2vw, 14px); font-size: clamp(13px, 3.5vw, 16px);
font-weight: 900; font-weight: 900;
white-space: nowrap; white-space: nowrap;
} }
.tierCheck { .tierCheck {
display: grid; display: grid;
width: 19px; width: 22px;
height: 19px; height: 22px;
place-items: center; place-items: center;
border: 1px solid rgba(111, 76, 73, 0.2); border: 1px solid rgba(111, 76, 73, 0.2);
border-radius: 999px; border-radius: 999px;
@@ -333,8 +315,8 @@
} }
.tierCheck svg { .tierCheck svg {
width: 12px; width: 14px;
height: 12px; height: 14px;
} }
.tierOption[data-selected="true"] .tierCheck { .tierOption[data-selected="true"] .tierCheck {
@@ -355,7 +337,7 @@
border-radius: 22px; border-radius: 22px;
background: rgba(255, 255, 255, 0.78); background: rgba(255, 255, 255, 0.78);
color: #76575c; color: #76575c;
font-size: 14px; font-size: 16px;
font-weight: 700; font-weight: 700;
line-height: 1.45; line-height: 1.45;
text-align: center; text-align: center;
@@ -367,15 +349,15 @@
} }
.catalogStatus button { .catalogStatus button {
min-height: 40px; min-height: 44px;
padding: 0 18px; padding: 0 20px;
border: 0; border: 0;
border-radius: 999px; border-radius: 999px;
background: #2b1a1e; background: #2b1a1e;
color: #ffffff; color: #ffffff;
cursor: pointer; cursor: pointer;
font: inherit; font: inherit;
font-size: 13px; font-size: 14px;
font-weight: 850; font-weight: 850;
} }
@@ -397,21 +379,21 @@
} }
.skeletonImage { .skeletonImage {
width: clamp(136px, 40vw, 176px); width: clamp(150px, 42vw, 184px);
aspect-ratio: 1; aspect-ratio: 1;
justify-self: center; justify-self: center;
border-radius: 20px; border-radius: 22px;
} }
.skeletonList { .skeletonList {
display: grid; display: grid;
gap: 6px; gap: 7px;
} }
.skeletonList span { .skeletonList span {
display: block; display: block;
min-height: 50px; min-height: 54px;
border-radius: 15px; border-radius: 17px;
} }
.paymentMethodSlot { .paymentMethodSlot {
@@ -424,7 +406,7 @@
.checkoutButton { .checkoutButton {
width: 100%; width: 100%;
min-height: 52px; min-height: 58px;
border: 0; border: 0;
border-radius: 999px; border-radius: 999px;
background: background:
@@ -433,7 +415,7 @@
color: #ffffff; color: #ffffff;
cursor: pointer; cursor: pointer;
font: inherit; font: inherit;
font-size: clamp(15px, 4vw, 18px); font-size: clamp(17px, 4.5vw, 20px);
font-weight: 900; font-weight: 900;
letter-spacing: -0.01em; letter-spacing: -0.01em;
box-shadow: box-shadow:
@@ -456,7 +438,7 @@
.checkoutError { .checkoutError {
margin: 7px 0 0; margin: 7px 0 0;
color: #b2474f; color: #b2474f;
font-size: 12px; font-size: 14px;
font-weight: 700; font-weight: 700;
line-height: 1.35; line-height: 1.35;
text-align: center; text-align: center;
@@ -464,99 +446,100 @@
@media (max-width: 350px) { @media (max-width: 350px) {
.shell { .shell {
padding-right: calc(var(--app-safe-right, 0px) + 11px); padding-right: calc(var(--app-safe-right, 0px) + 12px);
padding-left: calc(var(--app-safe-left, 0px) + 11px); padding-left: calc(var(--app-safe-left, 0px) + 12px);
} }
.header { .header {
min-height: 40px; min-height: 44px;
} }
.headerIdentity { .headerIdentity {
gap: 7px; gap: 6px;
} }
.headerTitle { .headerTitle {
max-width: 170px; max-width: 200px;
font-size: 17px; font-size: 20px;
} }
.productCard { .productCard {
gap: 7px; gap: 9px;
padding: 9px; padding: 11px;
} }
.tierOption { .tierOption {
grid-template-columns: minmax(0, 1fr) auto 18px; grid-template-columns: minmax(0, 1fr) auto 20px;
gap: 5px; gap: 6px;
padding-right: 6px; padding-right: 7px;
padding-left: 8px; padding-left: 9px;
} }
.tierName { .tierName {
font-size: 12px; font-size: 14px;
} }
.tierPrice { .tierPrice {
font-size: 11px; font-size: 13px;
} }
.tierCheck { .tierCheck {
width: 17px; width: 20px;
height: 17px; height: 20px;
} }
} }
@media (max-height: 700px) { @media (max-height: 700px) {
.shell { .shell {
padding-top: calc(var(--app-safe-top, 0px) + 9px); --responsive-icon-button-size: 44px;
padding-bottom: calc(var(--app-safe-bottom, 0px) + 9px);
padding-top: calc(var(--app-safe-top, 0px) + 10px);
padding-bottom: calc(var(--app-safe-bottom, 0px) + 10px);
} }
.header { .header {
min-height: 40px; min-height: 44px;
} }
.headerAvatar { .headerAvatar {
width: 38px; width: 44px;
height: 38px; height: 44px;
} }
.purchaseFlow { .purchaseFlow {
gap: 8px; gap: 10px;
margin-top: 8px; margin-top: 10px;
} }
.supportPrompt { .supportPrompt {
min-height: 0; margin-top: 9px;
margin-top: 7px; padding: 0 3px;
padding: 7px 11px; font-size: 13.5px;
font-size: 12px; line-height: 1.35;
line-height: 1.3;
} }
.productCard { .productCard {
padding: 9px; padding: 11px;
} }
.coffeeStage, .coffeeStage,
.skeletonImage { .skeletonImage {
width: 128px; width: 148px;
justify-self: center; justify-self: center;
} }
.tierList { .tierList {
max-height: 148px; max-height: 162px;
gap: 5px; gap: 6px;
} }
.tierOption, .tierOption,
.skeletonList span { .skeletonList span {
min-height: 44px; min-height: 48px;
} }
.checkoutButton { .checkoutButton {
min-height: 48px; min-height: 54px;
} }
} }
+4 -11
View File
@@ -1,6 +1,6 @@
"use client"; "use client";
import { useEffect, type CSSProperties } from "react"; import { useEffect } from "react";
import { BackButton, CharacterAvatar } from "@/app/_components"; import { BackButton, CharacterAvatar } from "@/app/_components";
import { MobileShell } from "@/app/_components/core"; import { MobileShell } from "@/app/_components/core";
@@ -201,15 +201,7 @@ export function TipScreen({
return ( return (
<MobileShell background="#fff5ed"> <MobileShell background="#fff5ed">
<main <main className={styles.shell}>
className={styles.shell}
style={
{
"--tip-cover-image": `url("${character.assets.cover}")`,
} as CSSProperties
}
>
<div className={styles.bgImage} aria-hidden="true" />
<div className={styles.bgGlowOne} aria-hidden="true" /> <div className={styles.bgGlowOne} aria-hidden="true" />
<div className={styles.bgGlowTwo} aria-hidden="true" /> <div className={styles.bgGlowTwo} aria-hidden="true" />
@@ -217,6 +209,7 @@ export function TipScreen({
<BackButton <BackButton
href={characterRoutes.splash} href={characterRoutes.splash}
variant="soft" variant="soft"
iconSize={28}
ariaLabel="Back to character home" ariaLabel="Back to character home"
analyticsKey="tip.back_to_splash" analyticsKey="tip.back_to_splash"
/> />
@@ -229,7 +222,7 @@ export function TipScreen({
src={character.assets.avatar} src={character.assets.avatar}
alt={character.displayName} alt={character.displayName}
size="100%" size="100%"
imageSize={44} imageSize={52}
priority priority
/> />
</div> </div>