feat(odin): chat animations + immersive fullscreen mobile view

- bubbles spring in anchored at their avatar corner; thinking state is a
  typing bubble (three bouncing dots) that springs in/out where the reply
  lands; reduced-motion falls back to opacity fades
- mobile /odin is immersive: AppShell hides its header and main padding
  below md, the chat owns 100svh full-bleed with safe-area insets and a
  back arrow (history back, home on deep links)
- desktop card border kept divider-colored via longhand border props (a
  responsive border shorthand reset the color to black)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
BOHA
2026-06-11 08:31:28 +02:00
parent 06519d521f
commit 4c8ed39d85
3 changed files with 156 additions and 32 deletions

View File

@@ -23,6 +23,11 @@ export default function AppShell() {
undefined,
);
// Chat-style pages own the full mobile viewport: under md the shell header
// disappears and main loses its padding — the page brings its own back
// button. Desktop keeps the normal shell.
const immersiveOnMobile = location.pathname === "/odin";
const handleLogout = useCallback(() => {
setLoggingOut(true);
setMobileOpen(false);
@@ -130,7 +135,9 @@ export default function AppShell() {
<Box
component="header"
sx={{
display: "flex",
display: immersiveOnMobile
? { xs: "none", md: "flex" }
: "flex",
alignItems: "center",
gap: 1,
px: 2,
@@ -160,7 +167,14 @@ export default function AppShell() {
<Box sx={{ flex: 1 }} />
<ThemeToggle />
</Box>
<Box component="main" sx={{ flex: 1, px: { xs: 2, md: 3 }, pb: 4 }}>
<Box
component="main"
sx={{
flex: 1,
px: immersiveOnMobile ? { xs: 0, md: 3 } : { xs: 2, md: 3 },
pb: immersiveOnMobile ? { xs: 0, md: 4 } : 4,
}}
>
<Outlet />
</Box>
</Box>