diff --git a/frontend/agentic-seek-front/public/favicon.ico b/frontend/agentic-seek-front/public/favicon.ico index a11777c..01673a0 100644 Binary files a/frontend/agentic-seek-front/public/favicon.ico and b/frontend/agentic-seek-front/public/favicon.ico differ diff --git a/frontend/agentic-seek-front/public/logo192.png b/frontend/agentic-seek-front/public/logo192.png index fc44b0a..58782a2 100644 Binary files a/frontend/agentic-seek-front/public/logo192.png and b/frontend/agentic-seek-front/public/logo192.png differ diff --git a/frontend/agentic-seek-front/public/logo512.png b/frontend/agentic-seek-front/public/logo512.png deleted file mode 100644 index a4e47a6..0000000 Binary files a/frontend/agentic-seek-front/public/logo512.png and /dev/null differ diff --git a/frontend/agentic-seek-front/src/App.css b/frontend/agentic-seek-front/src/App.css index b43c965..6b4028b 100644 --- a/frontend/agentic-seek-front/src/App.css +++ b/frontend/agentic-seek-front/src/App.css @@ -4,37 +4,272 @@ box-sizing: border-box; } +html, +body, +#root { + height: 100%; + overflow: hidden; +} + body { - font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif; - background-color: #0f172a; /* darkBackground */ - color: #f8fafc; /* darkText */ + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", + sans-serif; + background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%); + color: #f8fafc; overflow-x: hidden; + min-height: 100vh; } .app { - min-height: 100vh; + height: 100vh; display: flex; flex-direction: column; + background-color: var(--background); + color: var(--foreground); + overflow: hidden; } .header { - padding: 10px 16px; - background-color: #1e293b; /* darkCard */ - border-bottom: 1px solid #334155; /* darkBorder */ - box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem 2rem; + border-bottom: 1px solid var(--border); + background-color: var(--background); + flex-shrink: 0; + height: 70px; +} + +.header::before { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 1px; + background: linear-gradient(90deg, transparent, var(--accent), transparent); +} + +.header-brand { + display: flex; + align-items: center; + gap: 16px; +} + +.logo-container { + position: relative; display: flex; align-items: center; justify-content: center; } +.logo-icon { + width: 36px; + height: 36px; + transition: all 0.3s ease; +} + +.logo-pulse { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 36px; + height: 36px; + border: 2px solid var(--accent); + border-radius: 50%; + opacity: 0; + animation: pulse 2s infinite; +} + +@keyframes pulse { + 0% { + transform: translate(-50%, -50%) scale(1); + opacity: 0.7; + } + 70% { + transform: translate(-50%, -50%) scale(1.4); + opacity: 0; + } + 100% { + transform: translate(-50%, -50%) scale(1.4); + opacity: 0; + } +} + +.brand-text { + display: flex; + flex-direction: column; + gap: 2px; +} + .header h1 { font-size: 1.5rem; - font-weight: 600; - letter-spacing: 0.5px; - color: #f8fafc; /* darkText */ + font-weight: 700; + color: var(--foreground); margin: 0; } +.brand-subtitle { + font-size: 0.75rem; + color: var(--muted-foreground); + font-weight: 500; + letter-spacing: 0.5px; + text-transform: uppercase; +} + +.header-status { + display: flex; + align-items: center; + gap: 8px; +} + +.status-indicator { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 12px; + border-radius: 20px; + background-color: var(--muted); + border: 1px solid var(--border); + transition: all 0.3s ease; +} + +.status-indicator.online { + background-color: rgba(34, 197, 94, 0.1); + border-color: rgba(34, 197, 94, 0.3); +} + +.status-indicator.offline { + background-color: rgba(239, 68, 68, 0.1); + border-color: rgba(239, 68, 68, 0.3); +} + +.status-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background-color: var(--muted-foreground); + transition: all 0.3s ease; +} + +.status-indicator.online .status-dot { + background-color: #22c55e; + box-shadow: 0 0 8px rgba(34, 197, 94, 0.5); + animation: statusPulse 2s infinite; +} + +.status-indicator.offline .status-dot { + background-color: #ef4444; +} + +@keyframes statusPulse { + 0%, + 100% { + opacity: 1; + transform: scale(1); + } + 50% { + opacity: 0.7; + transform: scale(1.2); + } +} + +.status-text { + font-size: 0.75rem; + font-weight: 600; + color: var(--foreground); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.header-actions { + display: flex; + align-items: center; + gap: 12px; +} + +.action-button { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + min-width: 44px; + height: 44px; + padding: 0 12px; + border-radius: 12px; + border: 1px solid var(--border); + background: var(--card); + color: var(--foreground); + text-decoration: none; + font-size: 0.875rem; + font-weight: 500; + cursor: pointer; + transition: all 0.3s ease; + position: relative; + overflow: hidden; +} + +.action-button::before { + content: ""; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient( + 90deg, + transparent, + rgba(255, 255, 255, 0.1), + transparent + ); + transition: left 0.5s ease; +} + +.action-button:hover::before { + left: 100%; +} + +.action-button:hover { + background: var(--muted); + color: var(--foreground); + transform: translateY(-2px); + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1); + border-color: var(--border); +} + +.action-button.github-link:hover { + background: #24292e; + border-color: #24292e; + color: white; + box-shadow: 0 8px 25px rgba(36, 41, 46, 0.3); +} + +.action-text { + font-size: 0.8rem; + font-weight: 600; + display: none; +} + +@media (min-width: 768px) { + .action-text { + display: block; + } + + .action-button { + min-width: auto; + padding: 0 16px; + } +} + +.main { + flex: 1; + padding: 1rem 2rem; + overflow: hidden; + display: flex; + flex-direction: column; +} + .section-tabs { display: flex; gap: 8px; @@ -65,17 +300,11 @@ body { color: #f8fafc; /* darkText */ } -.main { - flex: 1; - padding: 16px; - width: 100%; -} - .app-sections { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 16px; - height: calc(100vh - 80px); + display: flex; + gap: 1rem; + height: 100%; + overflow: hidden; } .left-panel, @@ -83,7 +312,8 @@ body { background-color: #1e293b; /* darkCard */ border: 1px solid #334155; /* darkBorder */ border-radius: 8px; - box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), + 0 2px 4px -1px rgba(0, 0, 0, 0.06); display: flex; flex-direction: column; overflow: hidden; @@ -98,32 +328,68 @@ body { .task-section, .chat-section, .computer-section { - background-color: #1e293b; /* darkCard */ - border: 1px solid #334155; /* darkBorder */ - border-radius: 8px; - box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); - padding: 16px; + background: var(--card); + backdrop-filter: blur(20px); + border: 1px solid var(--border); + border-radius: 16px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12); + padding: 24px; display: flex; flex-direction: column; overflow: hidden; + position: relative; + height: 100%; + width: 100%; +} + +.task-section::before, +.chat-section::before, +.computer-section::before { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 1px; + background: linear-gradient( + 90deg, + transparent, + rgba(96, 165, 250, 0.5), + transparent + ); } .task-section h2, .chat-section h2, .computer-section h2 { - font-size: 1.1rem; + font-size: 1.25rem; font-weight: 600; - color: #cbd5e1; /* darkTextSecondary */ - margin-bottom: 12px; - letter-spacing: 0.5px; - border-bottom: 1px solid #334155; /* darkBorder */ - padding-bottom: 8px; + color: var(--foreground); + margin-bottom: 20px; + display: flex; + align-items: center; + gap: 12px; +} + +.task-section h2::before { + content: "💼"; + font-size: 1.1rem; +} + +.chat-section h2::before { + content: "💬"; + font-size: 1.1rem; +} + +.computer-section h2::before { + content: "🖥️"; + font-size: 1.1rem; } .task-details { flex: 1; overflow-y: auto; - background-color: #2d3748; /* Slightly lighter than darkCard */ + background-color: var(--muted); border-radius: 8px; padding: 16px; margin-top: 12px; @@ -136,14 +402,14 @@ body { display: flex; justify-content: center; align-items: flex-start; - background-color: #2d3748; /* Slightly lighter than darkCard */ + background-color: var(--muted); border-radius: 8px; padding: 16px; } .screenshot-container img { max-width: 100%; - border: 1px solid #4a5568; /* Medium gray */ + border: 1px solid var(--border); border-radius: 4px; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); } @@ -152,7 +418,7 @@ body { .right-panel h2 { font-size: 1.1rem; font-weight: 600; - color: #cbd5e1; /* darkTextSecondary */ + color: var(--foreground); margin-bottom: 8px; letter-spacing: 1px; } @@ -160,342 +426,355 @@ body { .messages { flex: 1; overflow-y: auto; - padding: 12px 8px; display: flex; flex-direction: column; - gap: 12px; - margin-bottom: 8px; + gap: 1rem; + margin-bottom: 1rem; + padding-right: 0.5rem; + min-height: 0; } -/* Message header layout */ -.message-header { - display: flex; - flex-direction: column; - align-items: flex-start; - justify-content: space-between; - align-items: center; - margin-bottom: 8px; +.messages::-webkit-scrollbar { + width: 6px; } -.reasoning-toggle { - background: rgba(255, 255, 255, 0.1); - border: 1px solid rgba(255, 255, 255, 0.2); - border-radius: 4px; - color: #fff; - padding: 4px 8px; - font-size: 12px; - cursor: pointer; - transition: all 0.2s ease; - display: flex; - align-items: center; - gap: 4px; - align-self: flex-start; +.messages::-webkit-scrollbar-track { + background: var(--muted); + border-radius: 3px; } -.reasoning-toggle:hover { - background: rgba(255, 255, 255, 0.2); - border-color: rgba(255, 255, 255, 0.3); -} - -.reasoning-toggle:active { - transform: translateY(1px); -} - -/* Reasoning content container */ -.reasoning-content { - margin-top: 12px; - padding: 12px; - background: rgba(0, 0, 0, 0.2); - border-left: 3px solid rgba(255, 255, 255, 0.3); - border-radius: 0 4px 4px 0; - font-size: 0.9em; - line-height: 1.4; -} - -.reasoning-content h1, -.reasoning-content h2, -.reasoning-content h3, -.reasoning-content h4, -.reasoning-content h5, -.reasoning-content h6 { - font-size: 1em; - margin: 8px 0 4px 0; - color: rgba(255, 255, 255, 0.9); -} - -.reasoning-content p { - margin: 6px 0; - color: rgba(255, 255, 255, 0.8); -} - -/* Alternative light theme styles */ -.message.user-message .reasoning-toggle { - background: rgba(0, 0, 0, 0.05); - border-color: rgba(0, 0, 0, 0.1); - color: #333; -} - -.message.user-message .reasoning-toggle:hover { - background: rgba(0, 0, 0, 0.1); - border-color: rgba(0, 0, 0, 0.2); -} - -.message.user-message .reasoning-content { - background: rgba(0, 0, 0, 0.03); - border-left-color: rgba(0, 0, 0, 0.2); -} - -.message.user-message .reasoning-content p { - color: rgba(0, 0, 0, 0.7); +.messages::-webkit-scrollbar-thumb { + background: var(--muted-foreground); + border-radius: 3px; } .placeholder { text-align: center; - color: #64748b; /* lighter gray */ - margin-top: 20px; - font-style: italic; + color: var(--muted-foreground); + margin: 2rem 0; + font-size: 0.875rem; } .message { max-width: 85%; - padding: 12px 16px; - border-radius: 12px; - font-size: 0.95rem; + padding: 0.75rem 1rem; + border-radius: 8px; + font-size: 0.875rem; line-height: 1.5; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + position: relative; + animation: messageSlide 0.3s ease-out; } -.messages::-webkit-scrollbar, -.content::-webkit-scrollbar { - width: 6px; -} - -.messages::-webkit-scrollbar-track, -.content::-webkit-scrollbar-track { - background: #2d3748; /* Slightly lighter than darkCard */ - border-radius: 8px; -} - -.messages::-webkit-scrollbar-thumb, -.content::-webkit-scrollbar-thumb { - background: #4a5568; /* Medium gray */ - border-radius: 8px; -} - -.messages::-webkit-scrollbar-thumb:hover, -.content::-webkit-scrollbar-thumb:hover { - background: #718096; /* Lighter gray on hover */ +@keyframes messageSlide { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } } .user-message { - background-color: #0066cc; /* primary */ - color: #ffffff; /* white */ + background-color: var(--accent); + color: var(--accent-foreground); align-self: flex-end; - border-top-right-radius: 4px; } .agent-message { - background-color: #2d3748; /* Slightly lighter than darkCard */ - color: #f8fafc; /* darkText */ + background-color: var(--muted); + color: var(--foreground); align-self: flex-start; - border-top-left-radius: 4px; + border: 1px solid var(--border); } .error-message { - background-color: #dc3545; /* error */ - color: #ffffff; /* white */ + background-color: var(--destructive); + color: var(--destructive-foreground); align-self: flex-start; - border-top-left-radius: 4px; +} + +.message-header { + display: flex; + flex-direction: column; + gap: 0.5rem; + margin-bottom: 0.5rem; } .agent-name { - display: block; - font-size: 0.8rem; - color: #cbd5e1; /* darkTextSecondary */ - margin-bottom: 4px; + font-size: 0.75rem; + color: var(--muted-foreground); font-weight: 500; } +.reasoning-toggle { + background-color: var(--secondary); + border: 1px solid var(--border); + border-radius: 6px; + color: var(--secondary-foreground); + padding: 0.25rem 0.5rem; + font-size: 0.75rem; + cursor: pointer; + transition: all 0.2s ease; + width: fit-content; +} + +.reasoning-toggle:hover { + background-color: var(--muted); +} + +.reasoning-content { + margin-top: 0.75rem; + padding: 0.75rem; + background-color: var(--secondary); + border: 1px solid var(--border); + border-radius: 6px; + font-size: 0.8rem; +} + .loading-animation { text-align: center; - color: #cbd5e1; /* darkTextSecondary */ - padding: 8px 0; - font-size: 0.9rem; - font-style: italic; - border-top: 1px solid #334155; /* darkBorder */ - margin-bottom: 8px; + color: var(--muted-foreground); + padding: 0.75rem; + font-size: 0.875rem; + border-top: 1px solid var(--border); + flex-shrink: 0; } .input-form { display: flex; - gap: 8px; - margin-top: 8px; + align-items: center; + gap: 0.75rem; + padding: 0.75rem 1rem; + background-color: var(--card); + border-radius: 24px; + border: 1px solid var(--border); + flex-shrink: 0; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + transition: all 0.2s ease; +} + +.input-form:focus-within { + border-color: var(--accent); + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); } .input-form input { flex: 1; - padding: 12px 16px; + padding: 0.75rem 1rem; font-size: 0.95rem; - background-color: #2d3748; /* Slightly lighter than darkCard */ - border: 1px solid #4a5568; /* Medium gray */ - color: #f8fafc; /* darkText */ - border-radius: 8px; - outline: none; - transition: all 0.2s ease; -} - -.input-form input:focus { - border-color: #0066cc; /* primary */ - box-shadow: 0 0 0 2px rgba(0, 102, 204, 0.2); /* primary with opacity */ -} - -.input-form button { - padding: 12px 20px; - font-size: 0.95rem; - background-color: #0066cc; /* primary */ - color: #ffffff; /* white */ + background-color: transparent; border: none; - border-radius: 8px; + color: var(--foreground); + border-radius: 20px; + outline: none; + font-family: inherit; +} + +.input-form input::placeholder { + color: var(--muted-foreground); +} + +.input-form .action-buttons { + display: flex; + gap: 0.5rem; + align-items: center; +} + +.input-form .icon-button { + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + background-color: var(--foreground); + color: var(--background); + border: none; + border-radius: 50%; cursor: pointer; font-weight: 500; transition: all 0.2s ease; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } -.input-form button:hover { - background-color: #004c99; /* primaryDark */ +.input-form .icon-button:hover { + transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); + background-color: var(--muted-foreground); } -.input-form button:disabled { - background-color: #4a5568; /* Medium gray */ - opacity: 0.7; +.input-form .icon-button:disabled { + opacity: 0.5; cursor: not-allowed; + transform: none; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } -.right-panel { - padding: 16px; +.input-form .icon-button.stop-button { + background-color: var(--destructive); + color: var(--destructive-foreground); +} + +.input-form .icon-button.stop-button:hover { + background-color: #dc2626; } .view-selector { display: flex; - gap: 8px; - margin-bottom: 16px; + gap: 0.25rem; + margin-bottom: 1rem; + padding: 0.25rem; + background-color: var(--muted); + border-radius: 8px; + flex-shrink: 0; } .view-selector button { - padding: 10px 16px; - font-size: 0.9rem; - background-color: #2d3748; /* Slightly lighter than darkCard */ - color: #cbd5e1; /* darkTextSecondary */ - border: 1px solid #4a5568; /* Medium gray */ - border-radius: 8px; + padding: 0.5rem 1rem; + font-size: 0.875rem; + background-color: transparent; + color: var(--muted-foreground); + border: none; + border-radius: 6px; cursor: pointer; font-weight: 500; transition: all 0.2s ease; + flex: 1; } .view-selector button.active { - background-color: #0066cc; /* primary */ - color: #ffffff; /* white */ - border-color: #0066cc; /* primary */ + background-color: var(--background); + color: var(--foreground); } .view-selector button:hover:not(.active) { - background-color: #4a5568; /* Medium gray */ - color: #f8fafc; /* darkText */ -} - -.view-selector button:disabled { - background-color: #4a5568; /* Medium gray */ - opacity: 0.5; - cursor: not-allowed; + color: var(--foreground); } .content { flex: 1; overflow-y: auto; - padding: 8px 0; - margin-top: 8px; + min-height: 0; +} + +.content::-webkit-scrollbar { + width: 6px; +} + +.content::-webkit-scrollbar-track { + background: var(--muted); + border-radius: 3px; +} + +.content::-webkit-scrollbar-thumb { + background: var(--muted-foreground); + border-radius: 3px; } .blocks { display: flex; flex-direction: column; - gap: 16px; + gap: 1rem; } .block { - background-color: #2d3748; /* Slightly lighter than darkCard */ - padding: 16px; - border: 1px solid #4a5568; /* Medium gray */ + background-color: var(--card); + padding: 1rem; + border: 1px solid var(--border); border-radius: 8px; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); } .block-tool, .block-feedback, -.block-success { - font-size: 0.9rem; - margin-bottom: 8px; - color: #cbd5e1; /* darkTextSecondary */ -} - -.block-tool { - font-weight: 600; - color: #0066cc; /* primary */ +.block-success, +.block-failure { + font-size: 0.875rem; + margin-bottom: 0.5rem; + font-weight: 500; } .block-success { - color: #28a745; /* success */ + color: #22c55e; } .block-failure { - color: #d21b0b; /* success */ + color: #ef4444; +} + +.block-failure::before { + content: "❌"; +} + +.block-feedback { + color: #cbd5e1; +} + +.block-feedback::before { + content: "💬"; } .block pre { - background-color: #1a202c; /* Darker than darkCard */ - padding: 12px; - border-radius: 6px; + background: var(--muted); + padding: 16px; + border-radius: 8px; font-size: 0.85rem; white-space: pre-wrap; word-break: break-all; - color: #e2e8f0; /* Light gray */ - margin: 8px 0; - font-family: 'Menlo', 'Monaco', 'Courier New', monospace; + color: var(--muted-foreground); + margin: 12px 0; + font-family: "JetBrains Mono", "Fira Code", "Menlo", monospace; + border-left: 3px solid var(--muted-foreground); + overflow-x: auto; } .screenshot { - margin-top: 8px; display: flex; justify-content: center; align-items: center; + padding: 20px; + background: var(--muted); + border-radius: 12px; + border: 1px solid var(--border); } .screenshot img { max-width: 100%; - border: 1px solid #4a5568; /* Medium gray */ - border-radius: 8px; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + border: 2px solid var(--border); + border-radius: 12px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2); + transition: all 0.3s ease; +} + +.screenshot img:hover { + transform: scale(1.02); + box-shadow: 0 12px 40px rgba(0, 0, 0, 0.3); } .error { - color: #dc3545; /* error */ + color: var(--destructive-foreground); font-size: 0.9rem; - margin-bottom: 12px; - padding: 8px 12px; - background-color: rgba(220, 53, 69, 0.1); /* error with opacity */ - border-radius: 6px; - border-left: 3px solid #dc3545; /* error */ + margin-bottom: 16px; + padding: 12px 16px; + background: rgba(239, 68, 68, 0.1); + border-radius: 8px; + border-left: 3px solid var(--destructive); + display: flex; + align-items: center; + gap: 8px; } -@media (max-width: 1024px) { - .app-sections { - grid-template-columns: 1fr 1fr; - grid-template-rows: auto 1fr; +.error::before { + content: "⚠️"; +} + +@media (max-width: 1200px) { + .main { + padding: 24px; } - .task-section { - grid-column: 1 / -1; + .app-sections { + gap: 24px; } } @@ -504,26 +783,49 @@ body { padding: 16px; } - .app-sections { - grid-template-columns: 1fr; - height: auto; - gap: 16px; - } - - .task-section, .chat-section, .computer-section { - height: calc(33vh - 60px); - min-height: 300px; + height: 50vh; + min-height: 400px; + } + + .header { + padding: 0.75rem 1.25rem; } .header h1 { - font-size: 1.5rem; + font-size: 1.75rem; } - .input-form button { + .message { + max-width: 90%; padding: 12px 16px; } + + .view-selector button { + padding: 10px 16px; + font-size: 0.85rem; + } + + .input-form { + padding: 0.75rem 1rem; + border-radius: 20px; + } + + .input-form input { + padding: 0.75rem; + font-size: 0.9rem; + } + + .input-form .icon-button { + width: 36px; + height: 36px; + } + + .input-form .icon-button.stop-button { + width: 36px; + height: 36px; + } } @media (max-width: 480px) { @@ -531,35 +833,46 @@ body { padding: 12px; } - .message { - max-width: 90%; - padding: 10px 12px; - } - - .view-selector button { - padding: 8px 12px; - font-size: 0.85rem; - } - - .task-section, .chat-section, .computer-section { + padding: 16px; + min-height: 350px; + } + + .header h1 { + font-size: 1.5rem; + } + + .message { + padding: 10px 14px; + font-size: 0.9rem; + } + + .block { + padding: 16px; + } + + .block pre { + font-size: 0.8rem; padding: 12px; } .input-form { - margin-top: 8px; + padding: 0.5rem 0.75rem; } - .header h1 { - font-size: 1.3rem; + .input-form input { + padding: 0.5rem 0.75rem; + font-size: 0.85rem; } - .task-section h2, - .chat-section h2, - .computer-section h2 { - font-size: 1rem; - margin-bottom: 8px; - padding-bottom: 6px; + .input-form .icon-button { + width: 34px; + height: 34px; } -} \ No newline at end of file + + .input-form .icon-button.stop-button { + width: 34px; + height: 34px; + } +} diff --git a/frontend/agentic-seek-front/src/App.js b/frontend/agentic-seek-front/src/App.js index b456e79..6222e11 100644 --- a/frontend/agentic-seek-front/src/App.js +++ b/frontend/agentic-seek-front/src/App.js @@ -1,329 +1,418 @@ -import React, { useState, useEffect, useRef } from 'react'; -import ReactMarkdown from 'react-markdown'; -import axios from 'axios'; -import './App.css'; -import { colors } from './colors'; +import React, { useState, useEffect, useRef, useCallback } from "react"; +import ReactMarkdown from "react-markdown"; +import axios from "axios"; +import "./App.css"; +import { ThemeToggle } from "./components/ThemeToggle"; +import { ResizableLayout } from "./components/ResizableLayout"; +import faviconPng from "./logo.png"; + +const BACKEND_URL = process.env.REACT_APP_BACKEND_URL || 'http://localhost:7777'; +console.log("Using backend URL:", BACKEND_URL); -const BACKEND_URL = 'http://localhost:7777' || process.env.REACT_APP_BACKEND_URL; -console.log('Using backend URL:', BACKEND_URL); function App() { - const [query, setQuery] = useState(''); - const [messages, setMessages] = useState([]); - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(null); - const [currentView, setCurrentView] = useState('blocks'); - const [responseData, setResponseData] = useState(null); - const [isOnline, setIsOnline] = useState(false); - const [status, setStatus] = useState('Agents ready'); - const [expandedReasoning, setExpandedReasoning] = useState(new Set()); - const messagesEndRef = useRef(null); + const [query, setQuery] = useState(""); + const [messages, setMessages] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const [currentView, setCurrentView] = useState("blocks"); + const [responseData, setResponseData] = useState(null); + const [isOnline, setIsOnline] = useState(false); + const [status, setStatus] = useState("Agents ready"); + const [expandedReasoning, setExpandedReasoning] = useState(new Set()); + const messagesEndRef = useRef(null); - useEffect(() => { - const intervalId = setInterval(() => { - checkHealth(); - fetchLatestAnswer(); - fetchScreenshot(); - }, 3000); - return () => clearInterval(intervalId); - }, [messages]); + const fetchLatestAnswer = useCallback(async () => { + try { + const res = await axios.get(`${BACKEND_URL}/latest_answer`); + const data = res.data; - const checkHealth = async () => { - try { - await axios.get(`${BACKEND_URL}/health`); - setIsOnline(true); - console.log('System is online'); - } catch { - setIsOnline(false); - console.log('System is offline'); - } - }; - - const fetchScreenshot = async () => { - try { - const timestamp = new Date().getTime(); - const res = await axios.get(`${BACKEND_URL}/screenshots/updated_screen.png?timestamp=${timestamp}`, { - responseType: 'blob' - }); - console.log('Screenshot fetched successfully'); - const imageUrl = URL.createObjectURL(res.data); - setResponseData((prev) => { - if (prev?.screenshot && prev.screenshot !== 'placeholder.png') { - URL.revokeObjectURL(prev.screenshot); - } - return { - ...prev, - screenshot: imageUrl, - screenshotTimestamp: new Date().getTime() - }; - }); - } catch (err) { - console.error('Error fetching screenshot:', err); - setResponseData((prev) => ({ - ...prev, - screenshot: 'placeholder.png', - screenshotTimestamp: new Date().getTime() - })); - } - }; - - const normalizeAnswer = (answer) => { - return answer - .trim() - .toLowerCase() - .replace(/\s+/g, ' ') - .replace(/[.,!?]/g, '') - }; - - const scrollToBottom = () => { - messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); - }; - - const toggleReasoning = (messageIndex) => { - setExpandedReasoning(prev => { - const newSet = new Set(prev); - if (newSet.has(messageIndex)) { - newSet.delete(messageIndex); - } else { - newSet.add(messageIndex); - } - return newSet; - }); - }; - - const fetchLatestAnswer = async () => { - try { - const res = await axios.get(`${BACKEND_URL}/latest_answer`); - const data = res.data; - - updateData(data); - if (!data.answer || data.answer.trim() === '') { - return; - } - const normalizedNewAnswer = normalizeAnswer(data.answer); - const answerExists = messages.some( - (msg) => normalizeAnswer(msg.content) === normalizedNewAnswer - ); - if (!answerExists) { - setMessages((prev) => [ - ...prev, - { - type: 'agent', - content: data.answer, - reasoning: data.reasoning, - agentName: data.agent_name, - status: data.status, - uid: data.uid, - }, - ]); - setStatus(data.status); - scrollToBottom(); - } else { - console.log('Duplicate answer detected, skipping:', data.answer); - } - } catch (error) { - console.error('Error fetching latest answer:', error); - } - }; - - const updateData = (data) => { - setResponseData((prev) => ({ - ...prev, - blocks: data.blocks || prev.blocks || null, - done: data.done, - answer: data.answer, - agent_name: data.agent_name, + updateData(data); + if (!data.answer || data.answer.trim() === "") { + return; + } + const normalizedNewAnswer = normalizeAnswer(data.answer); + const answerExists = messages.some( + (msg) => normalizeAnswer(msg.content) === normalizedNewAnswer + ); + if (!answerExists) { + setMessages((prev) => [ + ...prev, + { + type: "agent", + content: data.answer, + reasoning: data.reasoning, + agentName: data.agent_name, status: data.status, uid: data.uid, - })); - }; - - const handleStop = async (e) => { - e.preventDefault(); - checkHealth(); - setIsLoading(false); - setError(null); - try { - const res = await axios.get(`${BACKEND_URL}/stop`); - setStatus("Requesting stop..."); - } catch (err) { - console.error('Error stopping the agent:', err); - } + }, + ]); + setStatus(data.status); + scrollToBottom(); + } else { + console.log("Duplicate answer detected, skipping:", data.answer); + } + } catch (error) { + console.error("Error fetching latest answer:", error); } + }, [messages]); - const handleSubmit = async (e) => { - e.preventDefault(); - checkHealth(); - if (!query.trim()) { - console.log('Empty query'); - return; + useEffect(() => { + const intervalId = setInterval(() => { + checkHealth(); + fetchLatestAnswer(); + fetchScreenshot(); + }, 3000); + return () => clearInterval(intervalId); + }, [fetchLatestAnswer]); + + const checkHealth = async () => { + try { + await axios.get(`${BACKEND_URL}/health`); + setIsOnline(true); + console.log("System is online"); + } catch { + setIsOnline(false); + console.log("System is offline"); + } + }; + + const fetchScreenshot = async () => { + try { + const timestamp = new Date().getTime(); + const res = await axios.get( + `${BACKEND_URL}/screenshots/updated_screen.png?timestamp=${timestamp}`, + { + responseType: "blob", } - setMessages((prev) => [...prev, { type: 'user', content: query }]); - setIsLoading(true); - setError(null); - - try { - console.log('Sending query:', query); - setQuery('waiting for response...'); - const res = await axios.post(`${BACKEND_URL}/query`, { - query, - tts_enabled: false - }); - setQuery('Enter your query...'); - console.log('Response:', res.data); - const data = res.data; - updateData(data); - } catch (err) { - console.error('Error:', err); - setError('Failed to process query.'); - setMessages((prev) => [ - ...prev, - { type: 'error', content: 'Error: Unable to get a response.' }, - ]); - } finally { - console.log('Query completed'); - setIsLoading(false); - setQuery(''); + ); + console.log("Screenshot fetched successfully"); + const imageUrl = URL.createObjectURL(res.data); + setResponseData((prev) => { + if (prev?.screenshot && prev.screenshot !== "placeholder.png") { + URL.revokeObjectURL(prev.screenshot); } - }; + return { + ...prev, + screenshot: imageUrl, + screenshotTimestamp: new Date().getTime(), + }; + }); + } catch (err) { + console.error("Error fetching screenshot:", err); + setResponseData((prev) => ({ + ...prev, + screenshot: "placeholder.png", + screenshotTimestamp: new Date().getTime(), + })); + } + }; - const handleGetScreenshot = async () => { - try { - setCurrentView('screenshot'); - } catch (err) { - setError('Browser not in use'); - } - }; + const normalizeAnswer = (answer) => { + return answer + .trim() + .toLowerCase() + .replace(/\s+/g, " ") + .replace(/[.,!?]/g, ""); + }; - return ( -
-
-

