fix: refine landing and auth entry design
This commit is contained in:
@@ -52,8 +52,8 @@ export function LoginClient({ nextPath, initialError, initialMode }: LoginClient
|
|||||||
const copy = {
|
const copy = {
|
||||||
backHome: isEn ? "Back to Home" : "返回首页",
|
backHome: isEn ? "Back to Home" : "返回首页",
|
||||||
subtitle: isEn
|
subtitle: isEn
|
||||||
? "Explore weather details from every corner of the world"
|
? "Enter the live terminal, subscription center, and Telegram entitlement workflow."
|
||||||
: "探索世界每一个角落的气象细节",
|
: "进入实时终端、订阅中心和 Telegram 权益工作流。",
|
||||||
googleOneClick: isEn
|
googleOneClick: isEn
|
||||||
? "Continue with Google"
|
? "Continue with Google"
|
||||||
: "使用 Google 账号一键登录",
|
: "使用 Google 账号一键登录",
|
||||||
@@ -64,9 +64,9 @@ export function LoginClient({ nextPath, initialError, initialMode }: LoginClient
|
|||||||
passwordSignupPlaceholder: isEn
|
passwordSignupPlaceholder: isEn
|
||||||
? "Set at least 6 characters"
|
? "Set at least 6 characters"
|
||||||
: "设置至少 6 位密码",
|
: "设置至少 6 位密码",
|
||||||
loginSubmit: isEn ? "Start your weather decision journey" : "开启气象决策之旅",
|
loginSubmit: isEn ? "Enter PolyWeather Terminal" : "进入 PolyWeather 终端",
|
||||||
loginSubmitting: isEn ? "Signing in..." : "正在登录...",
|
loginSubmitting: isEn ? "Signing in..." : "正在登录...",
|
||||||
signupSubmit: isEn ? "Create account now" : "立即创建账号",
|
signupSubmit: isEn ? "Create account and start trial" : "创建账号并领取试用",
|
||||||
signupSubmitting: isEn ? "Creating account..." : "正在创建账号...",
|
signupSubmitting: isEn ? "Creating account..." : "正在创建账号...",
|
||||||
googleSubmitting: isEn ? "Connecting Google..." : "正在连接 Google...",
|
googleSubmitting: isEn ? "Connecting Google..." : "正在连接 Google...",
|
||||||
loginHint: isEn
|
loginHint: isEn
|
||||||
@@ -101,8 +101,8 @@ export function LoginClient({ nextPath, initialError, initialMode }: LoginClient
|
|||||||
// New translations for Koyfin-style layouts
|
// New translations for Koyfin-style layouts
|
||||||
workEmail: isEn ? "Work email" : "工作邮箱",
|
workEmail: isEn ? "Work email" : "工作邮箱",
|
||||||
password: isEn ? "Password" : "密码",
|
password: isEn ? "Password" : "密码",
|
||||||
welcomeBack: isEn ? "Welcome Back" : "欢迎回来",
|
welcomeBack: isEn ? "Sign in to PolyWeather" : "登录 PolyWeather",
|
||||||
signUpTitle: isEn ? "Sign up for your PolyWeather account" : "注册您的 PolyWeather 账户",
|
signUpTitle: isEn ? "Create your PolyWeather account" : "创建 PolyWeather 账号",
|
||||||
newToPoly: isEn ? "New to PolyWeather?" : "还没有 PolyWeather 账号?",
|
newToPoly: isEn ? "New to PolyWeather?" : "还没有 PolyWeather 账号?",
|
||||||
alreadyHave: isEn ? "Already have an account?" : "已经有账号了?",
|
alreadyHave: isEn ? "Already have an account?" : "已经有账号了?",
|
||||||
termsAgreement: isEn
|
termsAgreement: isEn
|
||||||
@@ -264,22 +264,28 @@ export function LoginClient({ nextPath, initialError, initialMode }: LoginClient
|
|||||||
<div className="absolute inset-0 bg-[linear-gradient(to_right,rgba(255,255,255,0.015)_1px,transparent_1px),linear-gradient(to_bottom,rgba(255,255,255,0.015)_1px,transparent_1px)] bg-[size:32px_32px] pointer-events-none" />
|
<div className="absolute inset-0 bg-[linear-gradient(to_right,rgba(255,255,255,0.015)_1px,transparent_1px),linear-gradient(to_bottom,rgba(255,255,255,0.015)_1px,transparent_1px)] bg-[size:32px_32px] pointer-events-none" />
|
||||||
|
|
||||||
<div className="relative z-10 flex flex-col gap-14">
|
<div className="relative z-10 flex flex-col gap-14">
|
||||||
<Link href="/" className="flex items-center hover:opacity-90 transition-opacity">
|
<Link
|
||||||
<img src="/logo.png" alt="PolyWeather" className="h-8 w-auto object-contain brightness-0 invert" />
|
href="/"
|
||||||
|
className="inline-flex items-center gap-2 text-lg font-black tracking-tight text-white transition-opacity hover:opacity-90"
|
||||||
|
>
|
||||||
|
<span className="grid h-8 w-8 place-items-center rounded-lg border border-white/15 bg-white/10 text-[11px] font-black text-cyan-200">
|
||||||
|
PW
|
||||||
|
</span>
|
||||||
|
<span>PolyWeather</span>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-3xl font-black leading-[1.25] tracking-tight text-white animate-fade-up [animation-delay:150ms] opacity-0">
|
<h2 className="text-3xl font-black leading-[1.25] tracking-tight text-white animate-fade-up [animation-delay:150ms] opacity-0">
|
||||||
{isEn ? (
|
{isEn ? (
|
||||||
<>
|
<>
|
||||||
Weather intelligence and risk management{" "}
|
Settlement-source evidence for temperature markets{" "}
|
||||||
<span className="inline-block px-2.5 py-0.5 mt-1 rounded bg-gradient-to-r from-blue-600 to-indigo-500 text-white font-bold text-[0.9em] shadow-lg shadow-blue-600/25 animate-gradient bg-[length:200%_auto]">
|
<span className="inline-block px-2.5 py-0.5 mt-1 rounded bg-gradient-to-r from-blue-600 to-indigo-500 text-white font-bold text-[0.9em] shadow-lg shadow-blue-600/25 animate-gradient bg-[length:200%_auto]">
|
||||||
simplified.
|
simplified.
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
天气信息与风险管理{" "}
|
结算源实测与温度市场判断{" "}
|
||||||
<span className="inline-block px-2.5 py-0.5 mt-1 rounded bg-gradient-to-r from-blue-600 to-indigo-500 text-white font-bold text-[0.9em] shadow-lg shadow-blue-600/25 animate-gradient bg-[length:200%_auto]">
|
<span className="inline-block px-2.5 py-0.5 mt-1 rounded bg-gradient-to-r from-blue-600 to-indigo-500 text-white font-bold text-[0.9em] shadow-lg shadow-blue-600/25 animate-gradient bg-[length:200%_auto]">
|
||||||
化繁为简。
|
化繁为简。
|
||||||
</span>
|
</span>
|
||||||
@@ -407,12 +413,18 @@ export function LoginClient({ nextPath, initialError, initialMode }: LoginClient
|
|||||||
{/* Top Header Switch */}
|
{/* Top Header Switch */}
|
||||||
<div className="relative z-10 flex justify-between lg:justify-end items-center gap-3">
|
<div className="relative z-10 flex justify-between lg:justify-end items-center gap-3">
|
||||||
{/* Logo on top-left for mobile only */}
|
{/* Logo on top-left for mobile only */}
|
||||||
<Link href="/" className="flex items-center hover:opacity-90 transition-opacity lg:hidden">
|
<Link
|
||||||
<img src="/logo.png" alt="PolyWeather" className="h-7 w-auto object-contain" />
|
href="/"
|
||||||
|
className="inline-flex items-center gap-2 text-sm font-black tracking-tight text-slate-950 transition-opacity hover:opacity-90 lg:hidden"
|
||||||
|
>
|
||||||
|
<span className="grid h-8 w-8 place-items-center rounded-lg border border-slate-200 bg-white text-[10px] font-black text-sky-700 shadow-sm">
|
||||||
|
PW
|
||||||
|
</span>
|
||||||
|
<span className="hidden min-[360px]:inline">PolyWeather</span>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<span className="text-xs text-slate-500">
|
<span className="hidden text-xs text-slate-500 sm:inline">
|
||||||
{isLogin ? copy.newToPoly : copy.alreadyHave}
|
{isLogin ? copy.newToPoly : copy.alreadyHave}
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
@@ -422,7 +434,7 @@ export function LoginClient({ nextPath, initialError, initialMode }: LoginClient
|
|||||||
setInfoText("");
|
setInfoText("");
|
||||||
setMode(isLogin ? "signup" : "login");
|
setMode(isLogin ? "signup" : "login");
|
||||||
}}
|
}}
|
||||||
className="rounded-xl border border-slate-200 bg-white px-4 py-2 text-xs font-bold text-slate-700 shadow-sm transition-all hover:border-slate-300 hover:bg-slate-50 hover:text-slate-900 active:scale-[0.98]"
|
className="whitespace-nowrap rounded-xl border border-slate-200 bg-white px-4 py-2 text-xs font-bold text-slate-700 shadow-sm transition-all hover:border-slate-300 hover:bg-slate-50 hover:text-slate-900 active:scale-[0.98]"
|
||||||
>
|
>
|
||||||
{isLogin ? copy.signup : copy.login}
|
{isLogin ? copy.signup : copy.login}
|
||||||
</button>
|
</button>
|
||||||
@@ -430,8 +442,8 @@ export function LoginClient({ nextPath, initialError, initialMode }: LoginClient
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Center Form Card */}
|
{/* Center Form Card */}
|
||||||
<div className="relative z-10 flex flex-1 items-center justify-center my-10">
|
<div className="relative z-10 flex flex-1 items-start justify-center pt-10 sm:pt-12 lg:my-10 lg:items-center lg:pt-0">
|
||||||
<div className="w-full max-w-[440px] bg-white/90 backdrop-blur-xl border border-slate-200/50 rounded-2xl p-6 sm:p-10 shadow-[0_24px_60px_rgba(8,16,36,0.06)] animate-fade-up [animation-delay:200ms] opacity-0 transition-transform hover:-translate-y-1 hover:shadow-[0_32px_80px_rgba(8,16,36,0.08)] duration-500">
|
<div className="w-full max-w-[440px] bg-white/90 backdrop-blur-xl border border-slate-200/50 rounded-2xl p-6 sm:p-10 shadow-[0_24px_60px_rgba(8,16,36,0.06)] animate-fade-up [animation-delay:200ms] opacity-0">
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<h1 className="text-2xl font-black tracking-tight text-slate-900 mb-2">
|
<h1 className="text-2xl font-black tracking-tight text-slate-900 mb-2">
|
||||||
{isLogin ? copy.welcomeBack : copy.signUpTitle}
|
{isLogin ? copy.welcomeBack : copy.signUpTitle}
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
|
||||||
|
function assert(condition: unknown, message: string) {
|
||||||
|
if (!condition) throw new Error(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function runTests() {
|
||||||
|
const source = fs.readFileSync(
|
||||||
|
path.join(process.cwd(), "components", "auth", "LoginClient.tsx"),
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
source.includes("登录 PolyWeather") &&
|
||||||
|
source.includes("创建 PolyWeather 账号") &&
|
||||||
|
source.includes("创建账号并领取试用"),
|
||||||
|
"auth entry copy must be product-specific and explain the signup trial path",
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
source.includes("PW") && source.includes("PolyWeather"),
|
||||||
|
"auth pages must show a readable PolyWeather brand mark",
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
!source.includes("brightness-0 invert"),
|
||||||
|
"auth dark panel must not invert the square logo image into a white block",
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
source.includes("items-start justify-center pt-10") &&
|
||||||
|
source.includes("lg:items-center lg:pt-0"),
|
||||||
|
"auth form must sit higher on mobile while staying centered on desktop",
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
!source.includes("hover:-translate-y-1"),
|
||||||
|
"auth form card must not shift position on hover",
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
source.includes("hidden text-xs text-slate-500 sm:inline") &&
|
||||||
|
source.includes("whitespace-nowrap rounded-xl"),
|
||||||
|
"auth mode switch prompt must not crowd or wrap the mobile header",
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -249,8 +249,11 @@ function InstitutionalLandingScreen({ locale }: { locale: LandingLocale }) {
|
|||||||
<LandingAnalytics />
|
<LandingAnalytics />
|
||||||
<header className="sticky top-0 z-30 border-b border-slate-200 bg-[#fbfbfa]/90 backdrop-blur-xl">
|
<header className="sticky top-0 z-30 border-b border-slate-200 bg-[#fbfbfa]/90 backdrop-blur-xl">
|
||||||
<div className="mx-auto flex h-14 max-w-6xl items-center justify-between px-4 sm:px-6">
|
<div className="mx-auto flex h-14 max-w-6xl items-center justify-between px-4 sm:px-6">
|
||||||
<Link href="/" className="flex items-center gap-2 transition-opacity hover:opacity-80">
|
<Link
|
||||||
<img src="/logo.png" alt="PolyWeather" className="h-7 w-auto object-contain" />
|
href="/"
|
||||||
|
className="text-base font-black tracking-tight text-slate-950 transition-opacity hover:opacity-80 sm:text-lg"
|
||||||
|
>
|
||||||
|
PolyWeather
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<nav className="hidden items-center gap-7 text-sm font-medium text-slate-500 md:flex">
|
<nav className="hidden items-center gap-7 text-sm font-medium text-slate-500 md:flex">
|
||||||
|
|||||||
@@ -49,6 +49,12 @@ export function runTests() {
|
|||||||
assert(!source.includes("高频刷新与 API 仍为 Pro 权益"), "landing page must not incorrectly exclude high-frequency refresh or API from trial access");
|
assert(!source.includes("高频刷新与 API 仍为 Pro 权益"), "landing page must not incorrectly exclude high-frequency refresh or API from trial access");
|
||||||
assert(source.includes("bg-[#fbfbfa]"), "landing page must use a light Notion-style background");
|
assert(source.includes("bg-[#fbfbfa]"), "landing page must use a light Notion-style background");
|
||||||
assert(source.includes("WeatherWorkflowIllustration"), "landing page must include a friendly illustration surface");
|
assert(source.includes("WeatherWorkflowIllustration"), "landing page must include a friendly illustration surface");
|
||||||
|
assert(
|
||||||
|
/className="text-base font-black tracking-tight text-slate-950[\s\S]*?>\s*PolyWeather\s*<\/Link>/.test(
|
||||||
|
source,
|
||||||
|
),
|
||||||
|
"landing header must expose a readable PolyWeather wordmark instead of a tiny square logo image",
|
||||||
|
);
|
||||||
assert(!fs.existsSync(publicPngPath), "heavy PNG preview must not remain in public static assets");
|
assert(!fs.existsSync(publicPngPath), "heavy PNG preview must not remain in public static assets");
|
||||||
assert(fs.existsSync(fixturePngPath), "PNG preview may only remain as a test fixture");
|
assert(fs.existsSync(fixturePngPath), "PNG preview may only remain as a test fixture");
|
||||||
assert(fs.existsSync(webpPath), "landing page must ship a WebP preview image for the LCP product screenshot");
|
assert(fs.existsSync(webpPath), "landing page must ship a WebP preview image for the LCP product screenshot");
|
||||||
|
|||||||
Reference in New Issue
Block a user