diff --git a/src/app/_components/core/__tests__/tailwind-components.test.tsx b/src/app/_components/core/__tests__/tailwind-components.test.tsx index 2c686720..7348710b 100644 --- a/src/app/_components/core/__tests__/tailwind-components.test.tsx +++ b/src/app/_components/core/__tests__/tailwind-components.test.tsx @@ -1,6 +1,7 @@ import { renderToStaticMarkup } from "react-dom/server"; import { describe, expect, it } from "vitest"; +import { AppBottomNav } from "../app-bottom-nav"; import { Checkbox } from "../checkbox"; import { LoadingIndicator } from "../loading-indicator"; import { PageLoadingFallback } from "../page-loading-fallback"; @@ -73,4 +74,32 @@ describe("core Tailwind components", () => { expect(html).toContain("Content"); }); + it("renders AppBottomNav themes and active items accessibly", () => { + const chatHtml = renderToStaticMarkup( + undefined} + onPrivateRoomClick={() => undefined} + />, + ); + const privateRoomHtml = renderToStaticMarkup( + undefined} + onPrivateRoomClick={() => undefined} + />, + ); + + expect(chatHtml).toContain('aria-label="Primary navigation"'); + expect(chatHtml).toMatch(/class="[^"]*dark[^"]*"/); + expect(chatHtml).toMatch( + /]*aria-current="page"[^>]*>.*?Chat<\/span>/, + ); + expect(privateRoomHtml).toMatch(/class="[^"]*warm[^"]*"/); + expect(privateRoomHtml).toMatch( + /]*aria-current="page"[^>]*>.*?Elio Private room<\/span>/, + ); + }); }); diff --git a/src/app/_components/core/app-bottom-nav.module.css b/src/app/_components/core/app-bottom-nav.module.css index 19774d77..6b3cc2e0 100644 --- a/src/app/_components/core/app-bottom-nav.module.css +++ b/src/app/_components/core/app-bottom-nav.module.css @@ -1,77 +1,65 @@ .root { position: fixed; - bottom: calc(var(--app-safe-bottom, 0px) + 12px); + bottom: 0; left: 50%; z-index: 30; display: grid; grid-template-columns: 1fr 1fr; - gap: 8px; box-sizing: border-box; - width: min( - calc(100vw - var(--app-safe-left, 0px) - var(--app-safe-right, 0px) - 28px), - calc(var(--app-max-width, 540px) - 28px) + width: min(100vw, var(--app-max-width, 540px)); + min-height: calc( + var(--app-bottom-nav-height, 64px) + var(--app-safe-bottom, 0px) ); - margin: 0 auto; - padding: 8px; - border: 1px solid var(--nav-border); - border-radius: 28px; + padding: + 6px + calc(8px + var(--app-safe-right, 0px)) + calc(6px + var(--app-safe-bottom, 0px)) + calc(8px + var(--app-safe-left, 0px)); + border: 0; + border-top: 1px solid var(--nav-border); background: var(--nav-background); box-shadow: var(--nav-shadow); transform: translateX(-50%); - backdrop-filter: blur(20px) saturate(1.2); } .warm { - --nav-background: - linear-gradient(135deg, rgba(255, 255, 255, 0.9), rgba(255, 240, 230, 0.8)), - rgba(255, 255, 255, 0.78); - --nav-border: rgba(255, 116, 159, 0.18); - --nav-shadow: - 0 20px 48px rgba(131, 72, 85, 0.16), - inset 0 1px 0 rgba(255, 255, 255, 0.92); - --nav-button-color: #80686f; - --nav-button-hover-background: rgba(255, 116, 159, 0.1); - --nav-button-hover-color: #2d2024; - --nav-active-background: linear-gradient(135deg, #fff4ec, #ffe5f0); - --nav-active-border: rgba(255, 116, 159, 0.26); - --nav-active-color: #201417; - --nav-active-shadow: 0 12px 26px rgba(255, 116, 159, 0.16); + --nav-background: #fffaf7; + --nav-border: rgba(70, 46, 55, 0.12); + --nav-shadow: 0 -6px 18px rgba(70, 46, 55, 0.08); + --nav-button-color: #806f75; + --nav-button-hover-background: rgba(246, 87, 160, 0.07); + --nav-button-hover-color: #4a353d; + --nav-active-color: #f657a0; --nav-focus-color: #ff5d95; } .dark { - --nav-background: - linear-gradient(135deg, rgba(36, 22, 39, 0.78), rgba(255, 255, 255, 0.1)), - rgba(23, 16, 28, 0.72); - --nav-border: rgba(255, 255, 255, 0.18); - --nav-shadow: - 0 22px 52px rgba(0, 0, 0, 0.3), - inset 0 1px 0 rgba(255, 255, 255, 0.2); - --nav-button-color: rgba(255, 255, 255, 0.78); - --nav-button-hover-background: rgba(255, 255, 255, 0.12); + --nav-background: #17121c; + --nav-border: rgba(255, 255, 255, 0.14); + --nav-shadow: 0 -6px 20px rgba(0, 0, 0, 0.24); + --nav-button-color: rgba(255, 255, 255, 0.62); + --nav-button-hover-background: rgba(255, 255, 255, 0.07); --nav-button-hover-color: #ffffff; - --nav-active-background: - linear-gradient(135deg, rgba(255, 255, 255, 0.22), rgba(255, 116, 159, 0.2)); - --nav-active-border: rgba(255, 255, 255, 0.3); - --nav-active-color: #ffffff; - --nav-active-shadow: 0 12px 28px rgba(0, 0, 0, 0.22); + --nav-active-color: #ff8fc4; --nav-focus-color: #ffffff; } .button { display: flex; min-height: 52px; + flex-direction: column; align-items: center; justify-content: center; - gap: 8px; - border: 1px solid transparent; - border-radius: 22px; + gap: 3px; + border: 0; + border-radius: 10px; background: transparent; color: var(--nav-button-color); cursor: pointer; font: inherit; - font-size: clamp(12px, 2.963vw, 14px); - font-weight: 820; + font-size: clamp(11px, 2.407vw, 13px); + font-weight: 760; + line-height: 1.1; transition: background 0.18s ease, color 0.18s ease, @@ -93,8 +81,5 @@ } .active { - border-color: var(--nav-active-border); - background: var(--nav-active-background); color: var(--nav-active-color); - box-shadow: var(--nav-active-shadow); } diff --git a/src/app/private-room/private-room-screen.module.css b/src/app/private-room/private-room-screen.module.css index 8e56625a..19568884 100644 --- a/src/app/private-room/private-room-screen.module.css +++ b/src/app/private-room/private-room-screen.module.css @@ -4,13 +4,16 @@ padding: 0 0 - calc(var(--app-safe-bottom, 0px) + 96px) + calc( + var(--app-safe-bottom, 0px) + var(--app-bottom-nav-height, 64px) + + var(--page-section-gap, 18px) + ) 0; overflow: hidden auto; background: - radial-gradient(circle at 12% 10%, rgba(255, 193, 121, 0.32), transparent 30%), - radial-gradient(circle at 90% 4%, rgba(255, 91, 148, 0.22), transparent 28%), - linear-gradient(180deg, #fff7ed 0%, #fff8f6 42%, #ffffff 100%); + radial-gradient(circle at 12% 10%, rgba(255, 255, 255, 0.82), transparent 32%), + radial-gradient(circle at 90% 4%, rgba(210, 214, 211, 0.3), transparent 30%), + linear-gradient(180deg, #f3f4f2 0%, #f7f7f5 46%, #fbfbfa 100%); color: #21171b; } @@ -28,7 +31,7 @@ right: -82px; width: 170px; height: 170px; - background: rgba(255, 93, 149, 0.16); + background: rgba(190, 195, 191, 0.18); } .backgroundGlowTwo { @@ -36,7 +39,7 @@ left: -92px; width: 190px; height: 190px; - background: rgba(255, 188, 92, 0.18); + background: rgba(222, 224, 220, 0.3); } .hero, @@ -518,7 +521,9 @@ .toast { position: fixed; right: max(calc((100vw - var(--app-max-width, 540px)) / 2 + 18px), 18px); - bottom: calc(var(--app-safe-bottom, 0px) + 92px); + bottom: calc( + var(--app-safe-bottom, 0px) + var(--app-bottom-nav-height, 64px) + 12px + ); left: max(calc((100vw - var(--app-max-width, 540px)) / 2 + 18px), 18px); z-index: 45; padding: 14px 16px; diff --git a/src/app/private-room/private-room-screen.tsx b/src/app/private-room/private-room-screen.tsx index 4b370274..78e8a0b9 100644 --- a/src/app/private-room/private-room-screen.tsx +++ b/src/app/private-room/private-room-screen.tsx @@ -80,7 +80,7 @@ export function PrivateRoomScreen() { }; return ( - +
@@ -186,7 +186,7 @@ export function PrivateRoomScreen() { navigator.openChat({ replace: false })} + onChatClick={() => navigator.push(ROUTES.splash, { scroll: false })} onPrivateRoomClick={() => undefined} /> diff --git a/src/app/splash/components/__tests__/tailwind-components.test.tsx b/src/app/splash/components/__tests__/tailwind-components.test.tsx index 498421d0..0dbffced 100644 --- a/src/app/splash/components/__tests__/tailwind-components.test.tsx +++ b/src/app/splash/components/__tests__/tailwind-components.test.tsx @@ -70,6 +70,9 @@ describe("splash Tailwind components", () => { expect(readyHtml).toContain("z-2"); expect(readyHtml).toContain("max-w-120"); expect(readyHtml).toContain("bg-[linear-gradient(to_right"); + expect(readyHtml).toContain("active:enabled:scale-96"); + expect(readyHtml).toContain("active:enabled:brightness-90"); + expect(readyHtml).toContain("touch-manipulation"); expect(readyHtml).toContain("Start Chatting"); expect(loadingHtml).toContain("disabled"); expect(loadingHtml).toContain("animate-spin"); diff --git a/src/app/splash/components/splash-button.tsx b/src/app/splash/components/splash-button.tsx index a588f445..33fc207c 100644 --- a/src/app/splash/components/splash-button.tsx +++ b/src/app/splash/components/splash-button.tsx @@ -19,7 +19,7 @@ export function SplashButton({ onStartChat }: SplashButtonProps) { type="button" onClick={onStartChat} disabled={isLoading} - className="inline-flex min-h-(--responsive-control-height,48px) w-full max-w-120 flex-auto cursor-pointer items-center justify-center rounded-(--radius-full,999px) border-0 bg-[linear-gradient(to_right,var(--color-button-gradient-start),var(--color-button-gradient-end))] px-[clamp(var(--spacing-lg,16px),5.556vw,30px)] font-bold italic text-white shadow-[0_0_10px_rgba(248,89,168,0.3)] disabled:cursor-not-allowed disabled:opacity-70" + className="inline-flex min-h-(--responsive-control-height,48px) w-full max-w-120 touch-manipulation flex-auto cursor-pointer items-center justify-center rounded-(--radius-full,999px) border-0 bg-[linear-gradient(to_right,var(--color-button-gradient-start),var(--color-button-gradient-end))] px-[clamp(var(--spacing-lg,16px),5.556vw,30px)] font-bold italic text-white shadow-[0_0_10px_rgba(248,89,168,0.3)] transition-[filter,box-shadow,transform] duration-150 ease-out [-webkit-tap-highlight-color:transparent] focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-white enabled:hover:-translate-y-px enabled:hover:brightness-105 enabled:hover:shadow-[0_8px_22px_rgba(248,89,168,0.42)] active:enabled:translate-y-0.5 active:enabled:scale-96 active:enabled:brightness-90 active:enabled:shadow-[0_2px_6px_rgba(248,89,168,0.24)] motion-reduce:transition-none motion-reduce:active:enabled:transform-none disabled:cursor-not-allowed disabled:opacity-70" > {isLoading ? ( diff --git a/src/app/splash/components/splash-latest-message.module.css b/src/app/splash/components/splash-latest-message.module.css index ead428af..16ef8947 100644 --- a/src/app/splash/components/splash-latest-message.module.css +++ b/src/app/splash/components/splash-latest-message.module.css @@ -14,16 +14,19 @@ color: #ffffff; cursor: pointer; font: inherit; + -webkit-tap-highlight-color: transparent; text-align: left; + touch-action: manipulation; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.18), 0 18px 42px rgba(46, 22, 38, 0.18); backdrop-filter: blur(18px); - animation: messageFloatIn 0.5s ease 0.08s both; + animation: messageFloatIn 0.5s ease 0.08s backwards; transition: border-color 0.18s ease, + filter 0.14s ease, box-shadow 0.18s ease, - transform 0.18s ease; + transform 0.14s ease; } .card:hover { @@ -35,7 +38,12 @@ } .card:active { - transform: translateY(1px) scale(0.995); + border-color: rgba(255, 255, 255, 0.48); + filter: brightness(0.9); + box-shadow: + inset 0 2px 8px rgba(22, 10, 20, 0.28), + 0 6px 16px rgba(28, 12, 24, 0.18); + transform: translateY(2px) scale(0.97); } .card:focus-visible { @@ -53,6 +61,11 @@ background: linear-gradient(145deg, #ffb36d, #ff5d95); box-shadow: 0 10px 24px rgba(255, 95, 128, 0.24); overflow: hidden; + transition: transform 0.14s ease; +} + +.card:active .avatarWrap { + transform: scale(0.94); } .avatar { @@ -122,3 +135,19 @@ background-position: 0 0; } } + +@media (prefers-reduced-motion: reduce) { + .card, + .avatarWrap { + transition-duration: 0.01ms; + } + + .card { + animation: none; + } + + .card:active, + .card:active .avatarWrap { + transform: none; + } +} diff --git a/src/app/splash/components/splash-screen.module.css b/src/app/splash/components/splash-screen.module.css index f9a68e2d..b1db338e 100644 --- a/src/app/splash/components/splash-screen.module.css +++ b/src/app/splash/components/splash-screen.module.css @@ -31,7 +31,10 @@ 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) + 86px) + calc( + var(--spacing-md, 12px) + var(--app-safe-bottom, 0px) + + var(--app-bottom-nav-height, 64px) + ) 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 ec9ee425..ebd9b50a 100644 --- a/src/app/splash/splash-screen.tsx +++ b/src/app/splash/splash-screen.tsx @@ -40,6 +40,10 @@ export function SplashScreen() { navigator.push(ROUTES.privateRoom, { scroll: false }); }; + const handleOpenSplash = () => { + navigator.push(ROUTES.splash, { scroll: false }); + }; + useEffect(() => { pwaUtil.prepareInstallPrompt(); }, []); @@ -69,9 +73,9 @@ export function SplashScreen() {