AgenticSeek

-
-
-
-
-

Chat Interface

-
- {messages.length === 0 ? ( -

No messages yet. Type below to start!

- ) : ( - messages.map((msg, index) => ( -
-
- {msg.type === 'agent' && ( - {msg.agentName} - )} - {msg.type === 'agent' && msg.reasoning && expandedReasoning.has(index) && ( -
- {msg.reasoning} -
- )} - {msg.type === 'agent' && ( - - )} -
-
- {msg.content} -
-
- )) - )} -
-
- {isOnline &&
{status}
} - {!isLoading && !isOnline &&

System offline. Deploy backend first.

} -
- setQuery(e.target.value)} - placeholder="Type your query..." - disabled={isLoading} - /> - - -
-
+ const scrollToBottom = () => { + messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); + }; -
-

Computer View

-
- - -
-
- {error &&

{error}

} - {currentView === 'blocks' ? ( -
- {responseData && responseData.blocks && Object.values(responseData.blocks).length > 0 ? ( - Object.values(responseData.blocks).map((block, index) => ( -
-

Tool: {block.tool_type}

-
{block.block}
-

Feedback: {block.feedback}

- {block.success ? ( -

Success

- ) : ( -

Failure

- )} -
- )) - ) : ( -
-

Tool: No tool in use

-
No file opened
-
- )} -
- ) : ( -
- Screenshot { - e.target.src = 'placeholder.png'; - console.error('Failed to load screenshot'); - }} - key={responseData?.screenshotTimestamp || 'default'} - /> -
- )} -
-
-
-
+ const toggleReasoning = (messageIndex) => { + setExpandedReasoning((prev) => { + const newSet = new Set(prev); + if (newSet.has(messageIndex)) { + newSet.delete(messageIndex); + } else { + newSet.add(messageIndex); + } + return newSet; + }); + }; + + const updateData = (data) => { + setResponseData((prev) => ({ + ...prev, + blocks: data.blocks || prev.blocks || null, + done: data.done, + answer: data.answer, + agent_name: data.agent_name, + status: data.status, + uid: data.uid, + })); + }; + + const handleStop = async (e) => { + e.preventDefault(); + checkHealth(); + setIsLoading(false); + setError(null); + try { + await axios.get(`${BACKEND_URL}/stop`); + setStatus("Requesting stop..."); + } catch (err) { + console.error("Error stopping the agent:", err); + } + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + checkHealth(); + if (!query.trim()) { + console.log("Empty query"); + return; + } + setMessages((prev) => [...prev, { type: "user", content: query }]); + setIsLoading(true); + setError(null); + + try { + console.log("Sending query:", query); + setQuery("waiting for response..."); + const res = await axios.post(`${BACKEND_URL}/query`, { + query, + tts_enabled: false, + }); + setQuery("Enter your query..."); + console.log("Response:", res.data); + const data = res.data; + updateData(data); + } catch (err) { + console.error("Error:", err); + setError("Failed to process query."); + setMessages((prev) => [ + ...prev, + { type: "error", content: "Error: Unable to get a response." }, + ]); + } finally { + console.log("Query completed"); + setIsLoading(false); + setQuery(""); + } + }; + + const handleGetScreenshot = async () => { + try { + setCurrentView("screenshot"); + } catch (err) { + setError("Browser not in use"); + } + }; + + return ( +
+
+
+
+ AgenticSeek +
+
+

AgenticSeek

+
- ); +
+
+
+ + {isOnline ? "Online" : "Offline"} + +
+
+
+ + + + + GitHub + +
+ +
+
+
+
+ +
+

