|
|
@@ -1,14 +1,17 @@
|
|
|
-import { useState, useEffect } from 'react';
|
|
|
+import { useState, useEffect, useCallback } from 'react';
|
|
|
import { useNavigate } from 'react-router-dom';
|
|
|
import { getStorage, setStorage, STORAGE_KEYS, markRouteVisited, getUserId } from '../utils/storage';
|
|
|
import { getUserInfo, updateUsername } from '../utils/api';
|
|
|
import { px2rem } from '../utils/rem';
|
|
|
-import LumiImage from '../assets/lumi.png';
|
|
|
+import AvatarZone from '../assets/Avatar-anima-zone.png';
|
|
|
+import NamingBg from '../assets/tolan-bg.svg';
|
|
|
+import ConfirmBtnImage from '../assets/confirm-btn.png';
|
|
|
|
|
|
export const GreetingPage = () => {
|
|
|
const navigate = useNavigate();
|
|
|
const [userName, setUserName] = useState('');
|
|
|
const [isFocused, setIsFocused] = useState(false);
|
|
|
+ const [isCompactLayout, setIsCompactLayout] = useState(false);
|
|
|
|
|
|
// 组件加载时从 localStorage 读取姓名,并记录当前页面
|
|
|
useEffect(() => {
|
|
|
@@ -84,21 +87,59 @@ export const GreetingPage = () => {
|
|
|
};
|
|
|
|
|
|
const handleReplayIntro = () => {
|
|
|
- navigate('/video');
|
|
|
+ navigate('/welcome');
|
|
|
};
|
|
|
|
|
|
const handleBack = () => {
|
|
|
- navigate('/video');
|
|
|
+ navigate('/welcome');
|
|
|
};
|
|
|
|
|
|
+ const updateLayoutMode = useCallback(() => {
|
|
|
+ if (typeof window === 'undefined') {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const viewportHeight = window.visualViewport?.height ?? window.innerHeight;
|
|
|
+ setIsCompactLayout(viewportHeight < 720);
|
|
|
+ }, []);
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ updateLayoutMode();
|
|
|
+ if (typeof window === 'undefined') {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ window.addEventListener('resize', updateLayoutMode);
|
|
|
+ const visualViewport = window.visualViewport;
|
|
|
+ visualViewport?.addEventListener('resize', updateLayoutMode);
|
|
|
+
|
|
|
+ return () => {
|
|
|
+ window.removeEventListener('resize', updateLayoutMode);
|
|
|
+ visualViewport?.removeEventListener('resize', updateLayoutMode);
|
|
|
+ };
|
|
|
+ }, [updateLayoutMode]);
|
|
|
+
|
|
|
+ const contentPaddingTop = isCompactLayout ? px2rem(110) : px2rem(180);
|
|
|
+ const sectionGap = isCompactLayout ? px2rem(28) : px2rem(40);
|
|
|
+ const avatarMaxHeight = isCompactLayout ? '38vh' : '50vh';
|
|
|
+ const avatarMinHeight = isCompactLayout ? px2rem(160) : px2rem(220);
|
|
|
+ const avatarPaddingBottom = isCompactLayout ? px2rem(24) : px2rem(40);
|
|
|
+
|
|
|
return (
|
|
|
<div className="mobile-container fixed top-0 left-0 animate-fade-in">
|
|
|
- <div className="content-card safe-area-inset">
|
|
|
+ <div
|
|
|
+ className="content-card safe-area-inset relative flex flex-col min-h-screen overflow-y-auto"
|
|
|
+ style={{
|
|
|
+ backgroundImage: `url(${NamingBg})`,
|
|
|
+ backgroundSize: 'cover',
|
|
|
+ backgroundPosition: 'center',
|
|
|
+ backgroundColor: '#25232E',
|
|
|
+ }}
|
|
|
+ >
|
|
|
{/* 返回按钮 - 响应式定位,与 Replay intro 垂直居中对齐 */}
|
|
|
<button
|
|
|
className="absolute z-10 flex items-center justify-center
|
|
|
rounded-full transition-colors touch-manipulation
|
|
|
- hover:bg-[rgba(25,24,15,0.08)] active:bg-[rgba(25,24,15,0.12)]"
|
|
|
+ hover:bg-[rgba(255,255,255,0.16)] active:bg-[rgba(255,255,255,0.24)]"
|
|
|
style={{
|
|
|
top: px2rem(56),
|
|
|
left: px2rem(16),
|
|
|
@@ -116,14 +157,14 @@ export const GreetingPage = () => {
|
|
|
viewBox="0 0 24 24"
|
|
|
fill="none"
|
|
|
>
|
|
|
- <path d="M14 17L9.7 12L14 7" stroke="#19180F" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
|
|
|
+ <path d="M14 17L9.7 12L14 7" stroke="#FFFFFF" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
|
|
|
</svg>
|
|
|
</button>
|
|
|
|
|
|
{/* Replay intro 文本 - 响应式定位,与返回按钮垂直居中对齐 */}
|
|
|
<button
|
|
|
className="absolute z-10 flex items-center text-fluid-xs font-normal tracking-[0.4px]
|
|
|
- text-black transition-opacity hover:opacity-70 active:opacity-50"
|
|
|
+ text-white transition-opacity hover:opacity-70 active:opacity-50"
|
|
|
style={{
|
|
|
top: px2rem(56),
|
|
|
left: px2rem(58),
|
|
|
@@ -135,63 +176,74 @@ export const GreetingPage = () => {
|
|
|
Replay intro
|
|
|
</button>
|
|
|
|
|
|
- {/* 标题区域 - 响应式定位和字体 */}
|
|
|
- <div className="absolute"
|
|
|
- style={{
|
|
|
- top: px2rem(140),
|
|
|
- left: px2rem(32),
|
|
|
- // right: px2rem(32),
|
|
|
- }}>
|
|
|
- <h1 className="font-medium text-black mb-1"
|
|
|
+ <div
|
|
|
+ className="flex flex-col flex-1 overflow-hidden"
|
|
|
+ style={{
|
|
|
+ paddingLeft: px2rem(32),
|
|
|
+ paddingRight: px2rem(32),
|
|
|
+ paddingTop: contentPaddingTop,
|
|
|
+ paddingBottom: px2rem(32),
|
|
|
+ gap: sectionGap,
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ {/* 标题区域 */}
|
|
|
+ <div>
|
|
|
+ <h1
|
|
|
+ className="font-medium text-white mb-1"
|
|
|
style={{
|
|
|
- fontSize: px2rem(31),
|
|
|
- lineHeight: px2rem(50),
|
|
|
- }}>
|
|
|
- What is your name?
|
|
|
- </h1>
|
|
|
- <p className="text-fluid-base font-medium text-black"
|
|
|
- style={{
|
|
|
- lineHeight: px2rem(24),
|
|
|
- letterSpacing: '0.15px',
|
|
|
- }}>
|
|
|
- 我应该怎么称呼你呢?
|
|
|
- </p>
|
|
|
- </div>
|
|
|
+ fontSize: isCompactLayout ? px2rem(26) : px2rem(31),
|
|
|
+ lineHeight: isCompactLayout ? px2rem(40) : px2rem(50),
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ What is your name?
|
|
|
+ </h1>
|
|
|
+ <p
|
|
|
+ className="text-fluid-base font-medium text-white"
|
|
|
+ style={{
|
|
|
+ lineHeight: px2rem(24),
|
|
|
+ letterSpacing: '0.15px',
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ 我应该怎么称呼你呢?
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
|
|
|
- {/* 输入区域 - 响应式定位和尺寸 */}
|
|
|
- <div className="absolute"
|
|
|
- style={{
|
|
|
- top: px2rem(230),
|
|
|
- left: px2rem(32),
|
|
|
- right: px2rem(32),
|
|
|
- }}>
|
|
|
- <div className="relative w-full">
|
|
|
- <div className="relative w-full bg-white rounded-t-[4px] overflow-visible"
|
|
|
- style={{
|
|
|
- height: px2rem(56),
|
|
|
- }}>
|
|
|
+ {/* 输入区域 */}
|
|
|
+ <div className="w-full">
|
|
|
+ <div
|
|
|
+ className="relative w-full bg-black rounded-t-[4px] overflow-visible"
|
|
|
+ style={{
|
|
|
+ height: px2rem(56),
|
|
|
+ borderTopLeftRadius: '12px',
|
|
|
+ borderTopRightRadius: '12px',
|
|
|
+ borderBottomRightRadius: '2px',
|
|
|
+ borderBottomLeftRadius: '2px',
|
|
|
+ boxShadow:
|
|
|
+ '0px 1px 0px 0px rgba(255, 255, 255, 0.19), inset -2px 2px 2px 0px rgba(0, 0, 0, 0.11)',
|
|
|
+ }}
|
|
|
+ >
|
|
|
<input
|
|
|
type="text"
|
|
|
className="w-full h-full text-fluid-sm font-normal leading-normal tracking-[0.5px]
|
|
|
- text-black bg-transparent border-none outline-none
|
|
|
+ text-[rgba(245,243,240,0.4)] bg-transparent border-none outline-none
|
|
|
placeholder:text-[rgba(25,24,15,0.4)]"
|
|
|
- style={{
|
|
|
- paddingLeft: px2rem(24),
|
|
|
- paddingRight: px2rem(48)
|
|
|
+ style={{
|
|
|
+ paddingLeft: px2rem(24),
|
|
|
+ paddingRight: px2rem(48),
|
|
|
}}
|
|
|
placeholder="Coco"
|
|
|
value={userName}
|
|
|
onChange={(e) => setUserName(e.target.value)}
|
|
|
onFocus={() => setIsFocused(true)}
|
|
|
onBlur={() => setIsFocused(false)}
|
|
|
- onKeyPress={(e) => {
|
|
|
+ onKeyDown={(e) => {
|
|
|
if (e.key === 'Enter') {
|
|
|
handleSubmit();
|
|
|
}
|
|
|
}}
|
|
|
/>
|
|
|
{userName && (
|
|
|
- <button
|
|
|
+ <button
|
|
|
className="absolute top-1/2 -translate-y-1/2
|
|
|
flex items-center justify-center
|
|
|
transition-opacity hover:opacity-70 touch-manipulation"
|
|
|
@@ -203,48 +255,76 @@ export const GreetingPage = () => {
|
|
|
onClick={() => setUserName('')}
|
|
|
aria-label="清除"
|
|
|
>
|
|
|
- <svg
|
|
|
- width="20"
|
|
|
- height="20"
|
|
|
- viewBox="0 0 24 24"
|
|
|
+ <svg
|
|
|
+ width="20"
|
|
|
+ height="20"
|
|
|
+ viewBox="0 0 24 24"
|
|
|
fill="none"
|
|
|
>
|
|
|
- <circle cx="12" cy="12" r="11" stroke="#000000" strokeWidth="1.5" fill="none"/>
|
|
|
- <path d="M8 8L16 16M16 8L8 16" stroke="#000000" strokeWidth="1.5" strokeLinecap="round"/>
|
|
|
+ <circle cx="12" cy="12" r="11" stroke="rgba(191, 157, 248, 1)" strokeWidth="2.5" fill="none" />
|
|
|
+ <path
|
|
|
+ d="M8 8L16 16M16 8L8 16"
|
|
|
+ stroke="rgba(191, 157, 248, 1)"
|
|
|
+ strokeWidth="2.5"
|
|
|
+ strokeLinecap="round"
|
|
|
+ />
|
|
|
</svg>
|
|
|
</button>
|
|
|
)}
|
|
|
</div>
|
|
|
- {/* 输入框底部边框 - 聚焦时显示渐变 - 响应式高度 */}
|
|
|
- <div
|
|
|
- className="absolute top-full left-0 w-full rounded-b-[4px] transition-opacity duration-300"
|
|
|
+ {/* 输入框底部边框 */}
|
|
|
+ <div
|
|
|
+ className="w-full rounded-b-[4px] transition-opacity duration-300"
|
|
|
style={{
|
|
|
- height: px2rem(2),
|
|
|
- background: isFocused
|
|
|
+ height: '2px',
|
|
|
+ background: isFocused
|
|
|
? 'linear-gradient(270deg, #F67B7C 0%, #FFBEF4 29.64%, #A699FE 49.41%, #84CCF8 74.11%, #FFDD5E 98.81%)'
|
|
|
- : '#000000',
|
|
|
- opacity: isFocused ? 1 : 1
|
|
|
+ : 'var(--Schemes-Primary, rgba(103, 80, 164, 1))',
|
|
|
+ opacity: isFocused ? 1 : 1,
|
|
|
}}
|
|
|
></div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
|
|
|
- {/* 角色头像区域 - 响应式定位 */}
|
|
|
- <div className="absolute flex items-center justify-center pointer-events-none"
|
|
|
- style={{
|
|
|
- top: px2rem(300),
|
|
|
- bottom: px2rem(80),
|
|
|
- left: px2rem(16),
|
|
|
- right: px2rem(16),
|
|
|
- }}>
|
|
|
- <img
|
|
|
- src={LumiImage}
|
|
|
- alt="Character Avatar"
|
|
|
- className="w-full h-full object-contain object-[center_bottom]"
|
|
|
+ {/* 角色头像区域 */}
|
|
|
+ <div
|
|
|
+ className="flex-1 flex items-center justify-center pointer-events-none"
|
|
|
style={{
|
|
|
- maxHeight: '50vh',
|
|
|
+ width: '100%',
|
|
|
+ maxHeight: avatarMaxHeight,
|
|
|
+ minHeight: avatarMinHeight,
|
|
|
+ paddingBottom: avatarPaddingBottom,
|
|
|
}}
|
|
|
- />
|
|
|
+ >
|
|
|
+ <img
|
|
|
+ src={AvatarZone}
|
|
|
+ alt="Character Avatar"
|
|
|
+ className="w-full h-full object-contain object-bottom"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {/* 确认按钮 */}
|
|
|
+ <div className="w-full flex justify-center items-center">
|
|
|
+ <button
|
|
|
+ type="button"
|
|
|
+ onClick={handleSubmit}
|
|
|
+ disabled={!userName.trim()}
|
|
|
+ className="relative text-white font-semibold tracking-[0.5px] transition-all disabled:opacity-40 disabled:cursor-not-allowed"
|
|
|
+ style={{
|
|
|
+ width: px2rem(163),
|
|
|
+ height: px2rem(56),
|
|
|
+ borderRadius: px2rem(100),
|
|
|
+ backgroundImage: `url(${ConfirmBtnImage})`,
|
|
|
+ backgroundSize: 'cover',
|
|
|
+ backgroundRepeat: 'no-repeat',
|
|
|
+ backgroundPosition: 'center',
|
|
|
+ boxShadow: '0px 22px 54px rgba(0, 0, 0, 0.35)',
|
|
|
+ display: 'flex',
|
|
|
+ alignItems: 'center',
|
|
|
+ justifyContent: 'center',
|
|
|
+ }}
|
|
|
+ aria-label="Confirm"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|