diff --git a/packages/core/scripts/generate-site.mjs b/packages/core/scripts/generate-site.mjs index 23509f8..bf21b6a 100644 --- a/packages/core/scripts/generate-site.mjs +++ b/packages/core/scripts/generate-site.mjs @@ -23,6 +23,7 @@ const SITES_ROOT = path.join(REPO_ROOT, "sites"); // ==================================================================== const SITES = { "ai-stack": { + authVariant: "glass", themeClass: "site-ai-stack", brandZh: { name: "AI Stack", @@ -92,6 +93,7 @@ const SITES = { }, }, launchai: { + authVariant: "gradient", themeClass: "site-launchai", brandZh: { name: "LaunchAI", @@ -159,6 +161,7 @@ const SITES = { }, }, "the-ai-ledger": { + authVariant: "editorial", themeClass: "site-the-ai-ledger", brandZh: { name: "The AI Ledger", @@ -227,6 +230,7 @@ const SITES = { }, }, "devai-tools": { + authVariant: "terminal", themeClass: "site-devai-tools", brandZh: { name: "DevAI.tools", @@ -293,6 +297,7 @@ const SITES = { }, }, findai: { + authVariant: "minimal", themeClass: "site-findai", brandZh: { name: "findai", @@ -776,6 +781,7 @@ NEXT_PUBLIC_APP_URL="http://localhost:${site.port}" NEXT_PUBLIC_APP_NAME="${site.brand.name}" # 登录页品牌模板(分栏侧栏颜色与标语) NEXT_PUBLIC_AUTH_ACCENT="${site.accent}" +NEXT_PUBLIC_AUTH_VARIANT="${site.authVariant}" NEXT_PUBLIC_AUTH_BRAND_TAGLINE="${site.brand.tagline}" # 开发阶段免邮箱验证(生产置 false 发送真实验证邮件) DEV_SKIP_EMAIL_VERIFY="true" diff --git a/packages/core/src/features/auth/components/login-form.tsx b/packages/core/src/features/auth/components/login-form.tsx index a0d938b..8c32227 100644 --- a/packages/core/src/features/auth/components/login-form.tsx +++ b/packages/core/src/features/auth/components/login-form.tsx @@ -109,6 +109,7 @@ export function LoginForm() { isLoading={isLoading} /> } + variant={(process.env.NEXT_PUBLIC_AUTH_VARIANT as "glass" | "gradient" | "editorial" | "terminal" | "minimal" | "split" | undefined) ?? "split"} brandName={process.env.NEXT_PUBLIC_APP_NAME} brandTagline={process.env.NEXT_PUBLIC_AUTH_BRAND_TAGLINE} accent={process.env.NEXT_PUBLIC_AUTH_ACCENT} diff --git a/packages/core/src/features/auth/components/signup-form.tsx b/packages/core/src/features/auth/components/signup-form.tsx index b36d352..38c39b5 100644 --- a/packages/core/src/features/auth/components/signup-form.tsx +++ b/packages/core/src/features/auth/components/signup-form.tsx @@ -127,6 +127,7 @@ export function SignupForm() { isLoading={isLoading} /> } + variant={(process.env.NEXT_PUBLIC_AUTH_VARIANT as "glass" | "gradient" | "editorial" | "terminal" | "minimal" | "split" | undefined) ?? "split"} brandName={process.env.NEXT_PUBLIC_APP_NAME} brandTagline={process.env.NEXT_PUBLIC_AUTH_BRAND_TAGLINE} accent={process.env.NEXT_PUBLIC_AUTH_ACCENT} diff --git a/packages/core/src/features/forms/components/form-shell.tsx b/packages/core/src/features/forms/components/form-shell.tsx index 076a14a..12a5f97 100644 --- a/packages/core/src/features/forms/components/form-shell.tsx +++ b/packages/core/src/features/forms/components/form-shell.tsx @@ -6,7 +6,6 @@ import { useTranslations } from 'next-intl'; import { Form } from "@/components/ui/form"; import { Logo } from "@/components/Logo"; -import { Button } from "@/components/button"; import { cn } from "@/lib/utils"; interface FormShellProps { @@ -23,14 +22,26 @@ interface FormShellProps { className?: string; headerSlot?: React.ReactNode; socialSlot?: React.ReactNode; - /** 站点品牌名(默认取环境变量,五站登录页各不相同) */ + /** 站点品牌名 */ brandName?: string; /** 品牌侧栏标语 */ brandTagline?: string; - /** 品牌强调色 HSL 三元组(如 "16 85% 55%"),传入即启用分栏品牌模板 */ + /** 品牌强调色 HSL 三元组 */ accent?: string; + /** 登录布局变体:五种站点五种形态 */ + variant?: "glass" | "gradient" | "editorial" | "terminal" | "minimal" | "split"; } +/** + * # 组件 FormShell + * 说明:认证表单外壳 —— 六种布局形态,五个网站各用一种: + * - glass 深蓝渐变全屏 + 玻璃拟态浮动卡片(AI Stack) + * - gradient 品牌色渐变全屏 + 白色大圆角卡片(LaunchAI) + * - editorial 报纸头版:衬线居中报头 + 下划线输入 + 方角黑按钮(The AI Ledger) + * - terminal 深色终端窗口 + 等宽输入 + 命令行按钮(DevAI.tools) + * - minimal 纯白极简居中 + 大圆角搜索式输入(findai) + * - split 经典左右分栏(后备) + */ export function FormShell({ form, title, @@ -48,99 +59,231 @@ export function FormShell({ brandName, brandTagline, accent, + variant, }: FormShellProps) { const t = useTranslations('auth.social'); const brand = brandName || process.env.NEXT_PUBLIC_APP_NAME || 'AI Directory'; - // 品牌模式用站点品牌字标(不传 headerSlot 时),非品牌模式保留原 Logo - const headerContent = - headerSlot !== undefined - ? headerSlot - : accent - ? - - {brand.slice(0, 1)} - - {brand} - - : ; + const tagline = brandTagline || `Welcome back to ${brand}.`; + const accentHsl = accent ? `hsl(${accent})` : 'hsl(222 47% 11%)'; + const initial = brand.slice(0, 1); - // 品牌模板:传入 accent 时渲染分栏布局(左侧品牌区 / 右侧表单),每站视觉不同 - const formColumn = ( -
-
-
{headerContent}
-