Chat Interface

+
+ {messages.length === 0 ? ( +

+ No messages yet. Type below to start! +

+ ) : ( + messages.map((msg, index) => ( +
+
+ {msg.type === "agent" && ( + {msg.agentName} + )} + {msg.type === "agent" && + msg.reasoning && + expandedReasoning.has(index) && ( +
+ {msg.reasoning} +
+ )} + {msg.type === "agent" && ( + + )} +
+
+ {msg.content} +
+
+ )) + )} +
+
+ {isOnline &&
{status}
} + {!isLoading && !isOnline && ( +

+ System offline. Deploy backend first. +

+ )} +
+ setQuery(e.target.value)} + placeholder="Type your query..." + disabled={isLoading} + /> +
+ + +
+
+
+ +
+

Computer View

+
+ + +
+
+ {error &&

{error}

} + {currentView === "blocks" ? ( +
+ {responseData && + responseData.blocks && + Object.values(responseData.blocks).length > 0 ? ( + Object.values(responseData.blocks).map((block, index) => ( +
+

Tool: {block.tool_type}

+
{block.block}
+

+ Feedback: {block.feedback} +

+ {block.success ? ( +

Success

+ ) : ( +

Failure

+ )} +
+ )) + ) : ( +
+

Tool: No tool in use

+
No file opened
+
+ )} +
+ ) : ( +
+ Screenshot { + e.target.src = "placeholder.png"; + console.error("Failed to load screenshot"); + }} + key={responseData?.screenshotTimestamp || "default"} + /> +
+ )} +
+
+ +
+
+ ); } -export default App; \ No newline at end of file +export default App; diff --git a/frontend/agentic-seek-front/src/colors.js b/frontend/agentic-seek-front/src/colors.js index 7141ca4..a012414 100644 --- a/frontend/agentic-seek-front/src/colors.js +++ b/frontend/agentic-seek-front/src/colors.js @@ -1,63 +1,64 @@ export const colors = { - // Primary colors - primary: '#0066cc', - primaryLight: '#e6f2ff', - primaryDark: '#004c99', - - // Secondary colors - secondary: '#6c757d', - secondaryLight: '#f8f9fa', - secondaryDark: '#343a40', - + // Primary colors - matching the dashboard theme + primary: "#2563eb", + primaryLight: "#dbeafe", + primaryDark: "#1d4ed8", + + // Secondary colors - modern grays + secondary: "#64748b", + secondaryLight: "#f1f5f9", + secondaryDark: "#1e293b", + // Accent colors - accent: '#ff9500', - accentLight: '#fff4e6', - accentDark: '#cc7a00', - + accent: "#f59e0b", + accentLight: "#fef3c7", + accentDark: "#d97706", + // Status colors - success: '#28a745', - successLight: '#e8f5e9', - warning: '#ffc107', - warningLight: '#fff9e6', - error: '#dc3545', - errorLight: '#ffebee', - info: '#17a2b8', - infoLight: '#e3f2fd', - - // Neutral colors - white: '#ffffff', - gray100: '#f8f9fa', - gray200: '#e9ecef', - gray300: '#dee2e6', - gray400: '#ced4da', - gray500: '#adb5bd', - gray600: '#6c757d', - gray700: '#495057', - gray800: '#343a40', - gray900: '#212529', - black: '#000000', - + success: "#10b981", + successLight: "#d1fae5", + warning: "#f59e0b", + warningLight: "#fef3c7", + error: "#ef4444", + errorLight: "#fee2e2", + info: "#06b6d4", + infoLight: "#cffafe", + + // Neutral colors - modern palette + white: "#ffffff", + gray50: "#f8fafc", + gray100: "#f1f5f9", + gray200: "#e2e8f0", + gray300: "#cbd5e1", + gray400: "#94a3b8", + gray500: "#64748b", + gray600: "#475569", + gray700: "#334155", + gray800: "#1e293b", + gray900: "#0f172a", + black: "#000000", + // Text colors - textPrimary: '#212529', - textSecondary: '#6c757d', - textDisabled: '#adb5bd', - + textPrimary: "#0f172a", + textSecondary: "#64748b", + textDisabled: "#94a3b8", + // Background colors - background: '#f8f8f8', - card: '#ffffff', - + background: "#f8fafc", + card: "#ffffff", + // Border colors - border: '#dee2e6', - divider: '#e9ecef', - + border: "#e2e8f0", + divider: "#f1f5f9", + // Transparent colors - transparent: 'transparent', - semiTransparent: 'rgba(0, 0, 0, 0.5)', - + transparent: "transparent", + semiTransparent: "rgba(15, 23, 42, 0.6)", + // Dark theme colors - darkBackground: '#0f172a', - darkCard: '#1e293b', - darkBorder: '#334155', - darkText: '#f8fafc', - darkTextSecondary: '#cbd5e1', + darkBackground: "#0f172a", + darkCard: "#1e293b", + darkBorder: "#334155", + darkText: "#f8fafc", + darkTextSecondary: "#cbd5e1", }; diff --git a/frontend/agentic-seek-front/src/components/ResizableLayout.css b/frontend/agentic-seek-front/src/components/ResizableLayout.css new file mode 100644 index 0000000..7422666 --- /dev/null +++ b/frontend/agentic-seek-front/src/components/ResizableLayout.css @@ -0,0 +1,69 @@ +.resizable-container { + display: flex; + width: 100%; + height: 100%; + overflow: hidden; +} + +.resizable-left, +.resizable-right { + height: 100%; + overflow: hidden; + min-width: 0; +} + +.resize-handle { + width: 8px; + background-color: transparent; + cursor: col-resize; + display: flex; + align-items: center; + justify-content: center; + padding: 0 2px; + transition: background-color 0.2s ease; + position: relative; + flex-shrink: 0; +} + +.resize-handle:hover { + background-color: var(--accent); +} + +.resize-handle-line { + width: 2px; + height: 40px; + background-color: var(--border); + border-radius: 1px; + transition: all 0.2s ease; +} + +.resize-handle:hover .resize-handle-line { + background-color: var(--accent-foreground); + height: 60px; +} + +.resizable-container.dragging .resize-handle { + background-color: var(--accent); +} + +.resizable-container.dragging .resize-handle-line { + background-color: var(--accent-foreground); + height: 100vh; +} + +/* Mobile responsiveness */ +@media (max-width: 768px) { + .resizable-container { + flex-direction: column; + } + + .resizable-left, + .resizable-right { + width: 100% !important; + height: 50vh; + } + + .resize-handle { + display: none; + } +} diff --git a/frontend/agentic-seek-front/src/components/ResizableLayout.js b/frontend/agentic-seek-front/src/components/ResizableLayout.js new file mode 100644 index 0000000..ecdb38f --- /dev/null +++ b/frontend/agentic-seek-front/src/components/ResizableLayout.js @@ -0,0 +1,70 @@ +import React, { useState, useRef, useCallback } from "react"; +import "./ResizableLayout.css"; + +export const ResizableLayout = ({ children, initialLeftWidth = 50 }) => { + const [leftWidth, setLeftWidth] = useState(initialLeftWidth); + const [isDragging, setIsDragging] = useState(false); + const containerRef = useRef(null); + + const handleMouseDown = useCallback((e) => { + e.preventDefault(); + setIsDragging(true); + }, []); + + const handleMouseMove = useCallback( + (e) => { + if (!isDragging || !containerRef.current) return; + + const containerRect = containerRef.current.getBoundingClientRect(); + const newLeftWidth = + ((e.clientX - containerRect.left) / containerRect.width) * 100; + + // Constrain between 20% and 80% + const constrainedWidth = Math.max(20, Math.min(80, newLeftWidth)); + setLeftWidth(constrainedWidth); + }, + [isDragging] + ); + + const handleMouseUp = useCallback(() => { + setIsDragging(false); + }, []); + + React.useEffect(() => { + if (isDragging) { + document.addEventListener("mousemove", handleMouseMove); + document.addEventListener("mouseup", handleMouseUp); + document.body.style.cursor = "col-resize"; + document.body.style.userSelect = "none"; + } else { + document.removeEventListener("mousemove", handleMouseMove); + document.removeEventListener("mouseup", handleMouseUp); + document.body.style.cursor = ""; + document.body.style.userSelect = ""; + } + + return () => { + document.removeEventListener("mousemove", handleMouseMove); + document.removeEventListener("mouseup", handleMouseUp); + document.body.style.cursor = ""; + document.body.style.userSelect = ""; + }; + }, [isDragging, handleMouseMove, handleMouseUp]); + + return ( +
+
+ {children[0]} +
+
+
+
+
+ {children[1]} +
+
+ ); +}; diff --git a/frontend/agentic-seek-front/src/components/ThemeToggle.js b/frontend/agentic-seek-front/src/components/ThemeToggle.js new file mode 100644 index 0000000..25514af --- /dev/null +++ b/frontend/agentic-seek-front/src/components/ThemeToggle.js @@ -0,0 +1,34 @@ +import React from "react"; +import { useTheme } from "../contexts/ThemeContext"; + +export const ThemeToggle = () => { + const { isDark, toggleTheme } = useTheme(); + + return ( + + ); +}; diff --git a/frontend/agentic-seek-front/src/contexts/ThemeContext.js b/frontend/agentic-seek-front/src/contexts/ThemeContext.js new file mode 100644 index 0000000..5814683 --- /dev/null +++ b/frontend/agentic-seek-front/src/contexts/ThemeContext.js @@ -0,0 +1,34 @@ +import React, { createContext, useContext, useState, useEffect } from "react"; + +const ThemeContext = createContext(); + +export const ThemeProvider = ({ children }) => { + const [isDark, setIsDark] = useState(() => { + const saved = localStorage.getItem("theme"); + return saved ? saved === "dark" : true; // Default to dark + }); + + useEffect(() => { + localStorage.setItem("theme", isDark ? "dark" : "light"); + document.documentElement.setAttribute( + "data-theme", + isDark ? "dark" : "light" + ); + }, [isDark]); + + const toggleTheme = () => setIsDark(!isDark); + + return ( + + {children} + + ); +}; + +export const useTheme = () => { + const context = useContext(ThemeContext); + if (!context) { + throw new Error("useTheme must be used within ThemeProvider"); + } + return context; +}; diff --git a/frontend/agentic-seek-front/src/index.js b/frontend/agentic-seek-front/src/index.js index 5851d84..25ce631 100644 --- a/frontend/agentic-seek-front/src/index.js +++ b/frontend/agentic-seek-front/src/index.js @@ -1,10 +1,14 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import App from './App'; +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App"; +import { ThemeProvider } from "./contexts/ThemeContext"; +import "./styles/globals.css"; -const root = ReactDOM.createRoot(document.getElementById('root')); +const root = ReactDOM.createRoot(document.getElementById("root")); root.render( - + + + -); \ No newline at end of file +); diff --git a/frontend/agentic-seek-front/src/logo.png b/frontend/agentic-seek-front/src/logo.png new file mode 100644 index 0000000..58782a2 Binary files /dev/null and b/frontend/agentic-seek-front/src/logo.png differ diff --git a/frontend/agentic-seek-front/src/logo.svg b/frontend/agentic-seek-front/src/logo.svg deleted file mode 100644 index 9dfc1c0..0000000 --- a/frontend/agentic-seek-front/src/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/agentic-seek-front/src/styles/globals.css b/frontend/agentic-seek-front/src/styles/globals.css new file mode 100644 index 0000000..7ea7709 --- /dev/null +++ b/frontend/agentic-seek-front/src/styles/globals.css @@ -0,0 +1,217 @@ +:root { + --background: hsl(0 0% 100%); + --foreground: hsl(222.2 47.4% 11.2%); + --muted: hsl(210 40% 96.1%); + --muted-foreground: hsl(215.4 16.3% 46.9%); + --popover: hsl(0 0% 100%); + --popover-foreground: hsl(222.2 47.4% 11.2%); + --card: hsl(0 0% 100%); + --card-foreground: hsl(222.2 47.4% 11.2%); + --border: hsl(214.3 31.8% 91.4%); + --input: hsl(214.3 31.8% 91.4%); + --primary: hsl(222.2 47.4% 11.2%); + --primary-foreground: hsl(210 40% 98%); + --secondary: hsl(210 40% 96.1%); + --secondary-foreground: hsl(222.2 47.4% 11.2%); + --accent: hsl(210 40% 96.1%); + --accent-foreground: hsl(222.2 47.4% 11.2%); + --destructive: hsl(0 100% 50%); + --destructive-foreground: hsl(210 40% 98%); + --ring: hsl(215 20.2% 65.1%); + --radius: 0.5rem; +} + +.dark { + --background: hsl(224 71% 4%); + --foreground: hsl(213 31% 91%); + --muted: hsl(223 47% 11%); + --muted-foreground: hsl(215.4 16.3% 56.9%); + --popover: hsl(224 71% 4%); + --popover-foreground: hsl(215 20.2% 65.1%); + --card: hsl(224 71% 4%); + --card-foreground: hsl(213 31% 91%); + --border: hsl(216 34% 17%); + --input: hsl(216 34% 17%); + --primary: hsl(210 40% 98%); + --primary-foreground: hsl(222.2 47.4% 1.2%); + --secondary: hsl(222.2 47.4% 11.2%); + --secondary-foreground: hsl(210 40% 98%); + --accent: hsl(216 34% 17%); + --accent-foreground: hsl(210 40% 98%); + --destructive: hsl(0 63% 31%); + --destructive-foreground: hsl(210 40% 98%); + --ring: hsl(216 34% 17%); + --radius: 0.5rem; +} + +[data-theme="dark"] { + --background: #0a0a0a; + --foreground: #fafafa; + --card: #1a1a1a; + --card-foreground: #fafafa; + --popover: #1a1a1a; + --popover-foreground: #fafafa; + --primary: #fafafa; + --primary-foreground: #0a0a0a; + --secondary: #2a2a2a; + --secondary-foreground: #fafafa; + --muted: #1e1e1e; + --muted-foreground: #a1a1aa; + --accent: #6b7280; + --accent-foreground: #ffffff; + --destructive: #ef4444; + --destructive-foreground: #ffffff; + --border: #333333; + --input: #333333; + --ring: #6b7280; +} + +[data-theme="light"] { + --background: #ffffff; + --foreground: #0a0a0a; + --card: #ffffff; + --card-foreground: #0a0a0a; + --popover: #ffffff; + --popover-foreground: #0a0a0a; + --primary: #0a0a0a; + --primary-foreground: #ffffff; + --secondary: #f5f5f5; + --secondary-foreground: #0a0a0a; + --muted: #f5f5f5; + --muted-foreground: #737373; + --accent: #6b7280; + --accent-foreground: #ffffff; + --destructive: #ef4444; + --destructive-foreground: #ffffff; + --border: #e5e5e5; + --input: #e5e5e5; + --ring: #6b7280; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + background-color: var(--background); + color: var(--foreground); + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + sans-serif; + transition: background-color 0.3s ease, color 0.3s ease; + margin: 0; + padding: 0; + height: 100vh; + overflow: hidden; +} + +html, +body, +#root { + height: 100%; + overflow: hidden; +} + +.theme-toggle { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + min-width: 44px; + height: 44px; + padding: 0 12px; + border-radius: 12px; + border: 1px solid var(--border); + background: var(--card); + color: var(--foreground); + text-decoration: none; + font-size: 0.875rem; + font-weight: 500; + cursor: pointer; + transition: all 0.3s ease; + position: relative; + overflow: hidden; +} + +.theme-toggle::before { + content: ""; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient( + 90deg, + transparent, + rgba(255, 255, 255, 0.1), + transparent + ); + transition: left 0.5s ease; +} + +.theme-toggle:hover::before { + left: 100%; +} + +.theme-toggle:hover { + background: #24292e; + border-color: #24292e; + color: white; + transform: translateY(-2px); + box-shadow: 0 8px 25px rgba(36, 41, 46, 0.3); +} + +.github-link { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + min-width: 44px; + height: 44px; + padding: 0 12px; + border-radius: 12px; + border: 1px solid var(--border); + background: var(--card); + color: var(--foreground); + text-decoration: none; + font-size: 0.875rem; + font-weight: 500; + transition: all 0.3s ease; + position: relative; + overflow: hidden; +} + +.github-link::before { + content: ""; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient( + 90deg, + transparent, + rgba(255, 255, 255, 0.1), + transparent + ); + transition: left 0.5s ease; +} + +.github-link:hover::before { + left: 100%; +} + +.github-link:hover { + background: #24292e; + border-color: #24292e; + color: white; + transform: translateY(-2px); + box-shadow: 0 8px 25px rgba(36, 41, 46, 0.3); +} + +.header-actions { + display: flex; + align-items: center; + gap: 12px; +}