feat: 优化聊天背景
|
Before Width: | Height: | Size: 323 KiB |
|
Before Width: | Height: | Size: 360 KiB |
|
Before Width: | Height: | Size: 294 KiB |
|
Before Width: | Height: | Size: 246 KiB |
|
Before Width: | Height: | Size: 268 KiB |
|
Before Width: | Height: | Size: 323 KiB |
|
Before Width: | Height: | Size: 469 KiB |
|
Before Width: | Height: | Size: 213 KiB |
|
Before Width: | Height: | Size: 308 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 180 KiB |
|
Before Width: | Height: | Size: 176 KiB |
|
Before Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 178 KiB |
|
Before Width: | Height: | Size: 201 KiB |
|
Before Width: | Height: | Size: 159 KiB |
|
Before Width: | Height: | Size: 161 KiB |
|
Before Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 131 KiB |
|
Before Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
|
@ -1,4 +0,0 @@
|
||||||
<svg width="38" height="36" viewBox="0 0 38 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M25.7099 2.93655C25.9272 2.12908 27.0728 2.12907 27.2901 2.93655L28.986 9.23985C29.0584 9.50889 29.2627 9.72261 29.5282 9.80701L35.5471 11.7203C36.3075 11.962 36.3075 13.038 35.5471 13.2797L29.5282 15.193C29.2627 15.2774 29.0584 15.4911 28.986 15.7602L27.2901 22.0634C27.0728 22.8709 25.9272 22.8709 25.7099 22.0634L24.014 15.7602C23.9416 15.4911 23.7373 15.2774 23.4718 15.193L17.4529 13.2797C16.6925 13.038 16.6925 11.962 17.4529 11.7203L23.4718 9.80701C23.7373 9.72261 23.9416 9.50889 24.014 9.23985L25.7099 2.93655Z" fill="white"/>
|
|
||||||
<path d="M7.10254 19.6309C7.204 19.2146 7.796 19.2146 7.89746 19.6309L9.1445 24.7479C9.17728 24.8824 9.27586 24.9912 9.40649 25.037L13.9001 26.614C14.2649 26.742 14.2649 27.258 13.9001 27.386L9.40649 28.963C9.27586 29.0088 9.17728 29.1176 9.1445 29.2521L7.89746 34.3691C7.796 34.7854 7.204 34.7854 7.10254 34.3691L5.8555 29.2521C5.82272 29.1176 5.72414 29.0088 5.59351 28.963L1.09994 27.386C0.73511 27.258 0.735109 26.742 1.09994 26.614L5.59351 25.037C5.72414 24.9912 5.82272 24.8824 5.8555 24.7479L7.10254 19.6309Z" fill="white"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.2 KiB |
|
|
@ -19,7 +19,7 @@ export default async function Page({ params }: { params: Promise<{ id: string }>
|
||||||
<IconButton variant="tertiary" size="large" iconfont="icon-Like" />
|
<IconButton variant="tertiary" size="large" iconfont="icon-Like" />
|
||||||
</div>
|
</div>
|
||||||
{/* 内容区 */}
|
{/* 内容区 */}
|
||||||
<div className="mx-auto flex-1 overflow-auto w-full">
|
<div className="mx-auto flex-1 overflow-auto pb-4 w-full">
|
||||||
<header className="flex items-end gap-10 justify-between">
|
<header className="flex items-end gap-10 justify-between">
|
||||||
<div className="flex flex-1 sm:flex-row flex-col gap-6 items-center sm:items-end">
|
<div className="flex flex-1 sm:flex-row flex-col gap-6 items-center sm:items-end">
|
||||||
<Avatar className="size-32">
|
<Avatar className="size-32">
|
||||||
|
|
@ -37,9 +37,7 @@ export default async function Page({ params }: { params: Promise<{ id: string }>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="sm:block hidden">
|
<ChatButton className="sm:block hidden" id={id}></ChatButton>
|
||||||
<ChatButton id={id}></ChatButton>
|
|
||||||
</div>
|
|
||||||
</header>
|
</header>
|
||||||
<div className="w-full flex bg-white/10 rounded-m py-3 mt-12">
|
<div className="w-full flex bg-white/10 rounded-m py-3 mt-12">
|
||||||
<div className="w-full flex items-center flex-col">
|
<div className="w-full flex items-center flex-col">
|
||||||
|
|
@ -67,7 +65,9 @@ export default async function Page({ params }: { params: Promise<{ id: string }>
|
||||||
<div className="mt-4">{character?.description}</div>
|
<div className="mt-4">{character?.description}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="sm:hidden w-full mb-4">
|
|
||||||
|
{/* 移动端聊天按钮 */}
|
||||||
|
<div className="sm:hidden w-full my-4">
|
||||||
<ChatButton className="w-full" id={id}></ChatButton>
|
<ChatButton className="w-full" id={id}></ChatButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
function Background({ imageUrl }: { imageUrl: string }) {
|
||||||
|
if (!imageUrl) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-background-default absolute top-0 right-0 bottom-0 left-0 overflow-hidden">
|
||||||
|
<div className="absolute top-0 bottom-0 left-1/2 w-[752px] -translate-x-1/2">
|
||||||
|
{imageUrl && (
|
||||||
|
<img
|
||||||
|
src={imageUrl}
|
||||||
|
alt="Background"
|
||||||
|
className="pointer-events-none h-full w-full object-cover"
|
||||||
|
width={720}
|
||||||
|
height={1280}
|
||||||
|
style={{ objectPosition: 'center -48px' }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
className="absolute top-0 bottom-0 left-0 w-120"
|
||||||
|
style={{
|
||||||
|
background: 'linear-gradient(to right, rgba(6, 3, 24, 1), transparent)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="absolute top-0 bottom-0 right-0 w-120"
|
||||||
|
style={{
|
||||||
|
background: 'linear-gradient(to left, rgba(6, 3, 24, 1), transparent)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="absolute bottom-0 h-60 w-full"
|
||||||
|
style={{
|
||||||
|
background: 'linear-gradient(to top, rgba(6, 3, 24, 1), transparent)',
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default React.memo(Background);
|
||||||
|
|
@ -7,15 +7,25 @@ import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||||
import { useCharacter } from '@/hooks/services/character';
|
import { useCharacter } from '@/hooks/services/character';
|
||||||
import { useParams } from 'next/navigation';
|
import { useParams } from 'next/navigation';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
// import CrushLevelAvatar from './CrushLevelAvatar'
|
import Link from 'next/link';
|
||||||
|
|
||||||
export const CharacterAvatorAndName = ({ name, avator }: { name: string; avator: string }) => {
|
export const CharacterAvatorAndName = ({
|
||||||
|
name,
|
||||||
|
avator,
|
||||||
|
id,
|
||||||
|
}: {
|
||||||
|
name: string;
|
||||||
|
avator: string;
|
||||||
|
id?: string;
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center gap-6">
|
<div className="flex flex-col items-center gap-6">
|
||||||
<Avatar className="h-20 w-20">
|
<Link href={`/character/${id}`}>
|
||||||
<AvatarImage src={avator} />
|
<Avatar className="h-20 w-20">
|
||||||
<AvatarFallback>{name?.slice(0, 1)}</AvatarFallback>
|
<AvatarImage src={avator} />
|
||||||
</Avatar>
|
<AvatarFallback>{name?.slice(0, 1)}</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
</Link>
|
||||||
<div className="txt-headline-s text-center text-white">{name}</div>
|
<div className="txt-headline-s text-center text-white">{name}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -26,7 +36,8 @@ function ChatMessageUserHeader() {
|
||||||
const [shouldShowExpandButton, setShouldShowExpandButton] = useState(false);
|
const [shouldShowExpandButton, setShouldShowExpandButton] = useState(false);
|
||||||
const textRef = useRef<HTMLDivElement>(null);
|
const textRef = useRef<HTMLDivElement>(null);
|
||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>();
|
||||||
const { data: character = {} } = useCharacter(id.split('-')[2]);
|
const characterId = id.split('-')[2];
|
||||||
|
const { data: character = {} } = useCharacter(characterId);
|
||||||
|
|
||||||
// 检测文本是否超过三行
|
// 检测文本是否超过三行
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -40,8 +51,11 @@ function ChatMessageUserHeader() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center gap-6">
|
<div className="flex flex-col items-center gap-6">
|
||||||
<CharacterAvatorAndName name={character.name || '-'} avator={character.headPortrait || ''} />
|
<CharacterAvatorAndName
|
||||||
|
id={characterId}
|
||||||
|
name={character.name || '-'}
|
||||||
|
avator={character.headPortrait || ''}
|
||||||
|
/>
|
||||||
<div className="bg-surface-element-normal border-outline-normal flex w-full flex-col gap-2 rounded-lg border border-solid p-4 backdrop-blur-2xl">
|
<div className="bg-surface-element-normal border-outline-normal flex w-full flex-col gap-2 rounded-lg border border-solid p-4 backdrop-blur-2xl">
|
||||||
<div
|
<div
|
||||||
ref={textRef}
|
ref={textRef}
|
||||||
|
|
|
||||||
|
|
@ -198,7 +198,11 @@ export default function Profile({ onActiveTab }: ProfileProps) {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full flex-col">
|
<div className="flex h-full flex-col">
|
||||||
<div className="flex flex-1 overflow-y-auto pr-1 show-scrollbar flex-col gap-4">
|
<div className="flex flex-1 overflow-y-auto pr-1 show-scrollbar flex-col gap-4">
|
||||||
<CharacterAvatorAndName avator={character.headPortrait} name={character.name} />
|
<CharacterAvatorAndName
|
||||||
|
id={characterId}
|
||||||
|
avator={character.headPortrait}
|
||||||
|
name={character.name}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Tags */}
|
{/* Tags */}
|
||||||
<div className="flex w-full flex-col items-start justify-start gap-4">
|
<div className="flex w-full flex-col items-start justify-start gap-4">
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@ export default function Input() {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col mb-6 items-end gap-4">
|
<div className="flex flex-col mb-6 items-end gap-4 z-10">
|
||||||
<div></div>
|
<div></div>
|
||||||
<div className="flex w-full items-end gap-4">
|
<div className="flex w-full items-end gap-4">
|
||||||
{/* 打电话按钮 */}
|
{/* 打电话按钮 */}
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ export default function MessageList() {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex-1 min-h-0">
|
<div className="flex-1 min-h-0 relative z-10">
|
||||||
<VirtualList className="h-full" data={itemList} itemContent={itemContent} />
|
<VirtualList className="h-full" data={itemList} itemContent={itemContent} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,16 @@ import SettingDialog from './Drawer';
|
||||||
import { useStreamChatStore } from '@/app/(main)/chat/[id]/stream-chat';
|
import { useStreamChatStore } from '@/app/(main)/chat/[id]/stream-chat';
|
||||||
import { useParams } from 'next/navigation';
|
import { useParams } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useCharacter } from '@/hooks/services/character';
|
||||||
|
import Background from './Background';
|
||||||
|
|
||||||
export default function ChatPage() {
|
export default function ChatPage() {
|
||||||
const { id } = useParams();
|
const { id } = useParams<{ id: string }>();
|
||||||
|
const characterId = id?.split('-')[2] || '';
|
||||||
const [settingOpen, setSettingOpen] = useState(false);
|
const [settingOpen, setSettingOpen] = useState(false);
|
||||||
const switchToChannel = useStreamChatStore((s) => s.switchToChannel);
|
const switchToChannel = useStreamChatStore((s) => s.switchToChannel);
|
||||||
const client = useStreamChatStore((s) => s.client);
|
const client = useStreamChatStore((s) => s.client);
|
||||||
|
const { data: character } = useCharacter(characterId);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (id && client) {
|
if (id && client) {
|
||||||
|
|
@ -21,9 +25,10 @@ export default function ChatPage() {
|
||||||
}, [id, client]);
|
}, [id, client]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full">
|
<div className="flex bg-[rgba(6,3,24,1)] h-full">
|
||||||
<div className="relative px-4 w-full flex-1 flex justify-center">
|
<div className="relative px-4 w-full flex-1 flex justify-center">
|
||||||
<div className="max-w-[752px] w-full h-full flex flex-col">
|
<div className="max-w-[752px] w-full relative h-full flex flex-col">
|
||||||
|
<Background imageUrl={character?.characterStand || character?.coverImage} />
|
||||||
<MessageList />
|
<MessageList />
|
||||||
<Input />
|
<Input />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ const Character = () => {
|
||||||
<InfiniteScrollList<any>
|
<InfiniteScrollList<any>
|
||||||
items={dataSource}
|
items={dataSource}
|
||||||
columns={(width) => {
|
columns={(width) => {
|
||||||
const cardWidth = width > 1200 ? 256 : width > 588 ? 200 : 170;
|
const cardWidth = width > 1200 ? 256 : width > 588 ? 200 : width > 375 ? 170 : 150;
|
||||||
return Math.floor(width / cardWidth);
|
return Math.floor(width / cardWidth);
|
||||||
}}
|
}}
|
||||||
renderItem={(character) => <AIStandardCard character={character} />}
|
renderItem={(character) => <AIStandardCard character={character} />}
|
||||||
|
|
|
||||||
|
|
@ -36,8 +36,8 @@ const NumDisplay = localFont({
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
metadataBase: new URL(process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'),
|
metadataBase: new URL(process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'),
|
||||||
title: 'CrushLevel',
|
title: 'Spicyxx.AI',
|
||||||
description: 'CrushLevel - Next Generation Social Platform',
|
description: 'Spicyxx.AI - Next Generation Social Platform',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function RootLayout({
|
export default async function RootLayout({
|
||||||
|
|
|
||||||
|
|
@ -1,108 +0,0 @@
|
||||||
'use client'
|
|
||||||
|
|
||||||
import Image from 'next/image'
|
|
||||||
import { useState, useEffect, useMemo } from 'react'
|
|
||||||
import { ScrollingBackground } from './ScrollingBackground'
|
|
||||||
|
|
||||||
interface LeftPanelProps {
|
|
||||||
scrollBg: string
|
|
||||||
images: string[]
|
|
||||||
}
|
|
||||||
|
|
||||||
// 基础文字内容
|
|
||||||
const baseTexts = [
|
|
||||||
{ title: 'AI Date', subtitle: "From 'Hi' to 'I Do', sparked by every chat." },
|
|
||||||
{ title: 'Crush', subtitle: "From 'Hi' to 'I Do', sparked by every chat." },
|
|
||||||
{ title: 'Chat', subtitle: "From 'Hi' to 'I Do', sparked by every chat." },
|
|
||||||
]
|
|
||||||
|
|
||||||
// 根据图片数量循环生成文字内容
|
|
||||||
const generateImageTexts = (count: number) => {
|
|
||||||
return Array.from({ length: count }, (_, i) => baseTexts[i % baseTexts.length])
|
|
||||||
}
|
|
||||||
|
|
||||||
export function LeftPanel({ scrollBg, images }: LeftPanelProps) {
|
|
||||||
const [currentIndex, setCurrentIndex] = useState(0)
|
|
||||||
const [isTransitioning, setIsTransitioning] = useState(false)
|
|
||||||
|
|
||||||
// 根据图片数量动态生成文案(使用 useMemo 优化性能)
|
|
||||||
const imageTexts = useMemo(() => generateImageTexts(images.length), [images.length])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (images.length <= 1) return
|
|
||||||
|
|
||||||
const timer = setInterval(() => {
|
|
||||||
setIsTransitioning(true)
|
|
||||||
setTimeout(() => {
|
|
||||||
setCurrentIndex((prevIndex) => (prevIndex === images.length - 1 ? 0 : prevIndex + 1))
|
|
||||||
setIsTransitioning(false)
|
|
||||||
}, 300)
|
|
||||||
}, 3000) // 每3秒切换一次
|
|
||||||
|
|
||||||
return () => clearInterval(timer)
|
|
||||||
}, [images.length])
|
|
||||||
|
|
||||||
const currentText = imageTexts[currentIndex] || imageTexts[0]
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="relative h-full w-full overflow-hidden">
|
|
||||||
{/* 滚动背景 */}
|
|
||||||
<ScrollingBackground imageSrc={scrollBg} />
|
|
||||||
|
|
||||||
{/* 内容层 */}
|
|
||||||
<div className="relative z-10 flex h-full flex-col justify-end">
|
|
||||||
{/* 底部遮罩层 - 铺满背景底部,高度500px */}
|
|
||||||
<div
|
|
||||||
className="absolute right-0 bottom-0 left-0 z-[5] h-[500px]"
|
|
||||||
style={{
|
|
||||||
background: 'linear-gradient(180deg, rgba(33, 26, 43, 0) 0%, #211A2B 100%)',
|
|
||||||
boxShadow: '0px 4px 4px 0px #00000040',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* 文字内容 - 在图片上方 */}
|
|
||||||
<div
|
|
||||||
className={`absolute right-0 bottom-16 left-0 z-10 mb-6 px-4 text-center transition-opacity duration-700 lg:bottom-20 lg:mb-8 lg:px-8 ${
|
|
||||||
isTransitioning ? 'opacity-0' : 'opacity-100'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div className="mx-auto flex items-center justify-center px-4 py-2 lg:px-6 lg:py-3">
|
|
||||||
<h2 className="txt-headline-m lg:txt-headline-l relative flex items-center gap-2 text-white">
|
|
||||||
{currentText.title}
|
|
||||||
<Image
|
|
||||||
src="/images/login/v1/icon-star-right.svg"
|
|
||||||
alt="logo"
|
|
||||||
width={38}
|
|
||||||
height={36}
|
|
||||||
className="absolute -top-[18px] -right-[38px]"
|
|
||||||
/>
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
<p className="txt-body-m lg:txt-body-l mx-auto max-w-[320px] lg:max-w-[380px]">
|
|
||||||
{currentText.subtitle}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 角色图片 - 尽可能放大,紧贴底部 */}
|
|
||||||
<div className="relative h-[80vh] w-full lg:h-[85vh]">
|
|
||||||
{images.map((image, index) => (
|
|
||||||
<div
|
|
||||||
key={`${image}-${index}`}
|
|
||||||
className={`absolute inset-0 transition-opacity duration-700 ${
|
|
||||||
index === currentIndex && !isTransitioning ? 'opacity-100' : 'opacity-0'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
src={image}
|
|
||||||
alt={`Character ${index + 1}`}
|
|
||||||
fill
|
|
||||||
className="object-contain object-bottom"
|
|
||||||
priority={index === 0}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +1,9 @@
|
||||||
'use client';
|
'use client';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { LoginForm } from './components/login-form';
|
import { LoginForm } from './components/login-form';
|
||||||
import { LeftPanel } from './components/LeftPanel';
|
|
||||||
import { IconButton } from '@/components/ui/button';
|
import { IconButton } from '@/components/ui/button';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
const scrollBg = '/images/login/v1/bg.png';
|
|
||||||
const images = [
|
|
||||||
'/images/login/v1/1.png',
|
|
||||||
'/images/login/v1/2.png',
|
|
||||||
'/images/login/v1/3.png',
|
|
||||||
'/images/login/v1/4.png',
|
|
||||||
'/images/login/v1/5.png',
|
|
||||||
'/images/login/v1/6.png',
|
|
||||||
'/images/login/v1/7.png',
|
|
||||||
'/images/login/v1/8.png',
|
|
||||||
'/images/login/v1/9.png',
|
|
||||||
'/images/login/v1/10.png',
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function LoginPage() {
|
export default function LoginPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
|
@ -38,7 +23,7 @@ export default function LoginPage() {
|
||||||
|
|
||||||
<div className="relative flex w-full flex-col items-center justify-center px-6">
|
<div className="relative flex w-full flex-col items-center justify-center px-6">
|
||||||
{/* Logo */}
|
{/* Logo */}
|
||||||
<div className="relative mb-8 h-[48px] w-[120px] sm:mb-12 sm:h-[64px] sm:w-[160px]">
|
<div className="relative mb-8 h-12 sm:mb-12 sm:h-16">
|
||||||
<i className="iconfont-v2 iconv2-Logo" style={{ fontSize: '50px' }} />
|
<i className="iconfont-v2 iconv2-Logo" style={{ fontSize: '50px' }} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,31 @@
|
||||||
import type { Metadata } from 'next'
|
import type { Metadata } from 'next';
|
||||||
import LoginPage from './login-page'
|
import LoginPage from './login-page';
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Login - CrushLevel AI',
|
title: 'Login - Spicyxx.AI',
|
||||||
description:
|
description:
|
||||||
'Sign in to CrushLevel AI to start your love story. Login with Discord, Google, or Apple to connect with AI companions and begin chatting.',
|
'Sign in to Spicyxx.AI to start your love story. Login with Discord, Google, or Apple to connect with AI companions and begin chatting.',
|
||||||
keywords: [
|
keywords: [
|
||||||
'CrushLevel login',
|
'Spicyxx.AI login',
|
||||||
'CrushLevel sign in',
|
'Spicyxx.AI sign in',
|
||||||
'AI companion login',
|
'AI companion login',
|
||||||
'Discord login',
|
'Discord login',
|
||||||
'Google login',
|
'Google login',
|
||||||
'Apple login',
|
'Apple login',
|
||||||
'CrushLevel account',
|
'Spicyxx.AI account',
|
||||||
],
|
],
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: 'Login - CrushLevel AI',
|
title: 'Login - Spicyxx.AI',
|
||||||
description:
|
description:
|
||||||
'Sign in to CrushLevel AI to start your love story. Login with Discord, Google, or Apple to connect with AI companions.',
|
'Sign in to Spicyxx.AI to start your love story. Login with Discord, Google, or Apple to connect with AI companions.',
|
||||||
url: 'https://www.crushlevel.com/login',
|
url: 'https://www.crushlevel.com/login',
|
||||||
siteName: 'CrushLevel AI',
|
siteName: 'Spicyxx.AI',
|
||||||
images: [
|
images: [
|
||||||
{
|
{
|
||||||
url: '/logo.svg',
|
url: '/logo.svg',
|
||||||
width: 1200,
|
width: 1200,
|
||||||
height: 630,
|
height: 630,
|
||||||
alt: 'CrushLevel AI Login',
|
alt: 'Spicyxx.AI Login',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
locale: 'en_US',
|
locale: 'en_US',
|
||||||
|
|
@ -33,9 +33,9 @@ export const metadata: Metadata = {
|
||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
card: 'summary_large_image',
|
card: 'summary_large_image',
|
||||||
title: 'Login - CrushLevel AI',
|
title: 'Login - Spicyxx.AI',
|
||||||
description:
|
description:
|
||||||
'Sign in to CrushLevel AI to start your love story. Login with Discord, Google, or Apple.',
|
'Sign in to Spicyxx.AI to start your love story. Login with Discord, Google, or Apple.',
|
||||||
images: ['/logo.svg'],
|
images: ['/logo.svg'],
|
||||||
},
|
},
|
||||||
robots: {
|
robots: {
|
||||||
|
|
@ -52,10 +52,10 @@ export const metadata: Metadata = {
|
||||||
alternates: {
|
alternates: {
|
||||||
canonical: 'https://www.crushlevel.com/login',
|
canonical: 'https://www.crushlevel.com/login',
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
const Page = () => {
|
const Page = () => {
|
||||||
return <LoginPage />
|
return <LoginPage />;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default Page
|
export default Page;
|
||||||
|
|
|
||||||
|
|
@ -380,7 +380,7 @@
|
||||||
--color-emphasis-onpic-normal: rgba(78, 72, 255, 0.85);
|
--color-emphasis-onpic-normal: rgba(78, 72, 255, 0.85);
|
||||||
|
|
||||||
/* Background */
|
/* Background */
|
||||||
--color-background-default: rgba(6, 3, 24, 1);
|
--color-background-default: #0d0f29;
|
||||||
|
|
||||||
/* Surface */
|
/* Surface */
|
||||||
--color-surface-base-normal: rgba(26, 21, 42, 1);
|
--color-surface-base-normal: rgba(26, 21, 42, 1);
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ function matchRoutePattern(pathname: string, patterns: string[]): boolean {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const blurPages = ['/chat/:id'];
|
||||||
|
|
||||||
function Topbar() {
|
function Topbar() {
|
||||||
const [isBlur, setIsBlur] = useState(false);
|
const [isBlur, setIsBlur] = useState(false);
|
||||||
const { data: user } = useCurrentUser();
|
const { data: user } = useCurrentUser();
|
||||||
|
|
@ -68,7 +70,10 @@ function Topbar() {
|
||||||
if (response.sm || items.some((item) => item.path === pathname)) {
|
if (response.sm || items.some((item) => item.path === pathname)) {
|
||||||
return (
|
return (
|
||||||
<Link href="/">
|
<Link href="/">
|
||||||
<i className="iconfont-v2 iconv2-Logo" style={{ fontSize: '50px' }} />
|
<i
|
||||||
|
className="iconfont-v2 iconv2-Logo"
|
||||||
|
style={{ fontSize: response.sm ? '50px' : '32px' }}
|
||||||
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -86,7 +91,7 @@ function Topbar() {
|
||||||
if (!user)
|
if (!user)
|
||||||
return (
|
return (
|
||||||
<Link href={loginHref} prefetch>
|
<Link href={loginHref} prefetch>
|
||||||
<Button size="small">Login in / Sign up</Button>
|
<Button size="small">{`Login in${response?.sm ? ' / Sign up' : ''}`}</Button>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
|
|
@ -110,13 +115,15 @@ function Topbar() {
|
||||||
|
|
||||||
if (response && !response.sm && matchRoutePattern(pathname, mobileHidenMenus)) return null;
|
if (response && !response.sm && matchRoutePattern(pathname, mobileHidenMenus)) return null;
|
||||||
|
|
||||||
|
const finalIgBlur = isBlur || matchRoutePattern(pathname, blurPages);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
className={cn('flex h-16 w-full items-center justify-between px-4 sm:px-8 transition-all', {
|
className={cn('flex h-16 w-full items-center justify-between px-4 sm:px-8 transition-all', {
|
||||||
'backdrop-blur-[10px]': isBlur,
|
'backdrop-blur-[10px]': finalIgBlur,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{isBlur && <div className="bg-background-default absolute inset-0 opacity-85" />}
|
{finalIgBlur && <div className="bg-background-default absolute inset-0 opacity-85" />}
|
||||||
<div className="relative inset-0 flex w-full items-center justify-between">
|
<div className="relative inset-0 flex w-full items-center justify-between">
|
||||||
{leftDomRender()}
|
{leftDomRender()}
|
||||||
{rightDomRender()}
|
{rightDomRender()}
|
||||||
|
|
|
||||||