diff --git a/frontend/app/globals.css b/frontend/app/globals.css index f73a7386..3899e674 100644 --- a/frontend/app/globals.css +++ b/frontend/app/globals.css @@ -285,6 +285,133 @@ 0%, 100% { opacity: 0.3; } 50% { opacity: 0.6; } } + + @keyframes landingRise { + from { + opacity: 0; + transform: translateY(18px); + } + to { + opacity: 1; + transform: translateY(0); + } + } + + @keyframes landingFloat { + 0%, + 100% { + translate: 0 0; + } + 50% { + translate: 0 -10px; + } + } + + @keyframes landingScan { + 0% { + transform: translateY(-120%); + opacity: 0; + } + 18%, + 72% { + opacity: 0.48; + } + 100% { + transform: translateY(120%); + opacity: 0; + } + } + + @keyframes landingPulseDot { + 0%, + 100% { + box-shadow: 0 0 0 0 rgba(37, 99, 235, 0.22); + transform: scale(1); + } + 50% { + box-shadow: 0 0 0 7px rgba(37, 99, 235, 0); + transform: scale(1.08); + } + } + + .landing-rise { + opacity: 0; + animation: landingRise 720ms cubic-bezier(0.16, 1, 0.3, 1) forwards; + } + + .landing-delay-1 { + animation-delay: 100ms; + } + + .landing-delay-2 { + animation-delay: 190ms; + } + + .landing-delay-3 { + animation-delay: 280ms; + } + + .landing-float { + animation: landingFloat 7s ease-in-out infinite; + } + + .landing-float-slow { + animation: landingFloat 9s ease-in-out infinite; + } + + .landing-hover-lift { + transition: + transform 220ms ease, + border-color 220ms ease, + box-shadow 220ms ease; + } + + .landing-hover-lift:hover { + transform: translateY(-4px); + border-color: rgba(148, 163, 184, 0.78); + box-shadow: 0 18px 44px rgba(15, 23, 42, 0.1); + } + + .landing-screen-glow { + position: relative; + } + + .landing-screen-glow::after { + content: ""; + pointer-events: none; + position: absolute; + inset: 44px 8px 8px; + border-radius: 6px; + background: linear-gradient( + 180deg, + transparent 0%, + rgba(37, 99, 235, 0.12) 48%, + transparent 100% + ); + mix-blend-mode: multiply; + opacity: 0; + animation: landingScan 5.6s ease-in-out infinite; + } + + .landing-pulse-dot { + animation: landingPulseDot 2.6s ease-in-out infinite; + } + + @media (prefers-reduced-motion: reduce) { + .landing-rise, + .landing-float, + .landing-float-slow, + .landing-screen-glow::after, + .landing-pulse-dot { + opacity: 1; + animation: none; + } + + .landing-hover-lift, + .landing-hover-lift:hover { + transform: none; + } + } } /* ── Reduced motion: disable all animations and transitions ── */ diff --git a/frontend/components/auth/LoginClient.tsx b/frontend/components/auth/LoginClient.tsx index 3d094e6b..eb8d8020 100644 --- a/frontend/components/auth/LoginClient.tsx +++ b/frontend/components/auth/LoginClient.tsx @@ -7,11 +7,8 @@ import { ArrowRight, ChevronLeft, Chrome, - CloudRain, - CloudSun, Lock, Mail, - Sun, Eye, EyeOff, } from "lucide-react"; @@ -51,13 +48,16 @@ export function LoginClient({ nextPath, initialError, initialMode }: LoginClient const copy = { backHome: isEn ? "Back to Home" : "返回首页", - subtitle: isEn - ? "Enter the live terminal, subscription center, and Telegram entitlement workflow." - : "进入实时终端、订阅中心和 Telegram 权益工作流。", + loginSubtitle: isEn + ? "Sign in to continue to your weather decision terminal." + : "登录后进入你的天气决策终端。", + signupSubtitle: isEn + ? "Create an account and get a one-time 3-day trial. No payment required first." + : "创建账号后自动开启一次 3 天试用,无需先付款。", googleOneClick: isEn ? "Continue with Google" : "使用 Google 账号一键登录", - orEmail: isEn ? "Or continue with email" : "或使用邮箱", + orGoogle: isEn ? "Or continue with Google" : "或使用 Google", login: isEn ? "Sign In" : "登录", signup: isEn ? "Sign Up" : "注册", passwordLoginPlaceholder: isEn ? "Enter password" : "输入密码", @@ -97,8 +97,6 @@ export function LoginClient({ nextPath, initialError, initialMode }: LoginClient loginFailedHint: isEn ? "If you just signed up, please verify your email first. Check your inbox or spam folder." : "如果刚注册,请先点击邮箱中的验证链接。检查收件箱或垃圾邮件。", - - // New translations for Koyfin-style layouts workEmail: isEn ? "Work email" : "工作邮箱", password: isEn ? "Password" : "密码", welcomeBack: isEn ? "Sign in to PolyWeather" : "登录 PolyWeather", @@ -109,12 +107,27 @@ export function LoginClient({ nextPath, initialError, initialMode }: LoginClient ? "By proceeding, you agree to the Privacy Policy and Terms & Conditions." : "继续操作即代表您同意隐私政策与服务条款。", desc: isEn - ? "Access robust METAR observations, advanced DEB forecast blends, and structured decision context for weather risk analysis." - : "提供精准的机场 METAR 实况、先进的 DEB 智能融合预测和结构化决策背景,助您理清气象风险脉络。", + ? "Use the same terminal palette as the product: live temperature evidence, DEB paths, and settlement-source context in one calm workspace." + : "沿用终端界面的配色和信息密度:实时温度证据、DEB 路径和结算源背景放在一个安静工作台里。", trusted: isEn ? "Trusted by industry professionals" : "深受行业决策人员信赖", } as const; const submittingLabel = isLogin ? copy.loginSubmitting : copy.signupSubmitting; const googleSubmittingLabel = copy.googleSubmitting; + const formSubtitle = isLogin ? copy.loginSubtitle : copy.signupSubtitle; + const accessHighlights = isEn + ? ["Live temperature charts", "DEB forecast path", "Runway and settlement alerts"] + : ["实时温度图表", "DEB 预测路径", "跑道与结算提醒"]; + const sideStats = isEn + ? [ + { label: "Trial", value: "3 days" }, + { label: "Access", value: "Terminal" }, + { label: "Signals", value: "Runway" }, + ] + : [ + { label: "试用", value: "3 天" }, + { label: "入口", value: "终端" }, + { label: "提醒", value: "跑道" }, + ]; const loadingSpinner = (