diff --git a/webapp/src/App.css b/webapp/src/App.css index b7612c07..212741fc 100644 --- a/webapp/src/App.css +++ b/webapp/src/App.css @@ -41,31 +41,51 @@ /* Improve tab button accessibility */ .tab-button { - padding: 0.75rem 1.25rem; + padding: 0.85rem 1.5rem; min-width: 100px; - transition: all 0.3s ease; - font-family: inherit; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + font-family: var(--font-display); border: none; - background: none; + background: transparent; cursor: pointer; position: relative; - font-weight: bold; + font-weight: var(--font-weight-semibold); color: var(--primary-color); + font-size: var(--font-size-md); + letter-spacing: var(--letter-spacing-wide); + text-transform: uppercase; + border-radius: 8px; + margin: 0 0.25rem; + white-space: nowrap; + user-select: none; + backdrop-filter: blur(8px); + text-shadow: 0 1px 2px rgba(0,0,0,0.1); } /* Active tab state */ .tab-button.active { - background-color: var(--color-primary-dark); + background-color: var(--color-primary-alpha); color: var(--color-light); transform: translateY(-2px); - box-shadow: var(--shadow-md); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2); + position: relative; + z-index: 1; + font-weight: var(--font-weight-bold); + letter-spacing: var(--letter-spacing-wider); + text-shadow: 0 2px 4px rgba(0,0,0,0.2); +} +.tab-button:hover { + transform: translateY(-.5em); + background-color: var(--color-primary-alpha); + transition: all .5s cubic-bezier(0.1, 0, 0.5, 1); } /* Focus state - now separate from active state */ .tab-button:focus-visible { - outline: 3px solid var(--primary-color); + outline: 2px solid var(--primary-color); outline-offset: 2px; + box-shadow: 0 0 0 4px rgba(var(--color-primary-rgb), 0.2); } /* Improve button states */ @@ -143,12 +163,6 @@ } -.tab-button:hover { - background: var(--hover-background); - transform: translateY(-1px); - box-shadow: var(--shadow-sm); -} - .tab-content { flex: 1; overflow: visible; @@ -212,15 +226,12 @@ /* CSS variables for consistent theming */ :root { /* Color palette */ - --color-primary: #3498db; - --color-primary-dark: #2980b9; - --color-success: #2ecc71; - --color-error: #e74c3c; - --color-light: #ffffff; - --color-dark: #1a1a1a; - --color-primary-alpha: rgba(52, 152, 219, 0.1); + --color-primary-rgb: 52, 152, 219; + --color-primary-alpha: rgba(52, 152, 219, 0.08); /* Semantic variables */ /* Shadows */ - --shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.1); - --shadow-md: 0 4px 8px rgba(0, 0, 0, 0.2); + /* Typography */ + --font-primary: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; + --font-heading: 'Poppins', -apple-system, BlinkMacSystemFont, sans-serif; + --font-mono: 'Fira Code', Consolas, Monaco, monospace; } \ No newline at end of file diff --git a/webapp/src/hooks/useWebSocket.ts b/webapp/src/hooks/useWebSocket.ts index 1ac7f08a..a922034b 100644 --- a/webapp/src/hooks/useWebSocket.ts +++ b/webapp/src/hooks/useWebSocket.ts @@ -104,10 +104,6 @@ export const useWebSocket = (sessionId: string) => { readyState: WebSocketService.ws?.readyState, send: (message: string) => { console.log('[WebSocket] Attempting to send message:', message); - if (!isConnected) { - console.warn('[WebSocket] Cannot send message - not connected'); - return; - } return WebSocketService.send(message); }, isConnected diff --git a/webapp/src/index.css b/webapp/src/index.css index 3dcdd89c..a3d47556 100644 --- a/webapp/src/index.css +++ b/webapp/src/index.css @@ -1,13 +1,13 @@ body { margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; + font-family: var(--font-primary); background-color: #f5f5f5; color: #333; line-height: 1.6; transition: background-color 0.3s ease; overflow: clip; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } .chat-input { diff --git a/webapp/src/services/websocket.ts b/webapp/src/services/websocket.ts index 9fe3a097..a0396cbc 100644 --- a/webapp/src/services/websocket.ts +++ b/webapp/src/services/websocket.ts @@ -43,9 +43,26 @@ export class WebSocketService { ); this.ws.send(message); } else { - console.warn('[WebSocket] Cannot send message - connection not open'); + console.warn('[WebSocket] Connection not open, attempting reconnect before sending'); + this.reconnectAndSend(message); } } + private reconnectAndSend(message: string): void { + if (this.isReconnecting) { + console.warn('[WebSocket] Already attempting to reconnect'); + return; + } + console.log('[WebSocket] Attempting to reconnect before sending message'); + const onConnect = (connected: boolean) => { + if (connected) { + console.log('[WebSocket] Reconnected successfully, sending queued message'); + this.removeConnectionHandler(onConnect); + this.send(message); + } + }; + this.addConnectionHandler(onConnect); + this.connect(this.sessionId); + } public addConnectionHandler(handler: (connected: boolean) => void): void { this.connectionHandlers.push(handler); diff --git a/webapp/src/styles/GlobalStyles.ts b/webapp/src/styles/GlobalStyles.ts index 44324f4e..cf2dd2ee 100644 --- a/webapp/src/styles/GlobalStyles.ts +++ b/webapp/src/styles/GlobalStyles.ts @@ -28,11 +28,46 @@ const GlobalStyles = createGlobalStyle<{ theme: DefaultTheme; }>` } } - @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap'); + @import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap'); + @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&display=swap'); + @import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&display=swap'); + @import url('https://fonts.googleapis.com/css2?family=Syne:wght@600;700;800&display=swap'); /* Theme CSS variables */ :root { /* Theme variables are now set dynamically in ThemeProvider */ + /* Font weights */ + --font-weight-light: 300; + --font-weight-regular: 400; + --font-weight-medium: 500; + --font-weight-semibold: 600; + --font-weight-bold: 700; + --font-weight-extrabold: 800; + /* Font families */ + --font-primary: 'Outfit', system-ui, -apple-system, BlinkMacSystemFont, sans-serif; + --font-heading: 'Space Grotesk', system-ui, sans-serif; + --font-mono: 'IBM Plex Mono', 'Fira Code', monospace; + --font-display: 'Syne', system-ui, sans-serif; + /* Font sizes */ + --font-size-xs: clamp(0.75rem, 1.5vw, 0.875rem); + --font-size-sm: clamp(0.875rem, 1.75vw, 1rem); + --font-size-md: clamp(1rem, 2vw, 1.125rem); + --font-size-lg: clamp(1.25rem, 2.5vw, 1.75rem); + --font-size-xl: clamp(1.75rem, 3.5vw, 2.5rem); + --font-size-2xl: clamp(2.5rem, 5vw, 3.5rem); + /* Line heights */ + --line-height-tight: 1.15; + --line-height-normal: 1.65; + --line-height-relaxed: 1.85; + /* Letter spacing */ + --letter-spacing-tight: -0.04em; + --letter-spacing-normal: -0.02em; + --letter-spacing-wide: 0.04em; + --letter-spacing-wider: 0.08em; } + @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); + @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@500;600;700;800&family=Raleway:wght@600;700;800&display=swap'); + @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&display=swap'); + @import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@600;700;800&display=swap'); /* Override Prism.js theme colors to match current theme */ .token.comment, @@ -117,15 +152,44 @@ const GlobalStyles = createGlobalStyle<{ theme: DefaultTheme; }>` } body { - font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; + font-family: var(--font-primary); + font-weight: var(--font-weight-regular); background-color: var(--theme-background); color: var(--theme-text); - line-height: 1.5; - font-size: var(--theme-font-size-md); - letter-spacing: -0.011em; + line-height: var(--line-height-normal); + font-size: var(--font-size-md); + letter-spacing: var(--letter-spacing-normal); text-rendering: optimizeLegibility; overflow-x: hidden; min-height: 100vh; + font-feature-settings: "liga" 1, "kern" 1; + } + /* Heading styles */ + h1, h2, h3, h4, h5, h6 { + font-family: var(--font-display); + font-weight: var(--font-weight-extrabold); + letter-spacing: var(--letter-spacing-tight); + line-height: var(--line-height-tight); + margin-bottom: 0.5em; + text-transform: uppercase; + background: linear-gradient(135deg, ${({theme}) => theme.colors.primary}, ${({theme}) => theme.colors.secondary}); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + text-shadow: 0 2px 4px rgba(0,0,0,0.1); + position: relative; + z-index: 1; + } + /* Code styles */ + code, pre { + font-family: var(--font-mono); + font-weight: 600; + font-feature-settings: "liga" 0; + font-size: 0.9em; + line-height: var(--line-height-relaxed); + letter-spacing: -0.01em; + font-variant-ligatures: contextual; + border-radius: 6px; + padding: 0.2em 0.4em; } border-radius: diff --git a/webapp/src/themes/themes.ts b/webapp/src/themes/themes.ts index 5bf40a10..edeffe42 100644 --- a/webapp/src/themes/themes.ts +++ b/webapp/src/themes/themes.ts @@ -229,14 +229,14 @@ interface ExtendedTheme extends BaseTheme { }, }, typography: { - fontFamily: "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif", + fontFamily: "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif", monoFontFamily: "'Fira Code', 'Consolas', monospace", fontSize: { - xs: '0.75rem', - sm: '0.875rem', - md: '1rem', - lg: '1.25rem', - xl: '1.5rem', + xs: '0.75rem', // 12px + sm: '0.875rem', // 14px + md: '1rem', // 16px + lg: '1.125rem', // 18px + xl: '1.25rem', // 20px }, fontWeight: { regular: 400, @@ -244,9 +244,9 @@ interface ExtendedTheme extends BaseTheme { bold: 700, }, console: { - fontFamily: "'Fira Code', 'Consolas', monospace", + fontFamily: "'Fira Code', Consolas, Monaco, 'Courier New', monospace", fontSize: '0.9rem', - lineHeight: '1.5', + lineHeight: '1.6', }, }, };