feat(assets): add initial fonts and image assets

This commit is contained in:
2026-06-09 15:47:20 +08:00
parent cda55c8f9b
commit e2f60bc4f2
41 changed files with 207 additions and 70 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 KiB

+35 -1
View File
@@ -1,5 +1,6 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google"; import { Geist, Geist_Mono } from "next/font/google";
import localFont from "next/font/local";
import Script from "next/script"; import Script from "next/script";
import "./globals.css"; import "./globals.css";
@@ -15,6 +16,39 @@ const geistMono = Geist_Mono({
subsets: ["latin"], subsets: ["latin"],
}); });
/**
* Athelas 字体加载
* 原始资源:/Users/chase/Documents/cozsweet/fonts/Athelas-*.ttf
* 放置位置:/public/fonts/Athelas-*.ttf
*
* 路径计算(`next/font/local` 要求相对路径从调用文件出发):
* src/app/layout.tsx → ../../public/fonts/...
*
* 通过 `next/font/local` 自动 preload、压缩、inline。
* 输出 CSS 变量 `--font-athelas`,由 `globals.css` 桥接到 Tailwind `font-athelas`。
*/
const athelas = localFont({
src: [
{
path: "../../public/fonts/Athelas-Regular.ttf",
weight: "400",
style: "normal",
},
{
path: "../../public/fonts/Athelas-Bold.ttf",
weight: "700",
style: "normal",
},
{
path: "../../public/fonts/Athelas-BoldItalic.ttf",
weight: "700",
style: "italic",
},
],
variable: "--font-athelas",
display: "swap",
});
export const metadata: Metadata = { export const metadata: Metadata = {
title: "cozsweet", title: "cozsweet",
description: "cozsweet frontend (Next.js)", description: "cozsweet frontend (Next.js)",
@@ -31,7 +65,7 @@ export default function RootLayout({
<html <html
lang="en" lang="en"
suppressHydrationWarning suppressHydrationWarning
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`} className={`${geistSans.variable} ${geistMono.variable} ${athelas.variable} h-full antialiased`}
> >
<body className="min-h-full flex flex-col"> <body className="min-h-full flex flex-col">
{/* Facebook SDK JSauth + splash 入口会用到 FB.login */} {/* Facebook SDK JSauth + splash 入口会用到 FB.login */}
@@ -1,8 +1,12 @@
.bg { .bg {
position: absolute; position: absolute;
inset: 0; inset: 0;
background:
radial-gradient(circle at 80% 20%, rgba(248, 77, 150, 0.25), transparent 60%),
linear-gradient(180deg, #1a0f2a 0%, #0d0b14 100%);
z-index: 0; z-index: 0;
background: var(--color-sidebar-background);
overflow: hidden;
}
.image {
object-fit: cover;
object-position: center;
} }
@@ -1,14 +1,18 @@
"use client"; "use client";
/** import Image from "next/image";
* Splash 背景
*
* 原始 Dart: lib/ui/splash/widgets/splash_background.dart
* 使用 `Assets.images.picBgHome.image(fit: BoxFit.cover)` 渲染全屏背景图。
*
* 当前实现:纯色 + CSS 渐变占位(待 `public/splash/bg.png` 资源到位后可切换为 `<Image>`)。
*/
import styles from "./splash-background.module.css"; import styles from "./splash-background.module.css";
export function SplashBackground() { export function SplashBackground() {
return <div className={styles.bg} aria-hidden="true" />; return (
<div className={styles.bg}>
<Image
src="/images/splash/pic-bg-home.png"
alt=""
fill
priority
sizes="100vw"
className={styles.image}
/>
</div>
);
} }
@@ -1,7 +1,61 @@
.wrapper { .wrapper {
display: flex; display: flex;
flex-direction: column; flex-direction: row;
gap: var(--spacing-md); align-items: center;
justify-content: center;
gap: var(--spacing-26);
width: 100%; width: 100%;
z-index: 2; z-index: 2;
} }
.skip {
font-family: var(--font-athelas);
font-size: var(--font-size-xxl);
font-weight: 700;
color: #ffffff;
background: transparent;
border: 0;
cursor: pointer;
padding: 0;
}
.skip:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.separator {
width: var(--spacing-26);
height: 1px;
}
.facebookButton {
height: 48px;
padding: 0 30px;
border: 0;
border-radius: 24px;
background: linear-gradient(
to right,
#fff96ade,
#fff657a0
);
color: #ffffff;
font-weight: 700;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
box-shadow: 0 0 10px rgba(248, 89, 168, 0.3);
}
.facebookButton:disabled {
cursor: not-allowed;
opacity: 0.7;
}
.facebookLabel {
font-family: var(--font-athelas);
font-size: var(--font-size-xxl);
font-weight: 700;
color: #ffffff;
}
+45 -17
View File
@@ -1,35 +1,63 @@
"use client"; "use client";
/** /**
* Splash 底部按钮组Skip + Facebook 登录) * Splash 底部按钮组
* *
* 原始 Dart: lib/ui/splash/widgets/splash_button.dart * 原始 Dart: lib/ui/splash/widgets/splash_button.dart
* `Row(children: [Skip 文本, SizedBox 26, Facebook 登录按钮])`
*
* 关键对齐点:
* - 仅两个元素:Skip 文本 + Facebook 登录按钮
* - 居中布局 (MainAxisAlignment.center)
* - Facebook 按钮:带渐变(primaryGradient+ 圆角 24 + 高度 48
* - 加载中:按钮内显示 spinner
*/ */
import { useAuthState } from "@/contexts/auth/auth-context"; import { useAuthState, useAuthDispatch } from "@/contexts/auth/auth-context";
import { LoadingIndicator } from "@/app/_components/core/loading-indicator";
import { AuthSocialButtons } from "@/app/auth/components/auth-social-buttons";
import { AuthPrimaryButton } from "@/app/auth/components/auth-primary-button";
import { AuthDivider } from "@/app/auth/components/auth-divider";
import { useAuthDispatch } from "@/contexts/auth/auth-context";
import styles from "./splash-button.module.css"; import styles from "./splash-button.module.css";
export interface SplashButtonProps { export interface SplashButtonProps {
onSkip: () => void; onSkip: () => void;
onFacebookLogin: () => void;
} }
export function SplashButton({ onSkip }: SplashButtonProps) { export function SplashButton({
onSkip,
onFacebookLogin,
}: SplashButtonProps) {
const state = useAuthState(); const state = useAuthState();
const dispatch = useAuthDispatch(); const dispatch = useAuthDispatch();
const isLoading = state.isLoading;
return ( return (
<div className={styles.wrapper}> <div className={styles.wrapper}>
<AuthSocialButtons {/* Skip 文本按钮 */}
loadingProvider={state.isLoading ? "facebook" : null} <button
onFacebook={() => dispatch({ type: "AuthFacebookLoginSubmitted" })} type="button"
onGoogle={() => dispatch({ type: "AuthGoogleLoginSubmitted" })} onClick={onSkip}
/> className={styles.skip}
<AuthDivider label="or" /> disabled={isLoading}
<AuthPrimaryButton onClick={onSkip} variant="primary"> >
Skip Continue as Guest Skip
</AuthPrimaryButton> </button>
<div className={styles.separator} aria-hidden="true" />
{/* Facebook 登录按钮(带渐变) */}
<button
type="button"
onClick={() => {
dispatch({ type: "AuthFacebookLoginSubmitted" });
onFacebookLogin();
}}
disabled={isLoading}
className={styles.facebookButton}
>
{isLoading ? (
<LoadingIndicator color="#ffffff" />
) : (
<span className={styles.facebookLabel}>Login with Facebook</span>
)}
</button>
</div> </div>
); );
} }
@@ -2,21 +2,17 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--spacing-md); gap: var(--spacing-md);
color: var(--color-text-primary);
z-index: 2; z-index: 2;
} }
.title { .title {
margin: 0;
font-family: var(--font-athelas); font-family: var(--font-athelas);
font-size: var(--font-size-26); font-size: var(--font-size-title);
font-weight: 600; font-weight: 700;
line-height: 1.3; font-style: italic;
} line-height: 1.4;
color: #ffffff;
.subtitle { text-align: left;
white-space: pre-line;
margin: 0; margin: 0;
font-size: var(--font-size-md);
line-height: 1.5;
color: rgba(255, 255, 255, 0.8);
} }
+14 -5
View File
@@ -1,18 +1,27 @@
"use client"; "use client";
/** /**
* Splash 内容文案 * Splash 内容文案
* *
* 原始 Dart: lib/ui/splash/widgets/splash_content.dart * 原始 Dart: lib/ui/splash/widgets/splash_content.dart
*
* 关键差异:
* - 使用 Athelas 字体(衬线体)
* - 标题加粗 + 斜体(与原 Dart 一致)
* - 颜色:白色(背景图上需高对比度)
*/ */
import styles from "./splash-content.module.css"; import styles from "./splash-content.module.css";
const APOSTROPHE = "&#39;";
export function SplashContent() { export function SplashContent() {
return ( return (
<div className={styles.content}> <div className={styles.content}>
<h1 className={styles.title}>Chat with Elio, anytime, anywhere</h1> <h1 className={styles.title}>
<p className={styles.subtitle}> Welcome to my secret hideout~
A safe, private space to talk, share, and unwind your AI companion who&apos;s always there. {"\n"}It{APOSTROPHE}s just the two of us now.
</p> {"\n"}Feel free to whisper your
{"\n"}little secrets.
</h1>
</div> </div>
); );
} }
@@ -1,20 +1,12 @@
.logo { .logo {
display: flex; display: inline-flex;
flex-direction: column;
gap: var(--spacing-xs);
color: var(--color-text-primary);
z-index: 2; z-index: 2;
} }
.brand { .image {
font-family: var(--font-athelas); display: block;
font-size: var(--font-size-title-x-large); width: auto;
font-weight: 700; height: auto;
letter-spacing: -0.02em; /* Apple 平台 Athelas 渲染时仍可能需要 fallback */
line-height: 1; font-family: var(--font-athelas, serif);
}
.tagline {
font-size: var(--font-size-md);
color: var(--color-text-secondary);
} }
+12 -5
View File
@@ -3,17 +3,24 @@
* Splash Logo * Splash Logo
* *
* 原始 Dart: lib/ui/splash/widgets/splash_logo.dart * 原始 Dart: lib/ui/splash/widgets/splash_logo.dart
* * 资源: /public/images/splash/ic-logo-home.png (kebab-case 重命名后)
* 当前实现:纯文字 logo"cozsweet" + tagline)。 * 原名: ic_logo_home.png (snake_case)
* 资源:`Assets.images.icLogoHome`(待替换为 `<Image src="/splash/logo.svg" />`)。 * 渲染: Assets.images.icLogoHome.image() → next/image <Image>
*/ */
import Image from "next/image";
import styles from "./splash-logo.module.css"; import styles from "./splash-logo.module.css";
export function SplashLogo() { export function SplashLogo() {
return ( return (
<div className={styles.logo}> <div className={styles.logo}>
<span className={styles.brand}>cozsweet</span> <Image
<span className={styles.tagline}>Your exclusive AI boyfriend</span> src="/images/splash/ic-logo-home.png"
alt="cozsweet"
width={120}
height={40}
priority
className={styles.image}
/>
</div> </div>
); );
} }
@@ -8,6 +8,7 @@
overflow: hidden; overflow: hidden;
} }
/* 渐变:accent (bottom-left) → transparent (center-right) */
.gradientOverlay { .gradientOverlay {
position: absolute; position: absolute;
inset: 0; inset: 0;
@@ -16,7 +17,7 @@
var(--color-accent), var(--color-accent),
transparent 60% transparent 60%
); );
opacity: 0.4; opacity: 0.5;
z-index: 1; z-index: 1;
pointer-events: none; pointer-events: none;
} }
@@ -40,7 +41,9 @@
.bottom { .bottom {
margin: var(--spacing-xxxl) 0 0 0; margin: var(--spacing-xxxl) 0 0 0;
font-family: var(--font-athelas);
font-size: var(--font-size-md); font-size: var(--font-size-md);
font-weight: 400;
line-height: 1.5; line-height: 1.5;
color: rgba(255, 255, 255, 0.9); color: rgba(255, 255, 255, 0.9);
text-align: center; text-align: center;
+7 -1
View File
@@ -42,13 +42,19 @@ export function SplashScreen() {
<MobileShell background="var(--color-sidebar-background)"> <MobileShell background="var(--color-sidebar-background)">
<div className={styles.wrapper}> <div className={styles.wrapper}>
<SplashBackground /> <SplashBackground />
{/* 渐变叠加层:accent → transparent (bottom-left → center-right) */}
<div className={styles.gradientOverlay} aria-hidden="true" /> <div className={styles.gradientOverlay} aria-hidden="true" />
<div className={styles.content}> <div className={styles.content}>
<SplashLogo /> <SplashLogo />
<div className={styles.spacer} /> <div className={styles.spacer} />
<SplashContent /> <SplashContent />
<div className={styles.buttonArea}> <div className={styles.buttonArea}>
<SplashButton onSkip={() => router.push(ROUTES.chat)} /> <SplashButton
onSkip={() => router.push(ROUTES.chat)}
onFacebookLogin={() => {
/* Facebook 登录触发由 SplashButton 内部 dispatch */
}}
/>
</div> </div>
<p className={styles.bottom}> <p className={styles.bottom}>
Elio Silvestri, Your exclusive AI boyfriend Elio Silvestri, Your exclusive AI boyfriend