diff --git a/public/images/auth/ic-logo-login.png b/public/images/auth/ic-logo-login.png
new file mode 100644
index 00000000..d5b13922
Binary files /dev/null and b/public/images/auth/ic-logo-login.png differ
diff --git a/public/images/auth/ic-logo-login@2x.png b/public/images/auth/ic-logo-login@2x.png
new file mode 100644
index 00000000..5bf0e279
Binary files /dev/null and b/public/images/auth/ic-logo-login@2x.png differ
diff --git a/public/images/auth/ic-logo-login@3x.png b/public/images/auth/ic-logo-login@3x.png
new file mode 100644
index 00000000..8f1c7084
Binary files /dev/null and b/public/images/auth/ic-logo-login@3x.png differ
diff --git a/src/app/_components/core/bottom-sheet.module.css b/src/app/_components/core/bottom-sheet.module.css
new file mode 100644
index 00000000..2ecccd2d
--- /dev/null
+++ b/src/app/_components/core/bottom-sheet.module.css
@@ -0,0 +1,34 @@
+/**
+ * 底部弹层样式
+ *
+ * 原始 Dart: showModalBottomSheet(透明背景,底部白卡 + 顶部圆角 radius28)
+ */
+.scrim {
+ position: fixed;
+ inset: 0;
+ z-index: 50;
+ display: flex;
+ align-items: flex-end;
+ justify-content: center;
+}
+
+.panel {
+ position: relative;
+ width: 100%;
+ max-width: 500px;
+ background: var(--color-auth-surface);
+ color: var(--color-auth-text-primary);
+ border-radius: var(--radius-bottom-sheet) var(--radius-bottom-sheet) 0 0;
+ box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.18);
+ padding-bottom: env(safe-area-inset-bottom);
+ animation: slideUp 0.18s ease-out;
+}
+
+@keyframes slideUp {
+ from {
+ transform: translateY(100%);
+ }
+ to {
+ transform: translateY(0);
+ }
+}
diff --git a/src/app/_components/core/bottom-sheet.tsx b/src/app/_components/core/bottom-sheet.tsx
new file mode 100644
index 00000000..b9b1a333
--- /dev/null
+++ b/src/app/_components/core/bottom-sheet.tsx
@@ -0,0 +1,75 @@
+"use client";
+/**
+ * 通用底部弹层(Bottom Sheet)
+ *
+ * 原始 Dart: 散落在各处的 `showModalBottomSheet`(auth_other_options_dialog 等)
+ *
+ * 设计目标:
+ * - portal 渲染到 body
+ * - 固定底部、最大宽度 500px、顶部圆角 `--radius-bottom-sheet`
+ * - ESC 关闭 / 点击 scrim 关闭 / 打开时锁定 body 滚动
+ * - 简单 slide-up 动画(与原 Dart 行为接近)
+ * - 调用方只关心内容布局
+ */
+import { type ReactNode, useEffect } from "react";
+import { createPortal } from "react-dom";
+
+import styles from "./bottom-sheet.module.css";
+
+export interface BottomSheetProps {
+ open: boolean;
+ onClose: () => void;
+ children: ReactNode;
+ /** 蒙层透明度(0-1)。默认 0.45。 */
+ scrimOpacity?: number;
+ /** 禁用点击外部 / ESC 关闭。 */
+ persistent?: boolean;
+ /** ARIA 标签。 */
+ ariaLabel?: string;
+}
+
+export function BottomSheet({
+ open,
+ onClose,
+ children,
+ scrimOpacity = 0.45,
+ persistent = false,
+ ariaLabel,
+}: BottomSheetProps) {
+ useEffect(() => {
+ if (!open || typeof document === "undefined") return;
+ const onKey = (e: KeyboardEvent) => {
+ if (e.key === "Escape" && !persistent) onClose();
+ };
+ document.addEventListener("keydown", onKey);
+ const prevOverflow = document.body.style.overflow;
+ document.body.style.overflow = "hidden";
+ return () => {
+ document.removeEventListener("keydown", onKey);
+ document.body.style.overflow = prevOverflow;
+ };
+ }, [open, onClose, persistent]);
+
+ if (!open || typeof document === "undefined") return null;
+
+ return createPortal(
+
{
+ if (!persistent && e.target === e.currentTarget) onClose();
+ }}
+ >
+
e.stopPropagation()}
+ >
+ {children}
+
+
,
+ document.body,
+ );
+}
diff --git a/src/app/auth/components/auth-email-panel.module.css b/src/app/auth/components/auth-email-panel.module.css
index c8dbcf02..bd95551f 100644
--- a/src/app/auth/components/auth-email-panel.module.css
+++ b/src/app/auth/components/auth-email-panel.module.css
@@ -1,21 +1,75 @@
+/* 原始 Dart: lib/ui/auth/widgets/auth_email_panel.dart
+ *
+ * 视觉规格(与 Dart 对齐):
+ * - 顶部:login 模式有 logo + 24px spacer;register 模式两个 24px spacer
+ * - 表单 + 12px gap + 切换链接 + 16px gap + 弹层链接
+ * - Spacer + 法律
+ * - 切换链接:14px 黑色下划线居中
+ * - "Other Sign In Options" 链接:14px 黑色下划线居中
+ */
.panel {
display: flex;
flex-direction: column;
- gap: var(--spacing-md);
+ align-items: stretch;
+ flex: 1 1 auto;
width: 100%;
+ min-height: 0;
}
-.backButton {
- align-self: flex-start;
+.topSection {
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+}
+
+.spacer24 {
+ height: var(--spacing-xxl);
+}
+
+.logo {
+ display: block;
+ height: var(--auth-logo-height);
+ width: auto;
+ max-width: 220px;
+ margin: 0 auto;
+ object-fit: contain;
+}
+
+.spacer {
+ flex: 1 1 0;
+ min-height: var(--spacing-lg);
+}
+
+.toggleButton {
+ align-self: center;
background: none;
border: none;
- padding: 0;
- color: var(--color-text-secondary);
- font-size: var(--font-size-sm);
+ padding: var(--spacing-md) 0 0;
+ font-size: var(--font-size-md);
+ color: var(--color-auth-text-primary);
+ text-decoration: underline;
+ text-underline-offset: 3px;
cursor: pointer;
- transition: color 0.15s ease;
+ transition: opacity 0.15s ease;
}
-.backButton:hover {
- color: var(--color-text-primary);
+.toggleButton:hover {
+ opacity: 0.8;
+}
+
+.linkButton {
+ align-self: center;
+ background: none;
+ border: none;
+ padding: var(--spacing-md) 0 var(--spacing-md);
+ font-size: var(--font-size-md);
+ color: var(--color-auth-text-primary);
+ text-decoration: underline;
+ text-underline-offset: 3px;
+ cursor: pointer;
+ transition: opacity 0.15s ease;
+}
+
+.linkButton:hover {
+ opacity: 0.8;
}
diff --git a/src/app/auth/components/auth-email-panel.tsx b/src/app/auth/components/auth-email-panel.tsx
index 15a0f3ba..7f9af2ae 100644
--- a/src/app/auth/components/auth-email-panel.tsx
+++ b/src/app/auth/components/auth-email-panel.tsx
@@ -1,47 +1,117 @@
"use client";
/**
- * Email 面板
+ * Email 面板(login/register 内部切换)
*
* 原始 Dart: lib/ui/auth/widgets/auth_email_panel.dart
*
- * 行为:
- * - 内部 `authMode`(login/register)切换显示 Login / Register 表单
- * - 提交派发 `AuthEmailLoginSubmitted` 或 `AuthEmailRegisterSubmitted`
+ * 视觉规格(与 Dart 对齐):
+ * - login 模式:顶部 24px spacer + logo(120h) + 24px gap
+ * - register 模式:48px spacer(两个 24px,无 logo)
+ * - 表单(EmailLoginForm / EmailRegisterForm)
+ * - 12px gap + 切换链接("Don't have an account? Sign up" / "Already have an account? Log in")
+ * - 16px gap + "Other Sign In Options" 链接(打开底部弹层)
+ * - Spacer + AuthLegalText
+ * - 顶部 "← Back" 按钮由父级 AuthPanel 渲染(悬浮 40×40 圆形)
*/
import { useState } from "react";
import { useAuthState } from "@/stores/auth/auth-context";
+import { AuthErrorMessage } from "./auth-error-message";
+import { AuthLegalText } from "./auth-legal-text";
+import { AuthOtherOptionsDialog } from "./auth-other-options-dialog";
import { EmailLoginForm } from "./email-login-form";
import { EmailRegisterForm } from "./email-register-form";
import styles from "./auth-email-panel.module.css";
export interface AuthEmailPanelProps {
- onBack: () => void;
+ /** "Other Sign-in Options" 弹层中 Facebook 按钮回调(派发回 facebook 模式) */
+ onSwitchToFacebook: () => void;
+ /** "Other Sign-in Options" 弹层中 Google 按钮回调(可空;空时不渲染 Google 行) */
+ onGoogle?: () => void;
+ /** 弹层 Google 自定义子节点 */
+ googleSlot?: React.ReactNode;
}
-export function AuthEmailPanel({ onBack }: AuthEmailPanelProps) {
+export function AuthEmailPanel({
+ onSwitchToFacebook,
+ onGoogle,
+ googleSlot,
+}: AuthEmailPanelProps) {
const state = useAuthState();
const [mode, setMode] = useState<"login" | "register">("login");
+ const [showOptions, setShowOptions] = useState(false);
return (
-
+
+ {mode === "login" ? (
+ <>
+
+

+ >
+ ) : (
+ <>
+
+
+ >
+ )}
+
+
+
{mode === "login" ? (
setMode("register")}
globalError={state.errorMessage}
isLoading={state.isLoading}
/>
) : (
setMode("login")}
globalError={state.errorMessage}
isLoading={state.isLoading}
/>
)}
+
+
+
+
+
+
+
+
+
+
+ setShowOptions(false)}
+ mode="email"
+ onFacebook={onSwitchToFacebook}
+ onGoogle={() => {
+ setShowOptions(false);
+ onGoogle?.();
+ }}
+ onEmail={() => {
+ // 在 email 模式下不再显示 Email 入口
+ }}
+ googleSlot={googleSlot}
+ />
);
}
diff --git a/src/app/auth/components/auth-error-message.module.css b/src/app/auth/components/auth-error-message.module.css
index ac8d8eb4..608e3980 100644
--- a/src/app/auth/components/auth-error-message.module.css
+++ b/src/app/auth/components/auth-error-message.module.css
@@ -1,10 +1,16 @@
+/* 原始 Dart: lib/ui/auth/widgets/auth_error_message.dart
+ *
+ * 视觉规格(与 Dart 对齐):
+ * - 细小红字,无背景,无圆角,无边框
+ * - 12px 居中
+ */
.banner {
width: 100%;
- padding: var(--spacing-md);
- border-radius: var(--radius-md);
- background: rgba(255, 77, 79, 0.12);
- border: var(--border-light) solid rgba(255, 77, 79, 0.3);
+ padding: 0;
+ background: transparent;
+ border: none;
color: var(--color-error);
font-size: var(--font-size-sm);
line-height: 1.4;
+ text-align: center;
}
diff --git a/src/app/auth/components/auth-facebook-panel.module.css b/src/app/auth/components/auth-facebook-panel.module.css
index 31c5fefd..2a156e2d 100644
--- a/src/app/auth/components/auth-facebook-panel.module.css
+++ b/src/app/auth/components/auth-facebook-panel.module.css
@@ -1,21 +1,111 @@
+/* 原始 Dart: lib/ui/auth/widgets/auth_facebook_panel.dart
+ *
+ * 视觉规格(与 Dart 对齐):
+ * - Spacer → logo(120h)→ Spacer → Facebook 主按钮 → 链接 → Spacer → 法律
+ * - Facebook 主按钮:46px 白胶囊 + Facebook "f" 图标(#1877f2)
+ * - 链接:14px 黑色下划线居中
+ */
.panel {
display: flex;
flex-direction: column;
+ align-items: stretch;
+ flex: 1 1 auto;
+ width: 100%;
+ min-height: 0;
+}
+
+.spacer {
+ flex: 1 1 0;
+ min-height: var(--spacing-lg);
+}
+
+.logo {
+ display: block;
+ height: var(--auth-logo-height);
+ width: auto;
+ max-width: 220px;
+ margin: 0 auto;
+ object-fit: contain;
+}
+
+.facebookButton {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
gap: var(--spacing-md);
width: 100%;
+ height: var(--auth-field-height);
+ padding: 0 var(--spacing-lg);
+ border: none;
+ border-radius: var(--radius-xxxl);
+ background: var(--color-auth-surface);
+ color: var(--color-auth-text-primary);
+ font-size: var(--font-size-md);
+ font-weight: 600;
+ cursor: pointer;
+ margin-top: var(--spacing-md);
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06);
+ transition: filter 0.15s ease, transform 0.05s ease, opacity 0.15s ease;
+}
+
+.facebookButton:hover:not(:disabled) {
+ filter: brightness(0.97);
+}
+
+.facebookButton:disabled {
+ opacity: 0.6;
+ cursor: not-allowed;
+}
+
+.facebookButton:not(:disabled):active {
+ transform: scale(0.98);
+}
+
+.facebookIcon {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 20px;
+ height: 20px;
+ flex: 0 0 auto;
+}
+
+.facebookLabel {
+ flex: 1 1 auto;
+ text-align: center;
+}
+
+.spinner {
+ display: inline-block;
+ width: 18px;
+ height: 18px;
+ border-width: 2px;
+ border-style: solid;
+ border-color: var(--color-facebook-blue) transparent transparent transparent;
+ border-radius: 50%;
+ animation: spin 0.8s linear infinite;
+ flex: 0 0 auto;
}
.linkButton {
+ align-self: center;
background: none;
border: none;
- padding: 0;
- font-size: var(--font-size-sm);
- color: var(--color-accent);
- text-align: center;
+ padding: var(--spacing-md) 0;
+ font-size: var(--font-size-md);
+ color: var(--color-auth-text-primary);
+ text-decoration: underline;
+ text-underline-offset: 3px;
cursor: pointer;
transition: opacity 0.15s ease;
}
.linkButton:hover {
- opacity: 0.85;
+ opacity: 0.8;
+}
+
+@keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
}
diff --git a/src/app/auth/components/auth-facebook-panel.tsx b/src/app/auth/components/auth-facebook-panel.tsx
index b02e7477..4a98a6ca 100644
--- a/src/app/auth/components/auth-facebook-panel.tsx
+++ b/src/app/auth/components/auth-facebook-panel.tsx
@@ -1,24 +1,23 @@
"use client";
/**
- * Facebook 面板(默认面板)
+ * Facebook 面板(默认登录面板)
*
* 原始 Dart: lib/ui/auth/widgets/auth_facebook_panel.dart
*
- * 行为:
- * - 默认显示「Continue with Facebook」+ 「Other sign-in options」入口
- * - 社交登录直接调 `nextauth.facebookLogin()` / `nextauth.googleLogin()`(NextAuth 流程)
+ * 视觉规格(与 Dart 对齐):
+ * - Spacer → logo(120h)→ Spacer → Facebook 主按钮 → 链接 → Spacer → 法律
+ * - Facebook 主按钮:46px 白胶囊 + Facebook "f" 图标(#1877f2)+ "Login with Facebook"
+ * - 社交登录走 NextAuth:`facebookLogin()` / `googleLogin()`
+ * - "Other Sign-in Options" 链接打开底部弹层
+ * - 弹层 Email 按钮 → 触发 `onSwitchToEmail`(父级派发 `AuthPanelModeChanged`)
*/
import { useState } from "react";
-import { useAuthState } from "@/stores/auth/auth-context";
import { facebookLogin, googleLogin } from "@/lib/auth/nextauth";
-import { AuthSocialButtons } from "./auth-social-buttons";
-import { AuthDivider } from "./auth-divider";
-import { AuthPrimaryButton } from "./auth-primary-button";
import { AuthErrorMessage } from "./auth-error-message";
-import { AuthOtherOptionsDialog } from "./auth-other-options-dialog";
import { AuthLegalText } from "./auth-legal-text";
+import { AuthOtherOptionsDialog } from "./auth-other-options-dialog";
import styles from "./auth-facebook-panel.module.css";
export interface AuthFacebookPanelProps {
@@ -26,65 +25,76 @@ export interface AuthFacebookPanelProps {
}
export function AuthFacebookPanel({ onSwitchToEmail }: AuthFacebookPanelProps) {
- const state = useAuthState();
const [showOptions, setShowOptions] = useState(false);
const [busy, setBusy] = useState(null);
+ const [error, setError] = useState(null);
const handleFacebook = async () => {
setBusy("facebook");
+ setError(null);
const r = await facebookLogin();
setBusy(null);
- if (!r.success) console.error("[auth-facebook-panel]", r.error);
+ if (!r.success) setError(r.error?.message ?? "Facebook login failed");
};
const handleGoogle = async () => {
setBusy("google");
+ setError(null);
const r = await googleLogin();
setBusy(null);
- if (!r.success) console.error("[auth-facebook-panel]", r.error);
+ if (!r.success) setError(r.error?.message ?? "Google login failed");
};
return (
-
-
+

-
-
- Continue with Email
-
+
+
+
+
+
+
+
+
+
setShowOptions(false)}
- onFacebook={() => {
- setShowOptions(false);
- void handleFacebook();
- }}
- onGoogle={() => {
- setShowOptions(false);
- void handleGoogle();
- }}
- onApple={() => {
- setShowOptions(false);
- console.warn("Apple login not implemented");
- }}
- onEmail={() => {
- setShowOptions(false);
- onSwitchToEmail();
- }}
- loadingProvider={busy ?? (state.isLoading ? "facebook" : null)}
- showApple={false}
+ mode="facebook"
+ onFacebook={() => void handleFacebook()}
+ onGoogle={() => void handleGoogle()}
+ onEmail={onSwitchToEmail}
/>
);
diff --git a/src/app/auth/components/auth-legal-text.module.css b/src/app/auth/components/auth-legal-text.module.css
index 7d0d29b7..1a6c4a4e 100644
--- a/src/app/auth/components/auth-legal-text.module.css
+++ b/src/app/auth/components/auth-legal-text.module.css
@@ -1,13 +1,53 @@
+/* 原始 Dart: lib/ui/auth/widgets/auth_legal_text.dart
+ *
+ * 视觉规格(与 Dart 对齐):
+ * - 行:16px 白色圆形复选框 + 12px 富文本
+ * - 复选框选中时 10px 粉色实心圆
+ * - 链接加粗(Privacy Policy / Terms of Service)
+ */
+.row {
+ display: flex;
+ align-items: flex-start;
+ gap: var(--spacing-sm);
+ width: 100%;
+}
+
+.checkbox {
+ flex: 0 0 auto;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--auth-legal-checkbox-size);
+ height: var(--auth-legal-checkbox-size);
+ border-radius: 50%;
+ background: var(--color-auth-surface);
+ border: 1px solid var(--color-auth-surface);
+ padding: 0;
+ cursor: pointer;
+ margin-top: 2px;
+ box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.06);
+}
+
+.checkboxInner {
+ display: block;
+ width: var(--auth-legal-checkbox-inner-size);
+ height: var(--auth-legal-checkbox-inner-size);
+ border-radius: 50%;
+ background: var(--color-auth-legal-check);
+}
+
.text {
margin: 0;
font-size: var(--font-size-sm);
- color: var(--color-text-secondary);
- text-align: center;
- line-height: 1.5;
+ line-height: 1.4;
+ color: var(--color-auth-text-primary);
+ text-align: left;
+ flex: 1 1 auto;
}
.link {
- color: var(--color-accent);
+ color: var(--color-auth-text-primary);
+ font-weight: 700;
text-decoration: none;
}
diff --git a/src/app/auth/components/auth-legal-text.tsx b/src/app/auth/components/auth-legal-text.tsx
index f25cb333..83079e50 100644
--- a/src/app/auth/components/auth-legal-text.tsx
+++ b/src/app/auth/components/auth-legal-text.tsx
@@ -1,23 +1,57 @@
"use client";
/**
- * 法律声明(Terms / Privacy)
+ * 法律声明(Terms / Privacy + 复选框)
*
* 原始 Dart: lib/ui/auth/widgets/auth_legal_text.dart
+ *
+ * 视觉规格(与 Dart 对齐):
+ * - 行:16px 白色圆形复选框 + 12px 富文本
+ * - 复选框:白底 + 1px 白色边框;选中时 10px 粉色实心圆
+ * - 默认选中(仅视觉,不阻塞提交)
+ * - 链接加粗(Privacy Policy / Terms of Service)
+ * - 文字深色
*/
+import { useState } from "react";
+
+import { AppConstants } from "@/core/constants/app_constants";
+
import styles from "./auth-legal-text.module.css";
export function AuthLegalText() {
+ const [checked, setChecked] = useState(true);
+
return (
-
- By continuing, you agree to our{" "}
-
- Terms
- {" "}
- and{" "}
-
- Privacy Policy
-
- .
-
+
+
+
+ By continuing, you agree to {AppConstants.appTitle}'s{" "}
+
+ Privacy Policy
+ {" "}
+ and{" "}
+
+ Terms of Service
+
+ .
+
+
);
}
diff --git a/src/app/auth/components/auth-other-options-dialog.module.css b/src/app/auth/components/auth-other-options-dialog.module.css
index 6220ba13..c8e6a34d 100644
--- a/src/app/auth/components/auth-other-options-dialog.module.css
+++ b/src/app/auth/components/auth-other-options-dialog.module.css
@@ -1,34 +1,53 @@
+/* 原始 Dart: lib/ui/auth/widgets/auth_other_options_dialog.dart
+ *
+ * 视觉规格(与 Dart 对齐):
+ * - drag handle 40×4 灰胶囊
+ * - 标题 24px bold 深色居中
+ * - 选项按钮 40px 白胶囊
+ * - Cancel 14px 深色居中文本按钮
+ */
.body {
display: flex;
flex-direction: column;
+ align-items: stretch;
gap: var(--spacing-md);
- padding: var(--spacing-xl);
+ padding: var(--spacing-md) var(--spacing-xxl)
+ calc(var(--spacing-xxl) + env(safe-area-inset-bottom));
+}
+
+.handle {
+ align-self: center;
+ width: var(--auth-drag-handle-width);
+ height: var(--auth-drag-handle-height);
+ border-radius: 2px;
+ background: var(--color-auth-drag-handle);
+ margin-bottom: var(--spacing-sm);
}
.title {
- margin: 0;
- font-size: var(--font-size-xl);
- font-weight: 600;
- color: var(--color-text-primary);
+ margin: var(--spacing-sm) 0;
+ font-size: var(--font-size-bottom-sheet-title);
+ font-weight: 700;
+ color: var(--color-auth-text-primary);
text-align: center;
}
-.emailButton {
- display: inline-flex;
- align-items: center;
- justify-content: center;
+.googleSlot {
width: 100%;
- height: var(--button-height);
- padding: 0 var(--spacing-lg);
- border: var(--border-light) solid var(--color-chat-input-border);
- border-radius: var(--radius-full);
- background: transparent;
- color: var(--color-text-primary);
- font-size: var(--font-size-md);
- cursor: pointer;
- transition: background 0.15s ease;
}
-.emailButton:hover {
- background: rgba(255, 255, 255, 0.06);
+.cancel {
+ align-self: center;
+ background: none;
+ border: none;
+ padding: var(--spacing-md) 0;
+ color: var(--color-auth-text-primary);
+ font-size: var(--font-size-md);
+ font-weight: 500;
+ cursor: pointer;
+ text-decoration: none;
+}
+
+.cancel:hover {
+ text-decoration: underline;
}
diff --git a/src/app/auth/components/auth-other-options-dialog.tsx b/src/app/auth/components/auth-other-options-dialog.tsx
index cd8565c5..69183253 100644
--- a/src/app/auth/components/auth-other-options-dialog.tsx
+++ b/src/app/auth/components/auth-other-options-dialog.tsx
@@ -1,12 +1,23 @@
"use client";
/**
- * "其他登录方式" 弹窗
+ * "其他登录方式" 底部弹层
*
* 原始 Dart: lib/ui/auth/widgets/auth_other_options_dialog.dart
+ * (showModalBottomSheet:白卡 + 顶部圆角 radius28 + drag handle)
+ *
+ * 视觉规格(与 Dart 对齐):
+ * - 固定底部、宽 100%(≤500px)、顶部圆角 28px
+ * - drag handle 40×4 灰胶囊
+ * - 标题 "Other Sign-in Options" 24px bold 深色
+ * - 条件渲染 Email / Facebook 按钮(40px 白胶囊 + 边框 + 图标)
+ * - 始终渲染 Google 按钮
+ * - Cancel 文本按钮
*/
-import { Dialog } from "@/app/_components/core/dialog";
-import { AuthSocialButtons } from "./auth-social-buttons";
-import { AuthDivider } from "./auth-divider";
+import type { ReactNode } from "react";
+
+import { BottomSheet } from "@/app/_components/core/bottom-sheet";
+
+import { AuthSocialButton } from "./auth-social-button";
import styles from "./auth-other-options-dialog.module.css";
export interface AuthOtherOptionsDialogProps {
@@ -14,11 +25,11 @@ export interface AuthOtherOptionsDialogProps {
onClose: () => void;
onFacebook: () => void;
onGoogle: () => void;
- onApple?: () => void;
onEmail: () => void;
- loadingProvider?: "facebook" | "google" | "apple" | null;
- showApple?: boolean;
- googleSlot?: React.ReactNode;
+ /** 当前面板模式:facebook 模式下显示 Email + Google;email 模式下显示 Facebook + Google。 */
+ mode: "facebook" | "email";
+ /** 自定义子节点(如嵌入 GIS 按钮) */
+ googleSlot?: ReactNode;
}
export function AuthOtherOptionsDialog({
@@ -26,29 +37,99 @@ export function AuthOtherOptionsDialog({
onClose,
onFacebook,
onGoogle,
- onApple,
onEmail,
- loadingProvider,
- showApple,
+ mode,
googleSlot,
}: AuthOtherOptionsDialogProps) {
return (
-
+
);
}
diff --git a/src/app/auth/components/auth-panel.module.css b/src/app/auth/components/auth-panel.module.css
index 4e492bd2..2011d5ac 100644
--- a/src/app/auth/components/auth-panel.module.css
+++ b/src/app/auth/components/auth-panel.module.css
@@ -1,7 +1,42 @@
-.panel {
+/* 原始 Dart: lib/ui/auth/widgets/auth_panel.dart
+ *
+ * 视觉规格(与 Dart 对齐):
+ * - panelShell 包裹整个面板(position: relative)
+ * - 悬浮返回按钮:40×40 白色圆形 + 淡黑阴影,固定 top/left 20px
+ */
+.panelShell {
+ position: relative;
+ flex: 1 1 auto;
display: flex;
flex-direction: column;
- gap: var(--spacing-lg);
width: 100%;
- padding: var(--spacing-xl);
+ min-height: 0;
+}
+
+.backButton {
+ position: absolute;
+ top: var(--spacing-xl);
+ left: var(--spacing-xl);
+ z-index: 2;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--auth-back-button-size);
+ height: var(--auth-back-button-size);
+ border: none;
+ border-radius: 50%;
+ background: var(--color-auth-surface);
+ color: var(--color-auth-text-primary);
+ padding: 0 2px 0 0;
+ cursor: pointer;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+ transition: background 0.15s ease, transform 0.05s ease;
+}
+
+.backButton:hover {
+ background: rgba(0, 0, 0, 0.04);
+}
+
+.backButton:active {
+ transform: scale(0.96);
}
diff --git a/src/app/auth/components/auth-panel.tsx b/src/app/auth/components/auth-panel.tsx
index 58a7d372..ab763606 100644
--- a/src/app/auth/components/auth-panel.tsx
+++ b/src/app/auth/components/auth-panel.tsx
@@ -1,30 +1,74 @@
"use client";
/**
- * 认证面板:顶层 switch(Facebook / Email)
+ * 认证面板:顶层 switch(Facebook / Email)+ 悬浮返回按钮
*
* 原始 Dart: lib/ui/auth/widgets/auth_panel.dart
+ *
+ * 行为对齐:
+ * - 状态机:`authPanelMode`("facebook" | "email")由 XState 管理
+ * - 切换派发 `AuthPanelModeChanged`
+ * - 悬浮返回按钮(40×40 白圆,top:20, left:20):
+ * - facebook 模式:`router.back()`
+ * - email 模式:派发 `AuthPanelModeChanged(facebook)`
+ * - Email 面板的弹层 Facebook 按钮同样派发 `AuthPanelModeChanged(facebook)`
*/
-import { useAuthState } from "@/stores/auth/auth-context";
+import { useRouter } from "next/navigation";
+
+import { useAuthDispatch, useAuthState } from "@/stores/auth/auth-context";
-import { AuthFacebookPanel } from "./auth-facebook-panel";
import { AuthEmailPanel } from "./auth-email-panel";
+import { AuthFacebookPanel } from "./auth-facebook-panel";
import styles from "./auth-panel.module.css";
export function AuthPanel() {
const state = useAuthState();
+ const dispatch = useAuthDispatch();
+ const router = useRouter();
+
+ const switchToEmail = () =>
+ dispatch({ type: "AuthPanelModeChanged", mode: "email" });
+ const switchToFacebook = () =>
+ dispatch({ type: "AuthPanelModeChanged", mode: "facebook" });
+
+ const handleBack = () => {
+ if (state.authPanelMode === "email") {
+ switchToFacebook();
+ } else {
+ router.back();
+ }
+ };
+
return (
-
+
+
+
+
+
{state.authPanelMode === "facebook" ? (
-
{
- // 切换模式由外部 dispatch 触发;这里不直接处理
- // 因为 auth-panel 受 `authPanelMode` 控制,调用方需要 dispatch `AuthPanelModeChanged`
- // 但我们目前 AuthFacebookPanel 内部 click "Continue with Email" 直接调用 onSwitchToEmail
- // 实际上本组件直接渲染对应 panel
+
+ ) : (
+ {
+ switchToFacebook();
}}
/>
- ) : (
- undefined} />
)}
);
diff --git a/src/app/auth/components/auth-primary-button.module.css b/src/app/auth/components/auth-primary-button.module.css
index 481bf6bc..2d2fcf6f 100644
--- a/src/app/auth/components/auth-primary-button.module.css
+++ b/src/app/auth/components/auth-primary-button.module.css
@@ -1,22 +1,37 @@
+/* 原始 Dart: lib/ui/auth/widgets/auth_primary_button.dart
+ *
+ * 视觉规格(与 Dart 对齐):
+ * - 46px 高
+ * - 粉渐变(#f96ADE → #f657A0,左→右)
+ * - 半径 24
+ * - 20% 红阴影(#d00c41 @ 20%)
+ * - 文字 16px bold 白色
+ */
.button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--spacing-sm);
width: 100%;
- height: var(--button-height);
+ height: var(--auth-field-height);
padding: 0 var(--spacing-lg);
border: none;
- border-radius: var(--radius-full);
- font-size: var(--font-size-md);
- font-weight: 600;
- color: var(--color-text-primary);
+ border-radius: var(--radius-xxxl);
+ background: linear-gradient(
+ 90deg,
+ var(--color-auth-primary-gradient-start),
+ var(--color-auth-primary-gradient-end)
+ );
+ box-shadow: 0 3px 5px var(--color-auth-primary-button-shadow);
+ color: #ffffff;
+ font-size: var(--font-size-lg);
+ font-weight: 700;
cursor: pointer;
- transition: opacity 0.15s ease, transform 0.05s ease;
+ transition: filter 0.15s ease, transform 0.05s ease, opacity 0.15s ease;
}
.button:disabled {
- opacity: 0.6;
+ opacity: 0.7;
cursor: not-allowed;
}
@@ -24,25 +39,18 @@
transform: scale(0.98);
}
-.accent {
- background: linear-gradient(
- 90deg,
- var(--color-accent),
- var(--color-facebook-button-gradient-end)
- );
-}
-
-.primary {
- background: var(--color-primary);
+.label {
+ flex: 1 1 auto;
+ text-align: center;
}
.spinner {
display: inline-block;
- width: 16px;
- height: 16px;
+ width: 18px;
+ height: 18px;
border-width: 2px;
border-style: solid;
- border-color: currentColor transparent transparent transparent;
+ border-color: #ffffff transparent transparent transparent;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
diff --git a/src/app/auth/components/auth-primary-button.tsx b/src/app/auth/components/auth-primary-button.tsx
index 1914ab94..a85f50dd 100644
--- a/src/app/auth/components/auth-primary-button.tsx
+++ b/src/app/auth/components/auth-primary-button.tsx
@@ -1,8 +1,15 @@
"use client";
/**
- * 认证主按钮(粉/蓝渐变)
+ * 认证主按钮(粉渐变,46px)
*
* 原始 Dart: lib/ui/auth/widgets/auth_primary_button.dart
+ *
+ * 视觉规格(与 Dart 对齐):
+ * - 46px 高
+ * - 粉渐变(#f96ADE → #f657A0)
+ * - 半径 24
+ * - 20% 红阴影(#d00c41 @ 20%)
+ * - 文字 16px bold 白色
*/
import { type ButtonHTMLAttributes, type ReactNode } from "react";
@@ -12,28 +19,21 @@ export interface AuthPrimaryButtonProps
extends Omit
, "children"> {
isLoading?: boolean;
children: ReactNode;
- variant?: "accent" | "primary";
}
export function AuthPrimaryButton({
isLoading,
children,
- variant = "accent",
disabled,
className,
+ type = "button",
...rest
}: AuthPrimaryButtonProps) {
return (
{isLoading ? (
diff --git a/src/app/auth/components/auth-screen.module.css b/src/app/auth/components/auth-screen.module.css
new file mode 100644
index 00000000..67485eb0
--- /dev/null
+++ b/src/app/auth/components/auth-screen.module.css
@@ -0,0 +1,23 @@
+/* 原始 Dart: lib/ui/auth/auth_screen.dart
+ *
+ * 视觉规格(与 Dart 对齐):
+ * - 全屏 `bg_login.png` 背景(cover)
+ * - 500px 内容列由 MobileShell 约束
+ * - 内容列居中
+ */
+.fullBleed {
+ position: relative;
+ flex: 1 1 auto;
+ display: flex;
+ flex-direction: column;
+ min-height: 100dvh;
+ background: url("/images/auth/bg-login.png") center / cover no-repeat;
+}
+
+.content {
+ flex: 1 1 auto;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ padding: var(--spacing-lg);
+}
diff --git a/src/app/auth/components/auth-screen.tsx b/src/app/auth/components/auth-screen.tsx
index 4649f4a9..17c360ab 100644
--- a/src/app/auth/components/auth-screen.tsx
+++ b/src/app/auth/components/auth-screen.tsx
@@ -5,10 +5,11 @@
* 原始 Dart: lib/ui/auth/auth_screen.dart
*
* 行为对齐:
- * - 500px 移动端宽度(MobileShell)
- * - 已登录用户:自动跳 /chat
- * - 未登录:渲染 AuthPanel(含 Facebook/Email 两种面板)
- * - 登录成功:通知 ChatBloc + UserBloc,然后 router.replace(/chat)
+ * - 500px 移动端宽度(MobileShell)
+ * - 全屏 `bg_login.png` 背景(cover 模式,扩展到 viewport)
+ * - 已登录用户:自动跳 /chat
+ * - 未登录:渲染 AuthPanel(含 Facebook/Email 两种面板)
+ * - 登录成功:通知 ChatBloc + UserBloc,然后 router.replace(/chat)
*/
import { useEffect, useRef } from "react";
import { useRouter } from "next/navigation";
@@ -20,14 +21,9 @@ import { ROUTES } from "@/router/routes";
import { MobileShell } from "@/app/_components/core/mobile-shell";
import { AuthPanel } from "@/app/auth/components/auth-panel";
-export interface AuthScreenProps {
- /** 自定义背景(默认紫粉渐变)。 */
- background?: string;
-}
+import styles from "./auth-screen.module.css";
-export function AuthScreen({
- background = "var(--color-sidebar-background)",
-}: AuthScreenProps) {
+export function AuthScreen() {
// ===== 鉴权路由跳转已由 src/router/proxy.ts 集中处理 =====
// 本组件只负责:
// 1. 渲染登录面板
@@ -50,18 +46,12 @@ export function AuthScreen({
}, [state.isSuccess, chatDispatch, userDispatch, router]);
return (
-
-
-
+
);
}
diff --git a/src/app/auth/components/auth-social-button.module.css b/src/app/auth/components/auth-social-button.module.css
index fd3049c7..36b41014 100644
--- a/src/app/auth/components/auth-social-button.module.css
+++ b/src/app/auth/components/auth-social-button.module.css
@@ -1,23 +1,33 @@
+/* 原始 Dart: lib/ui/auth/widgets/auth_social_button.dart
+ *
+ * 视觉规格(与 Dart 弹层按钮对齐):
+ * - 40px 高
+ * - 白底 + 1px 边框 `--color-auth-border` (#d5d7db)
+ * - 半径 20
+ * - 无阴影
+ * - 图标 20×20 + 标签
+ * - 14px 文字深色
+ */
.button {
display: inline-flex;
align-items: center;
justify-content: center;
- gap: var(--spacing-sm);
+ gap: var(--spacing-md);
width: 100%;
- height: var(--button-height);
+ height: var(--auth-social-button-height);
padding: 0 var(--spacing-lg);
- border: var(--border-light) solid var(--color-chat-input-border);
- border-radius: var(--radius-full);
- background: rgba(255, 255, 255, 0.04);
- color: var(--color-text-primary);
+ border: 1px solid var(--color-auth-border);
+ border-radius: var(--radius-xxl);
+ background: var(--color-auth-surface);
+ color: var(--color-auth-text-primary);
font-size: var(--font-size-md);
font-weight: 500;
cursor: pointer;
- transition: background 0.15s ease, border-color 0.15s ease;
+ transition: background 0.15s ease, opacity 0.15s ease;
}
.button:hover:not(:disabled) {
- background: rgba(255, 255, 255, 0.08);
+ background: rgba(0, 0, 0, 0.02);
}
.button:disabled {
@@ -31,22 +41,7 @@
justify-content: center;
width: 20px;
height: 20px;
-}
-
-.facebook {
- background: var(--color-facebook-blue);
- border-color: transparent;
-}
-
-.google {
- background: #ffffff;
- color: #1f1f1f;
- border-color: transparent;
-}
-
-.apple {
- background: #000000;
- border-color: #ffffff;
+ flex: 0 0 auto;
}
.label {
diff --git a/src/app/auth/components/auth-social-button.tsx b/src/app/auth/components/auth-social-button.tsx
index 37280150..760d08fc 100644
--- a/src/app/auth/components/auth-social-button.tsx
+++ b/src/app/auth/components/auth-social-button.tsx
@@ -1,10 +1,21 @@
"use client";
/**
- * 社交登录按钮
+ * 社交/选项登录按钮(用于底部弹层)
*
* 原始 Dart: lib/ui/auth/widgets/auth_social_button.dart
+ *
+ * 视觉规格(与 Dart 弹层按钮对齐):
+ * - 40px 高
+ * - 白底 + 1px 边框 `--color-auth-border`
+ * - 半径 20
+ * - 无阴影
+ * - 图标 20×20 + 标签
+ * - 14px 文字深色
*/
-import { type ButtonHTMLAttributes, type ReactNode } from "react";
+import {
+ type ButtonHTMLAttributes,
+ type ReactNode,
+} from "react";
import styles from "./auth-social-button.module.css";
@@ -12,24 +23,21 @@ export interface AuthSocialButtonProps
extends Omit, "children"> {
icon?: ReactNode;
children: ReactNode;
- variant?: "facebook" | "google" | "apple" | "default";
}
export function AuthSocialButton({
icon,
children,
- variant = "default",
disabled,
className,
+ type = "button",
...rest
}: AuthSocialButtonProps) {
return (
{icon ? {icon} : null}
diff --git a/src/app/auth/components/auth-text-field.module.css b/src/app/auth/components/auth-text-field.module.css
index bec6910e..63e9c7a1 100644
--- a/src/app/auth/components/auth-text-field.module.css
+++ b/src/app/auth/components/auth-text-field.module.css
@@ -1,3 +1,13 @@
+/* 原始 Dart: lib/ui/auth/widgets/auth_text_field.dart
+ *
+ * 视觉规格(与 Dart 对齐):
+ * - 46px 高
+ * - 白底胶囊(半径 24)
+ * - 无边框
+ * - 10% 粉色阴影(accent #f759a8 @ 10%)
+ * - padding: 0 16 0 24
+ * - 文字深色 #333,占位符 #999
+ */
.wrapper {
display: flex;
flex-direction: column;
@@ -12,32 +22,35 @@
.input {
width: 100%;
- height: var(--button-height);
- padding: 0 var(--spacing-lg);
- border: var(--border-light) solid var(--color-chat-input-border);
- border-radius: var(--radius-lg);
- background: rgba(255, 255, 255, 0.04);
- color: var(--color-text-primary);
+ height: var(--auth-field-height);
+ padding: 0 var(--spacing-lg) 0 var(--spacing-xxl);
+ border: none;
+ border-radius: var(--radius-xxxl);
+ background: var(--color-auth-surface);
+ color: var(--color-auth-text-primary);
font-size: var(--font-size-md);
+ box-shadow: 0 3px 5px var(--color-auth-input-shadow);
outline: none;
- transition: border-color 0.15s ease, box-shadow 0.15s ease;
+ transition: box-shadow 0.15s ease;
}
.input::placeholder {
- color: var(--color-input-placeholder);
+ color: var(--color-auth-input-placeholder);
}
.input:focus {
- border-color: var(--color-accent);
- box-shadow: 0 0 0 3px rgba(248, 77, 150, 0.18);
+ box-shadow: 0 3px 5px var(--color-auth-input-shadow),
+ 0 0 0 2px var(--color-accent);
}
.invalid {
- border-color: var(--color-error);
+ box-shadow: 0 3px 5px rgba(255, 77, 79, 0.18),
+ 0 0 0 1px var(--color-error);
}
.invalid:focus {
- box-shadow: 0 0 0 3px rgba(255, 77, 79, 0.18);
+ box-shadow: 0 3px 5px rgba(255, 77, 79, 0.18),
+ 0 0 0 2px var(--color-error);
}
.error {
diff --git a/src/app/auth/components/email-form.module.css b/src/app/auth/components/email-form.module.css
index 44e06848..c06e9c28 100644
--- a/src/app/auth/components/email-form.module.css
+++ b/src/app/auth/components/email-form.module.css
@@ -1,26 +1,14 @@
+/* 邮箱登录/注册表单样式
+ *
+ * 原始 Dart: lib/ui/auth/widgets/email_login_form.dart
+ *
+ * 视觉规格(与 Dart 对齐):
+ * - 字段间距 12px(Dart AppSpacing.md)
+ * - 错误横幅与主按钮之间 12px
+ */
.form {
display: flex;
flex-direction: column;
gap: var(--spacing-md);
width: 100%;
}
-
-.linkButton {
- background: none;
- border: none;
- padding: 0;
- font-size: var(--font-size-sm);
- color: var(--color-text-secondary);
- text-align: center;
- cursor: pointer;
- transition: color 0.15s ease;
-}
-
-.linkButton:hover {
- color: var(--color-text-primary);
-}
-
-.link {
- color: var(--color-accent);
- font-weight: 600;
-}
diff --git a/src/app/auth/components/email-login-form.tsx b/src/app/auth/components/email-login-form.tsx
index 5ce3fa93..f0d067f7 100644
--- a/src/app/auth/components/email-login-form.tsx
+++ b/src/app/auth/components/email-login-form.tsx
@@ -3,6 +3,13 @@
* 邮箱登录表单
*
* 原始 Dart: lib/ui/auth/widgets/email_login_form.dart
+ *
+ * 视觉规格(与 Dart 对齐):
+ * - 字段无 label(仅 placeholder)
+ * - 错误仅以红色横幅显示(AuthErrorMessage),无字段级错误
+ * - 不显示 "Forgot password?" 链接
+ * - 切换链接由父级 AuthEmailPanel 渲染
+ * - 提交派发 `AuthEmailLoginSubmitted`
*/
import { useState } from "react";
@@ -18,33 +25,22 @@ import { AuthErrorMessage } from "./auth-error-message";
import styles from "./email-form.module.css";
export interface EmailLoginFormProps {
- onSwitchToRegister: () => void;
- onForgotPassword?: () => void;
globalError?: string | null;
isLoading?: boolean;
}
export function EmailLoginForm({
- onSwitchToRegister,
- onForgotPassword,
globalError,
isLoading,
}: EmailLoginFormProps) {
const dispatch = useAuthDispatch();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
- const [errors, setErrors] = useState<{ email?: string; password?: string }>(
- {},
- );
const submit = () => {
const emailErr = validateEmail(email);
const passwordErr = validatePassword(password);
- if (emailErr || passwordErr) {
- setErrors({ email: emailErr ?? undefined, password: passwordErr ?? undefined });
- return;
- }
- setErrors({});
+ if (emailErr || passwordErr) return;
dispatch({ type: "AuthEmailLoginSubmitted", email, password });
};
@@ -56,45 +52,25 @@ export function EmailLoginForm({
submit();
}}
>
-
- {onForgotPassword ? (
-
- Forgot password?
-
- ) : null}
+
- Log In
+ Login
-
- Don't have an account? Sign up
-
);
}
diff --git a/src/app/auth/components/email-register-form.tsx b/src/app/auth/components/email-register-form.tsx
index 617b126d..a47902f7 100644
--- a/src/app/auth/components/email-register-form.tsx
+++ b/src/app/auth/components/email-register-form.tsx
@@ -3,6 +3,13 @@
* 邮箱注册表单
*
* 原始 Dart: lib/ui/auth/widgets/email_register_form.dart
+ *
+ * 视觉规格(与 Dart 对齐):
+ * - 4 字段:username / email / password / confirmPassword(顺序与 Dart 一致)
+ * - 字段无 label(仅 placeholder)
+ * - 错误仅以红色横幅显示(AuthErrorMessage),无字段级错误
+ * - 切换链接由父级 AuthEmailPanel 渲染
+ * - 提交派发 `AuthEmailRegisterSubmitted`
*/
import { useState } from "react";
@@ -20,13 +27,11 @@ import { AuthErrorMessage } from "./auth-error-message";
import styles from "./email-form.module.css";
export interface EmailRegisterFormProps {
- onSwitchToLogin: () => void;
globalError?: string | null;
isLoading?: boolean;
}
export function EmailRegisterForm({
- onSwitchToLogin,
globalError,
isLoading,
}: EmailRegisterFormProps) {
@@ -35,25 +40,13 @@ export function EmailRegisterForm({
const [password, setPassword] = useState("");
const [username, setUsername] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
- const [errors, setErrors] = useState<
- Partial>
- >({});
const submit = () => {
const emailErr = validateEmail(email);
const passwordErr = validatePassword(password);
const usernameErr = validateUsername(username);
const confirmErr = validateConfirmPassword(password, confirmPassword);
- if (emailErr || passwordErr || usernameErr || confirmErr) {
- setErrors({
- email: emailErr ?? undefined,
- password: passwordErr ?? undefined,
- username: usernameErr ?? undefined,
- confirmPassword: confirmErr ?? undefined,
- });
- return;
- }
- setErrors({});
+ if (emailErr || passwordErr || usernameErr || confirmErr) return;
dispatch({
type: "AuthEmailRegisterSubmitted",
email,
@@ -71,53 +64,38 @@ export function EmailRegisterForm({
submit();
}}
>
-
+
- Create Account
+ Register
-
- Already have an account? Log in
-
);
}
diff --git a/src/tokens/colors.css b/src/tokens/colors.css
index be8e703b..6af14f1d 100644
--- a/src/tokens/colors.css
+++ b/src/tokens/colors.css
@@ -53,4 +53,34 @@
/* 半透明 / 模糊 */
--color-blur-background: rgba(13, 11, 20, 0.85);
+
+ /* ==================== Auth 页专用(与 Dart auth_screen.dart 对齐) ==================== */
+ /* 浅色表面(Auth 页内容卡片 / 弹层背景) */
+ --color-auth-surface: #ffffff;
+
+ /* 弹层内第三方登录按钮边框 */
+ --color-auth-border: #d5d7db;
+
+ /* Auth 页输入框占位符 */
+ --color-auth-input-placeholder: #999999;
+
+ /* Auth 页文字主色(深色,区别于全局白字) */
+ --color-auth-text-primary: #333333;
+ --color-auth-text-secondary: #9e9e9e;
+
+ /* Auth 输入框阴影(accent #f759a8 10% 透明) */
+ --color-auth-input-shadow: rgba(247, 89, 168, 0.10);
+
+ /* Auth 主按钮阴影(#d00c41 20% 透明) */
+ --color-auth-primary-button-shadow: rgba(208, 12, 65, 0.20);
+
+ /* Auth 主按钮渐变(与 Dart primaryGradient 对齐) */
+ --color-auth-primary-gradient-start: #f96ade;
+ --color-auth-primary-gradient-end: #f657a0;
+
+ /* Legal 复选框选中点 */
+ --color-auth-legal-check: #f657a0;
+
+ /* 底部弹层 drag handle */
+ --color-auth-drag-handle: #3a3a3a;
}
diff --git a/src/tokens/dimensions.css b/src/tokens/dimensions.css
index c32e00a3..341a1763 100644
--- a/src/tokens/dimensions.css
+++ b/src/tokens/dimensions.css
@@ -32,4 +32,31 @@
/* 聊天媒体 */
--chat-media-max-width: 240px;
--chat-media-max-height: 240px;
+
+ /* ==================== Auth 页尺寸(与 Dart AppDimensions / Spacing 对齐) ==================== */
+ /* 输入框 / 主按钮高度(46px = Dart AppDimensions.height46) */
+ --auth-field-height: 46px;
+
+ /* 弹层内第三方登录按钮高度(40px) */
+ --auth-social-button-height: 40px;
+
+ /* 主按钮固定宽度(Dart AppDimensions.buttonWidth = 334) */
+ --auth-primary-button-width: 334px;
+
+ /* Logo 高度(120px) */
+ --auth-logo-height: 120px;
+
+ /* Legal 圆形复选框 */
+ --auth-legal-checkbox-size: 16px;
+ --auth-legal-checkbox-inner-size: 10px;
+
+ /* 悬浮返回按钮(40×40 圆形) */
+ --auth-back-button-size: 40px;
+
+ /* 底部弹层圆角(28px = Dart AppRadius.radius28) */
+ --auth-bottom-sheet-radius: 28px;
+
+ /* 弹层 drag handle(40×4 胶囊) */
+ --auth-drag-handle-width: 40px;
+ --auth-drag-handle-height: 4px;
}
diff --git a/src/tokens/radius.css b/src/tokens/radius.css
index f96096c8..75998fb5 100644
--- a/src/tokens/radius.css
+++ b/src/tokens/radius.css
@@ -10,4 +10,5 @@
--radius-xxl: 20px;
--radius-xxxl: 24px;
--radius-full: 999px;
+ --radius-bottom-sheet: 28px;
}
diff --git a/src/tokens/typography.css b/src/tokens/typography.css
index c4efb0d9..320c436f 100644
--- a/src/tokens/typography.css
+++ b/src/tokens/typography.css
@@ -28,4 +28,7 @@
/* 扩展字号(迁移自 Flutter widgets) */
--font-size-22: 22px;
--font-size-26: 26px;
+
+ /* Auth 底部弹层标题(24px bold) */
+ --font-size-bottom-sheet-title: 24px;
}