diff --git a/apps/web/src/pages/terminal.tsx b/apps/web/src/pages/terminal.tsx index 1d4f03a..09413b3 100644 --- a/apps/web/src/pages/terminal.tsx +++ b/apps/web/src/pages/terminal.tsx @@ -5,6 +5,7 @@ import { TerminalSessionTabs, type TerminalSessionInfo, } from "../components/terminal-session-tabs"; +import { Icon } from "../components/icon"; import { useMobileViewport } from "../hooks/use-mobile-viewport"; import { useAutoHide } from "../hooks/use-auto-hide"; import { useTerminalSessions } from "../hooks/use-terminal-sessions"; @@ -241,45 +242,99 @@ export const TerminalPage: React.FC = () => { const sessionInfos = SESSIONS_TO_INFO(sessions); if (isMobile) { + const activeSession = sessions.find((s) => s.id === activeSessionId); + const status = + terminalStatuses[activeSessionId ?? "default"] ?? "connecting"; + return (
+ {/* Overlay status bar — floats over terminal, never resizes it */}
headerAutoHide.show()} + className={`mobile-terminal-overlay ${headerAutoHide.isVisible ? "visible" : "hidden"}`} + onClick={(e) => e.stopPropagation()} > - -

Terminal

- +
+
+ +
+
+ + {activeSession?.name || "Terminal"} + + +
+
+ + + +
+
+
+ +
+ + {/* Pull handle — visible when overlay is hidden */} + {!headerAutoHide.isVisible && ( + + )} + + {/* Terminal content — always fills full viewport */}
headerAutoHide.show()} + className="terminal-page-content mobile-full" + onClick={() => headerAutoHide.hide()} > - -
-
{error &&
{error}
} {sessions .filter((session) => session.id === activeSessionId) @@ -291,6 +346,7 @@ export const TerminalPage: React.FC = () => { sessionId={session.id} onClose={() => handleClose(session.id)} isMobile={true} + showControls={false} onTerminalReady={handleTerminalReady} />
diff --git a/apps/web/src/styles.css b/apps/web/src/styles.css index ff600d1..5b536b1 100644 --- a/apps/web/src/styles.css +++ b/apps/web/src/styles.css @@ -2950,42 +2950,175 @@ a.nav-item, background: #cd3131; } -/* Mobile auto-hide header and tabs */ -.terminal-page.mobile .terminal-page-header, -.mobile-tabs-container { +/* ============================================ + Mobile Terminal Overlay + ============================================ */ + +/* Mobile terminal page — no padding, terminal fills viewport */ +.terminal-page.mobile { + padding: 0; + gap: 0; + height: 100vh; + height: 100dvh; + position: relative; + overflow: hidden; +} + +/* Overlay status bar — floats over terminal, never resizes it */ +.mobile-terminal-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + z-index: 100; + background: #2d2d2d; + border-bottom: 1px solid #3e3e3e; transition: transform 0.3s ease, opacity 0.3s ease; } -.terminal-page.mobile .terminal-page-header.hidden, -.mobile-tabs-container.hidden { +.mobile-terminal-overlay.hidden { transform: translateY(-100%); opacity: 0; pointer-events: none; } -.terminal-page.mobile .terminal-page-header.visible, -.mobile-tabs-container.visible { +.mobile-terminal-overlay.visible { transform: translateY(0); opacity: 1; } +/* Toolbar row */ +.mobile-terminal-toolbar { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--space-2) var(--space-3); + gap: var(--space-2); +} + +.mobile-terminal-toolbar-left, +.mobile-terminal-toolbar-right { + display: flex; + align-items: center; + gap: var(--space-1); + flex: 0 0 auto; +} + +.mobile-terminal-toolbar-center { + display: flex; + align-items: center; + gap: var(--space-2); + flex: 1; + justify-content: center; + min-width: 0; +} + +.mobile-terminal-title { + font-size: 0.875rem; + font-weight: 500; + color: #d4d4d4; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.mobile-terminal-status { + width: 8px; + height: 8px; + border-radius: 50%; + background: #666; + flex-shrink: 0; +} + +.mobile-terminal-status.connecting { + background: #f5f543; + animation: pulse 1.5s infinite; +} + +.mobile-terminal-status.connected { + background: #0dbc79; +} + +.mobile-terminal-status.disconnected, +.mobile-terminal-status.error { + background: #cd3131; +} + +.mobile-terminal-toolbtn { + display: flex; + align-items: center; + justify-content: center; + width: 36px; + height: 36px; + padding: 0; + background: transparent; + border: 1px solid #3e3e3e; + border-radius: 6px; + color: #d4d4d4; + cursor: pointer; + font-size: 0.875rem; + transition: background 0.2s ease; +} + +.mobile-terminal-toolbtn:hover { + background: #3e3e3e; +} + +/* Session tabs inside overlay */ +.mobile-terminal-overlay-tabs { + background: #1e1e1e; + border-top: 1px solid #3e3e3e; +} + +.mobile-terminal-overlay-tabs .terminal-session-tabs { + background: #1e1e1e; + border-bottom: none; +} + +/* Pull handle — shown when overlay is hidden */ +.mobile-terminal-pull-handle { + position: absolute; + top: 0; + left: 50%; + transform: translateX(-50%); + z-index: 99; + padding: 4px 16px; + background: #2d2d2d; + border: 1px solid #3e3e3e; + border-top: none; + border-radius: 0 0 12px 12px; + color: #888; + cursor: pointer; + font-size: 0.75rem; + line-height: 1; + transition: background 0.2s ease; +} + +.mobile-terminal-pull-handle:hover { + background: #3e3e3e; +} + +.mobile-terminal-pull-dots { + pointer-events: none; +} + +/* Terminal content — always fills full viewport on mobile */ +.terminal-page-content.mobile-full { + flex: 1; + min-height: 0; + border: none; + border-radius: 0; + overflow: hidden; +} + /* Mobile fullscreen */ @media (max-width: 767px) { .terminal-page.fullscreen { padding: 0; } - .terminal-page.mobile .terminal-page-header { - padding: var(--space-2); - gap: var(--space-2); - } - - .terminal-page.mobile .terminal-page-header h1 { - font-size: 1rem; - } - .terminal-session-tab-name { max-width: 80px; }