diff --git a/src/app/_components/core/app-bottom-nav.module.css b/src/app/_components/core/app-bottom-nav.module.css new file mode 100644 index 00000000..b31fa03f --- /dev/null +++ b/src/app/_components/core/app-bottom-nav.module.css @@ -0,0 +1,63 @@ +.root { + position: fixed; + right: max(calc((100vw - var(--app-max-width, 540px)) / 2), 0px); + bottom: 0; + left: max(calc((100vw - var(--app-max-width, 540px)) / 2), 0px); + z-index: 30; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 10px; + box-sizing: border-box; + width: min(100vw, var(--app-max-width, 540px)); + margin: 0 auto; + padding: + 10px + calc(var(--app-safe-right, 0px) + 14px) + calc(var(--app-safe-bottom, 0px) + 10px) + calc(var(--app-safe-left, 0px) + 14px); + border-top: 1px solid rgba(44, 29, 34, 0.08); + background: rgba(255, 255, 255, 0.86); + box-shadow: 0 -18px 48px rgba(53, 34, 40, 0.09); + backdrop-filter: blur(18px); +} + +.button { + display: flex; + min-height: 54px; + align-items: center; + justify-content: center; + gap: 8px; + border: 1px solid transparent; + border-radius: 18px; + background: transparent; + color: #8d777e; + cursor: pointer; + font: inherit; + font-size: clamp(12px, 2.963vw, 14px); + font-weight: 820; + transition: + background 0.18s ease, + color 0.18s ease, + transform 0.18s ease; +} + +.button:hover { + background: rgba(255, 116, 159, 0.08); + color: #2d2024; +} + +.button:active { + transform: scale(0.98); +} + +.button:focus-visible { + outline: 2px solid #ff5d95; + outline-offset: 3px; +} + +.active { + border-color: rgba(255, 116, 159, 0.2); + background: linear-gradient(135deg, rgba(255, 244, 236, 0.98), rgba(255, 232, 241, 0.96)); + color: #201417; + box-shadow: 0 10px 24px rgba(255, 116, 159, 0.12); +} diff --git a/src/app/_components/core/app-bottom-nav.tsx b/src/app/_components/core/app-bottom-nav.tsx new file mode 100644 index 00000000..04bc3bc0 --- /dev/null +++ b/src/app/_components/core/app-bottom-nav.tsx @@ -0,0 +1,46 @@ +"use client"; + +import { Camera, MessageCircle } from "lucide-react"; + +import styles from "./app-bottom-nav.module.css"; + +export type AppBottomNavItem = "chat" | "privateRoom"; + +export interface AppBottomNavProps { + activeItem?: AppBottomNavItem | null; + onChatClick: () => void; + onPrivateRoomClick: () => void; +} + +export function AppBottomNav({ + activeItem = null, + onChatClick, + onPrivateRoomClick, +}: AppBottomNavProps) { + return ( + + ); +} + +function getButtonClass(active: boolean): string { + return [styles.button, active ? styles.active : ""].filter(Boolean).join(" "); +} diff --git a/src/app/_components/core/index.ts b/src/app/_components/core/index.ts index 2558c34c..bc1cc9bd 100644 --- a/src/app/_components/core/index.ts +++ b/src/app/_components/core/index.ts @@ -2,6 +2,7 @@ * @file Automatically generated by barrelsby. */ +export * from "./app-bottom-nav"; export * from "./bottom-sheet"; export * from "./checkbox"; export * from "./dialog"; diff --git a/src/app/private-room/private-room-screen.module.css b/src/app/private-room/private-room-screen.module.css index e432343b..54f79309 100644 --- a/src/app/private-room/private-room-screen.module.css +++ b/src/app/private-room/private-room-screen.module.css @@ -40,8 +40,7 @@ } .hero, -.postsSection, -.bottomNav { +.postsSection { position: relative; z-index: 1; } @@ -394,51 +393,6 @@ margin-top: 16px; } -.bottomNav { - position: fixed; - right: max(calc((100vw - var(--app-max-width, 540px)) / 2), 0px); - bottom: 0; - left: max(calc((100vw - var(--app-max-width, 540px)) / 2), 0px); - display: grid; - grid-template-columns: 1fr 1fr; - gap: 10px; - box-sizing: border-box; - width: min(100vw, var(--app-max-width, 540px)); - margin: 0 auto; - padding: - 10px - calc(var(--app-safe-right, 0px) + 14px) - calc(var(--app-safe-bottom, 0px) + 10px) - calc(var(--app-safe-left, 0px) + 14px); - border-top: 1px solid rgba(44, 29, 34, 0.08); - background: rgba(255, 255, 255, 0.86); - box-shadow: 0 -18px 48px rgba(53, 34, 40, 0.09); - backdrop-filter: blur(18px); -} - -.navButton { - display: flex; - min-height: 54px; - align-items: center; - justify-content: center; - gap: 8px; - border: 1px solid transparent; - border-radius: 18px; - background: transparent; - color: #8d777e; - cursor: pointer; - font: inherit; - font-size: clamp(12px, 2.963vw, 14px); - font-weight: 820; -} - -.navButtonActive { - border-color: rgba(255, 116, 159, 0.2); - background: linear-gradient(135deg, rgba(255, 244, 236, 0.98), rgba(255, 232, 241, 0.96)); - color: #201417; - box-shadow: 0 10px 24px rgba(255, 116, 159, 0.12); -} - .dialogOverlay { position: fixed; inset: 0; @@ -529,14 +483,12 @@ .primaryCta:active, .lockedPreview:active, -.loadMoreButton:active, -.navButton:active { +.loadMoreButton:active { transform: translateY(1px) scale(0.99); } .primaryCta:focus-visible, .lockedPreview:focus-visible, -.navButton:focus-visible, .loadMoreButton:focus-visible, .dialogPrimary:focus-visible, .dialogSecondary:focus-visible { diff --git a/src/app/private-room/private-room-screen.tsx b/src/app/private-room/private-room-screen.tsx index 726a3eea..31caa010 100644 --- a/src/app/private-room/private-room-screen.tsx +++ b/src/app/private-room/private-room-screen.tsx @@ -4,16 +4,14 @@ import type { CSSProperties } from "react"; import { useEffect, useMemo, useRef } from "react"; import Image from "next/image"; import { - Camera, ImageIcon, LockKeyhole, - MessageCircle, RefreshCw, Sparkles, } from "lucide-react"; import type { PrivateRoomMoment } from "@/data/dto/private-room"; -import { MobileShell } from "@/app/_components/core"; +import { AppBottomNav, MobileShell } from "@/app/_components/core"; import { useGuestLoginBootstrap } from "@/hooks/use-guest-login-bootstrap"; import { ROUTES } from "@/router/routes"; import { useAppNavigator } from "@/router/use-app-navigator"; @@ -235,24 +233,11 @@ export function PrivateRoomScreen() { ) : null} - + navigator.openChat({ replace: false })} + onPrivateRoomClick={() => undefined} + /> {pendingMoment ? ( void; +} + +export function SplashLatestMessage({ + onOpenChat, +}: SplashLatestMessageProps) { + return ( + + ); +} diff --git a/src/app/splash/components/splash-screen.module.css b/src/app/splash/components/splash-screen.module.css index a77864bf..f9a68e2d 100644 --- a/src/app/splash/components/splash-screen.module.css +++ b/src/app/splash/components/splash-screen.module.css @@ -31,7 +31,7 @@ padding: calc(var(--page-padding-y, 20px) + var(--app-safe-top, 0px)) calc(var(--page-padding-x, 26px) + var(--app-safe-right, 0px)) - calc(var(--spacing-md, 12px) + var(--app-safe-bottom, 0px)) + calc(var(--spacing-md, 12px) + var(--app-safe-bottom, 0px) + 86px) calc(var(--page-padding-x, 26px) + var(--app-safe-left, 0px)); } diff --git a/src/app/splash/splash-screen.tsx b/src/app/splash/splash-screen.tsx index 5c50debd..b4a805a5 100644 --- a/src/app/splash/splash-screen.tsx +++ b/src/app/splash/splash-screen.tsx @@ -2,7 +2,8 @@ import { useEffect } from "react"; -import { MobileShell } from "@/app/_components/core"; +import { AppBottomNav, MobileShell } from "@/app/_components/core"; +import { ROUTES } from "@/router/routes"; import { useAppNavigator } from "@/router/use-app-navigator"; import { pwaUtil } from "@/utils"; @@ -10,6 +11,7 @@ import { SplashBackground, SplashButton, SplashContent, + SplashLatestMessage, SplashLogo, } from "./components"; import styles from "./components/splash-screen.module.css"; @@ -26,6 +28,10 @@ export function SplashScreen() { navigator.openChat({ replace: true }); }; + const handleOpenPrivateRoom = () => { + navigator.push(ROUTES.privateRoom, { scroll: false }); + }; + useEffect(() => { pwaUtil.prepareInstallPrompt(); }, []); @@ -43,12 +49,18 @@ export function SplashScreen() {
+

Elio Silvestri, Your exclusive AI boyfriend
24/7 online | Chat | Companion | Heal | Sweet moments

+ );