- {title} -

- {description ? ( -

{description}

- ) : null} -
-
-
- {socialSlot} - {socialSlot ? ( -
- - {t('orContinueWith')} - -
- ) : null} - {children} - {error ? ( -
{error}
- ) : null} - -
- {footer} -
+ const errorBox = error ? ( +
+ {error}
+ ) : null; + + const submitBtn = (cls: string) => ( + ); - if (accent) { + const footerNode = footer ?
{footer}
: null; + + /* ================= 1. AI Stack:深蓝渐变 + 玻璃卡片 ================= */ + if (variant === "glass") { return (
- {/* 品牌分栏模板:col-span 全宽(覆盖 AuthLayout 的 2 列网格),左品牌右表单 */} -
-
-
- - {brand.slice(0, 1)} +
+
+
+
+
+ + {initial} - {brand} + {brand}
-
-

- {brandTagline || `Welcome back to ${brand}.`} -

-

- Vote, review and save the AI tools you love — join the community. -

-
-

© {new Date().getFullYear()} {brand}

-
-
- {formColumn} +

{title}

+ {description &&

{description}

} + + {socialSlot} + {children} + {errorBox} + {submitBtn("w-full rounded-xl bg-white py-3 text-sm font-semibold text-slate-900 transition hover:bg-white/90 disabled:opacity-60")} + + {footerNode}
); } + /* ================= 2. LaunchAI:品牌渐变 + 白色大圆角卡片 ================= */ + if (variant === "gradient") { + return ( +
+
+
+
+ + {initial} + + {brand} +
+

{title}

+ {description &&

{description}

} + + {socialSlot} + {children} + {errorBox} + {submitBtn(`w-full rounded-2xl py-3.5 text-base font-bold text-white transition hover:opacity-90 disabled:opacity-60`)} + + {footerNode} +
+
+ + ); + } + + /* ================= 3. The AI Ledger:报纸头版 + 下划线输入 ================= */ + if (variant === "editorial") { + const lineInput = + "w-full border-0 border-b-2 border-neutral-300 bg-transparent px-0 py-2 text-lg font-serif text-neutral-900 placeholder:text-neutral-400 focus:border-neutral-900 focus:outline-none rounded-none"; + return ( +
+
+
+ {/* 报头 */} +
+

+ {new Date().toLocaleDateString("en-US", { weekday: "long", month: "long", day: "numeric", year: "numeric" })} +

+

{brand}

+

{tagline}

+
+ +

{title}

+ + {socialSlot} + {children} + {errorBox} + {submitBtn("w-full border-2 border-neutral-900 bg-neutral-900 py-3 text-sm font-bold uppercase tracking-[0.2em] text-white transition hover:bg-transparent hover:text-neutral-900 disabled:opacity-60")} + + {footerNode} +
+
+ © {new Date().getFullYear()} {brand} +
+
+ + ); + } + + /* ================= 4. DevAI.tools:深色终端窗口 ================= */ + if (variant === "terminal") { + const termInput = + "w-full rounded border border-emerald-500/30 bg-black/50 px-3 py-2.5 font-mono text-sm text-emerald-100 placeholder:text-emerald-100/30 focus:border-emerald-400/70 focus:outline-none rounded-md"; + const termLabel = "mb-1 block font-mono text-xs text-emerald-500/80"; + return ( +
+
+
+ {/* 终端标题栏 */} +
+ + + + {brand.toLowerCase()} — auth +
+
+

+ $ {brand.toLowerCase()} auth --login +

+ + {socialSlot} + {children} + {errorBox} + {submitBtn("w-full rounded-md bg-emerald-500 py-3 font-mono text-sm font-bold text-black transition hover:bg-emerald-400 disabled:opacity-60")} + + {footerNode} +
+
+
+ + ); + } + + /* ================= 5. findai:纯白极简居中 ================= */ + if (variant === "minimal") { + const softInput = + "w-full rounded-2xl border border-transparent bg-neutral-100 px-5 py-4 text-base text-foreground placeholder:text-neutral-400 focus:border-violet-300 focus:bg-white focus:outline-none focus:ring-4 focus:ring-violet-500/10 transition rounded-2xl"; + return ( +
+
+ + {initial} + +

{brand}

+

{tagline}

+
+ + {socialSlot} + {children} + {errorBox} + {submitBtn("w-full rounded-2xl bg-violet-600 py-4 text-base font-semibold text-white transition hover:bg-violet-700 disabled:opacity-60")} + + {footerNode} +
+

{brand} — search, ranked by people

+
+ + ); + } + + /* ================= 后备:左右分栏 ================= */ return (
- {formColumn} +
+
+ + {initial} + + {brand} +
+
+

{tagline}

+

+ Vote, review and save the AI tools you love — join the community. +

+
+

© {new Date().getFullYear()} {brand}

+
+
+
+ + + {initial} + + {brand} + +

{title}

+ {description &&

{description}

} + + {socialSlot} + {children} + {errorBox} + {submitBtn("w-full rounded-xl bg-primary py-3 text-sm font-semibold text-primary-foreground transition hover:opacity-90 disabled:opacity-60")} + + {footerNode} +
+
); diff --git a/packages/core/src/features/forms/components/form-text-field.tsx b/packages/core/src/features/forms/components/form-text-field.tsx index 71af726..965a5e1 100644 --- a/packages/core/src/features/forms/components/form-text-field.tsx +++ b/packages/core/src/features/forms/components/form-text-field.tsx @@ -23,7 +23,7 @@ type FormTextFieldProps = BaseProps & }; const inputClasses = - "block w-full rounded-md border-0 bg-input px-4 py-1.5 text-foreground shadow-aceternity placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring"; + "auth-input block w-full rounded-md border-0 bg-input px-4 py-2.5 text-[0.95rem] text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring transition-colors"; export function FormTextField({ control, diff --git a/packages/core/src/kit/types.ts b/packages/core/src/kit/types.ts index 1705a64..770545f 100644 --- a/packages/core/src/kit/types.ts +++ b/packages/core/src/kit/types.ts @@ -34,6 +34,8 @@ export interface SiteConfig { accent?: string; /** 站点视觉签名 class(挂载在站点外壳根元素,驱动全站差异化样式) */ themeClass?: string; + /** 登录页布局变体:五站五种形态 */ + authVariant?: "glass" | "gradient" | "editorial" | "terminal" | "minimal" | "split"; /** 中文品牌文案(导航/页脚/hero/搜索占位) */ brandZh?: { name?: string; diff --git a/packages/core/src/layouts/auth-layout.tsx b/packages/core/src/layouts/auth-layout.tsx index 42802aa..3921dac 100644 --- a/packages/core/src/layouts/auth-layout.tsx +++ b/packages/core/src/layouts/auth-layout.tsx @@ -1,15 +1,27 @@ "use client"; -import { cn } from "@/lib/utils"; - /** * # 组件 AuthLayout - * 说明:认证页容器。布局职责已下沉到 FormShell(品牌分栏/居中两套模板), - * 这里只提供页面底色,避免双层 grid 叠加导致的多列错位。 + * 说明:认证页容器。布局职责在 FormShell(六种布局变体); + * 这里注入 data-theme(由 NEXT_PUBLIC_AUTH_VARIANT 映射), + * 使输入框/标签/按钮的站点主题化样式(globals.css)在 auth 路由组生效。 */ +const VARIANT_THEME: Record = { + glass: "ai-stack", + gradient: "launchai", + editorial: "the-ai-ledger", + terminal: "devai-tools", + minimal: "findai", + split: "ai-stack", +}; + export function AuthLayout({ children }: { children: React.ReactNode }) { + const variant = process.env.NEXT_PUBLIC_AUTH_VARIANT || "glass"; return ( -
+
{children}
); diff --git a/packages/core/src/styles/globals.css b/packages/core/src/styles/globals.css index 2294cad..b38e58d 100644 --- a/packages/core/src/styles/globals.css +++ b/packages/core/src/styles/globals.css @@ -383,3 +383,80 @@ html { background: hsl(var(--background)); clip-path: polygon(0 100%, 100% 0, 100% 100%, 0 100%); } + +/* ============================================================ + 认证页主题化:输入框 / 标签 / 社交按钮按站点形态变化 + ============================================================ */ + +/* The AI Ledger:下划线输入 + 衬线 + 方角按钮 */ +[data-theme="the-ai-ledger"] .auth-input { + border-radius: 0; + border: 0; + border-bottom: 2px solid hsl(24 10% 70%); + background: transparent; + font-family: Georgia, "Times New Roman", serif; + font-size: 1.05rem; + padding-left: 0; + padding-right: 0; +} +[data-theme="the-ai-ledger"] .auth-input:focus { + border-bottom-color: hsl(24 10% 12%); + box-shadow: none; +} +[data-theme="the-ai-ledger"] form button[type="submit"], +[data-theme="the-ai-ledger"] form .rounded-lg { + border-radius: 1px; +} +[data-theme="the-ai-ledger"] form label { + font-family: Georgia, serif; + letter-spacing: 0.02em; +} + +/* DevAI.tools:终端等宽输入 */ +[data-theme="devai-tools"] .auth-input { + border-radius: 0.375rem; + border: 1px solid hsl(150 60% 45% / 0.3); + background: rgba(0, 0, 0, 0.45); + font-family: ui-monospace, "SF Mono", Menlo, monospace; + color: hsl(150 40% 88%); +} +[data-theme="devai-tools"] .auth-input::placeholder { + color: hsl(150 30% 50% / 0.4); +} +[data-theme="devai-tools"] .auth-input:focus { + border-color: hsl(150 60% 45% / 0.7); + box-shadow: 0 0 0 3px hsl(150 60% 45% / 0.1); +} +[data-theme="devai-tools"] form label { + font-family: ui-monospace, Menlo, monospace; + font-size: 0.75rem; + color: hsl(150 40% 60% / 0.85); +} +[data-theme="devai-tools"] form .rounded-lg { + border-radius: 0.375rem; + border-color: hsl(150 60% 45% / 0.25); + background: rgba(0, 0, 0, 0.3); +} + +/* findai:大圆角浅灰底输入 */ +[data-theme="findai"] .auth-input { + border-radius: 1rem; + background: hsl(0 0% 96%); +} +[data-theme="findai"] .auth-input:focus { + background: white; + box-shadow: 0 0 0 4px hsl(262 65% 52% / 0.1); +} +[data-theme="findai"] form .rounded-lg { + border-radius: 1rem; +} + +/* LaunchAI:大圆角输入 */ +[data-theme="launchai"] .auth-input { + border-radius: 0.875rem; + padding-top: 0.75rem; + padding-bottom: 0.75rem; +} +[data-theme="launchai"] form .rounded-lg { + border-radius: 0.875rem; +} diff --git a/sites/ai-stack/.env.example b/sites/ai-stack/.env.example index d4b82e6..564d0a5 100644 --- a/sites/ai-stack/.env.example +++ b/sites/ai-stack/.env.example @@ -14,6 +14,7 @@ NEXT_PUBLIC_APP_URL="http://localhost:3001" NEXT_PUBLIC_APP_NAME="AI Stack" # 登录页品牌模板(分栏侧栏颜色与标语) NEXT_PUBLIC_AUTH_ACCENT="222 47% 11%" +NEXT_PUBLIC_AUTH_VARIANT="glass" NEXT_PUBLIC_AUTH_BRAND_TAGLINE="The professional AI tools directory — ranked by the community." # 开发阶段免邮箱验证(生产置 false 发送真实验证邮件) DEV_SKIP_EMAIL_VERIFY="true" diff --git a/sites/ai-stack/src/site/brand.tsx b/sites/ai-stack/src/site/brand.tsx index a2505c9..fa0e048 100644 --- a/sites/ai-stack/src/site/brand.tsx +++ b/sites/ai-stack/src/site/brand.tsx @@ -2,6 +2,7 @@ import type { SiteConfig } from "@/kit/types"; // AI Stack —— 独立站点的全部品牌/导航/页脚配置 export const siteConfig: SiteConfig = { + "authVariant": "glass", "themeClass": "site-ai-stack", "brandZh": { "name": "AI Stack", diff --git a/sites/devai-tools/.env.example b/sites/devai-tools/.env.example index 0c6ad2f..47ebe9b 100644 --- a/sites/devai-tools/.env.example +++ b/sites/devai-tools/.env.example @@ -14,6 +14,7 @@ NEXT_PUBLIC_APP_URL="http://localhost:3004" NEXT_PUBLIC_APP_NAME="DevAI.tools" # 登录页品牌模板(分栏侧栏颜色与标语) NEXT_PUBLIC_AUTH_ACCENT="150 60% 30%" +NEXT_PUBLIC_AUTH_VARIANT="terminal" NEXT_PUBLIC_AUTH_BRAND_TAGLINE="AI tools for developers — curated, tested, ranked by the community." # 开发阶段免邮箱验证(生产置 false 发送真实验证邮件) DEV_SKIP_EMAIL_VERIFY="true" diff --git a/sites/devai-tools/src/site/brand.tsx b/sites/devai-tools/src/site/brand.tsx index 5057ae9..f2cadab 100644 --- a/sites/devai-tools/src/site/brand.tsx +++ b/sites/devai-tools/src/site/brand.tsx @@ -2,6 +2,7 @@ import type { SiteConfig } from "@/kit/types"; // DevAI.tools —— 独立站点的全部品牌/导航/页脚配置 export const siteConfig: SiteConfig = { + "authVariant": "terminal", "themeClass": "site-devai-tools", "brandZh": { "name": "DevAI.tools", diff --git a/sites/findai/.env.example b/sites/findai/.env.example index efbe01b..06a62b0 100644 --- a/sites/findai/.env.example +++ b/sites/findai/.env.example @@ -14,6 +14,7 @@ NEXT_PUBLIC_APP_URL="http://localhost:3005" NEXT_PUBLIC_APP_NAME="findai" # 登录页品牌模板(分栏侧栏颜色与标语) NEXT_PUBLIC_AUTH_ACCENT="260 70% 52%" +NEXT_PUBLIC_AUTH_VARIANT="minimal" NEXT_PUBLIC_AUTH_BRAND_TAGLINE="The minimal AI tool search engine." # 开发阶段免邮箱验证(生产置 false 发送真实验证邮件) DEV_SKIP_EMAIL_VERIFY="true" diff --git a/sites/findai/src/site/brand.tsx b/sites/findai/src/site/brand.tsx index c6a8a66..8393365 100644 --- a/sites/findai/src/site/brand.tsx +++ b/sites/findai/src/site/brand.tsx @@ -2,6 +2,7 @@ import type { SiteConfig } from "@/kit/types"; // findai —— 独立站点的全部品牌/导航/页脚配置 export const siteConfig: SiteConfig = { + "authVariant": "minimal", "themeClass": "site-findai", "brandZh": { "name": "findai", diff --git a/sites/launchai/.env.example b/sites/launchai/.env.example index 8ec300e..28201ef 100644 --- a/sites/launchai/.env.example +++ b/sites/launchai/.env.example @@ -14,6 +14,7 @@ NEXT_PUBLIC_APP_URL="http://localhost:3002" NEXT_PUBLIC_APP_NAME="LaunchAI" # 登录页品牌模板(分栏侧栏颜色与标语) NEXT_PUBLIC_AUTH_ACCENT="16 85% 55%" +NEXT_PUBLIC_AUTH_VARIANT="gradient" NEXT_PUBLIC_AUTH_BRAND_TAGLINE="Discover and upvote today's AI products." # 开发阶段免邮箱验证(生产置 false 发送真实验证邮件) DEV_SKIP_EMAIL_VERIFY="true" diff --git a/sites/launchai/src/site/brand.tsx b/sites/launchai/src/site/brand.tsx index 2700a3c..a8f914b 100644 --- a/sites/launchai/src/site/brand.tsx +++ b/sites/launchai/src/site/brand.tsx @@ -2,6 +2,7 @@ import type { SiteConfig } from "@/kit/types"; // LaunchAI —— 独立站点的全部品牌/导航/页脚配置 export const siteConfig: SiteConfig = { + "authVariant": "gradient", "themeClass": "site-launchai", "brandZh": { "name": "LaunchAI", diff --git a/sites/the-ai-ledger/.env.example b/sites/the-ai-ledger/.env.example index c7b149a..3a4dc6b 100644 --- a/sites/the-ai-ledger/.env.example +++ b/sites/the-ai-ledger/.env.example @@ -14,6 +14,7 @@ NEXT_PUBLIC_APP_URL="http://localhost:3003" NEXT_PUBLIC_APP_NAME="The AI Ledger" # 登录页品牌模板(分栏侧栏颜色与标语) NEXT_PUBLIC_AUTH_ACCENT="36 70% 40%" +NEXT_PUBLIC_AUTH_VARIANT="editorial" NEXT_PUBLIC_AUTH_BRAND_TAGLINE="Independent journalism on AI tools, with a community-ranked directory." # 开发阶段免邮箱验证(生产置 false 发送真实验证邮件) DEV_SKIP_EMAIL_VERIFY="true" diff --git a/sites/the-ai-ledger/src/site/brand.tsx b/sites/the-ai-ledger/src/site/brand.tsx index 694d175..cdf8f39 100644 --- a/sites/the-ai-ledger/src/site/brand.tsx +++ b/sites/the-ai-ledger/src/site/brand.tsx @@ -2,6 +2,7 @@ import type { SiteConfig } from "@/kit/types"; // The AI Ledger —— 独立站点的全部品牌/导航/页脚配置 export const siteConfig: SiteConfig = { + "authVariant": "editorial", "themeClass": "site-the-ai-ledger", "brandZh": { "name": "The AI Ledger", diff --git a/sites/the-ai-ledger/src/site/home.tsx b/sites/the-ai-ledger/src/site/home.tsx index a1f22c5..a32e2fd 100644 --- a/sites/the-ai-ledger/src/site/home.tsx +++ b/sites/the-ai-ledger/src/site/home.tsx @@ -64,7 +64,7 @@ async function SiteHome({ locale }: { locale: string }) {
{/* Masthead */} -
+

{t("mastheadLabel")}