@@ -320,14 +335,26 @@ watch(() => route.name, (newPage) => {
-
-
@@ -419,35 +446,102 @@ watch(() => route.name, (newPage) => {
position: fixed;
bottom: 20px;
right: 20px;
- width: 56px;
- height: 56px;
- border-radius: 16px;
- background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
+ width: 58px;
+ height: 58px;
+ border-radius: 18px;
+ background: linear-gradient(145deg, #7c3aed 0%, #6366f1 50%, #8b5cf6 100%);
color: white;
- border: none;
+ border: 2px solid rgba(255, 255, 255, 0.15);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
- box-shadow: 0 8px 24px rgba(99, 102, 241, 0.4);
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+ box-shadow:
+ 0 4px 15px rgba(124, 58, 237, 0.4),
+ 0 8px 30px rgba(99, 102, 241, 0.3),
+ inset 0 1px 0 rgba(255, 255, 255, 0.2);
+ transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
z-index: 9998;
overflow: visible;
+ backdrop-filter: blur(10px);
+}
+
+.terminal-fab::before {
+ content: '';
+ position: absolute;
+ inset: -3px;
+ border-radius: 21px;
+ background: linear-gradient(145deg, rgba(139, 92, 246, 0.5), rgba(99, 102, 241, 0.2));
+ z-index: -1;
+ opacity: 0;
+ transition: opacity 0.3s ease;
}
.terminal-fab:hover {
- transform: scale(1.05);
- box-shadow: 0 12px 32px rgba(99, 102, 241, 0.5);
+ transform: translateY(-3px) scale(1.05);
+ box-shadow:
+ 0 8px 25px rgba(124, 58, 237, 0.5),
+ 0 15px 40px rgba(99, 102, 241, 0.35),
+ inset 0 1px 0 rgba(255, 255, 255, 0.25);
}
+.terminal-fab:hover::before {
+ opacity: 1;
+}
+
+/* Nucleo atom icon animation */
+.nucleo-icon {
+ animation: nucleo-orbit 8s linear infinite;
+ filter: drop-shadow(0 0 6px rgba(255, 255, 255, 0.6));
+}
+
+.nucleo-icon ellipse {
+ transform-origin: center;
+}
+
+/* Terminal active - Connected state */
.terminal-fab.active {
- background: #ef4444;
- box-shadow: 0 8px 24px rgba(239, 68, 68, 0.4);
- transform: rotate(90deg);
+ background: linear-gradient(145deg, #6366f1 0%, #4f46e5 50%, #7c3aed 100%);
+ border-color: rgba(16, 185, 129, 0.5);
+ box-shadow:
+ 0 4px 15px rgba(99, 102, 241, 0.4),
+ 0 8px 30px rgba(79, 70, 229, 0.3),
+ 0 0 20px rgba(16, 185, 129, 0.2),
+ inset 0 1px 0 rgba(255, 255, 255, 0.2);
+ transform: none;
}
.terminal-fab.active:hover {
- box-shadow: 0 12px 32px rgba(239, 68, 68, 0.5);
+ transform: translateY(-2px);
+ box-shadow:
+ 0 6px 20px rgba(99, 102, 241, 0.5),
+ 0 12px 35px rgba(79, 70, 229, 0.35),
+ 0 0 25px rgba(16, 185, 129, 0.3),
+ inset 0 1px 0 rgba(255, 255, 255, 0.25);
+}
+
+/* Connection indicator dot */
+.connection-dot {
+ position: absolute;
+ top: 8px;
+ right: 8px;
+ width: 10px;
+ height: 10px;
+ background: #10b981;
+ border-radius: 50%;
+ border: 2px solid rgba(255, 255, 255, 0.9);
+ box-shadow: 0 0 8px rgba(16, 185, 129, 0.8);
+ animation: connection-pulse 2s ease-in-out infinite;
+}
+
+/* Minimize icon */
+.minimize-icon {
+ opacity: 0.9;
+ transition: transform 0.2s ease;
+}
+
+.terminal-fab.active:hover .minimize-icon {
+ transform: translateY(2px);
}
/* Processing state (UserPromptSubmit → Stop) - Orange pulsing */
@@ -492,6 +586,25 @@ watch(() => route.name, (newPage) => {
animation: notification-bounce 0.5s ease-in-out 4 !important;
}
+/* Permission Request - HIGHEST PRIORITY - Red pulsing alert */
+.terminal-fab.permission {
+ background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%) !important;
+ box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.7) !important;
+ animation: permission-pulse 1s ease-in-out infinite !important;
+ transform: rotate(0deg) scale(1.1) !important;
+ z-index: 10000 !important;
+}
+
+.terminal-fab.permission:hover {
+ transform: scale(1.15) !important;
+}
+
+/* Permission icon animation */
+.permission-icon {
+ animation: permission-shake 0.5s ease-in-out infinite;
+ filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.8));
+}
+
/* Tool flash - Quick white flash */
.terminal-fab.tool-flash::after {
content: '';
@@ -643,6 +756,48 @@ watch(() => route.name, (newPage) => {
}
}
+@keyframes permission-pulse {
+ 0%, 100% {
+ box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.7), 0 8px 24px rgba(239, 68, 68, 0.5);
+ transform: scale(1.1);
+ }
+ 50% {
+ box-shadow: 0 0 0 15px rgba(239, 68, 68, 0), 0 8px 40px rgba(239, 68, 68, 0.8);
+ transform: scale(1.15);
+ }
+}
+
+@keyframes permission-shake {
+ 0%, 100% { transform: rotate(0deg); }
+ 25% { transform: rotate(-5deg); }
+ 75% { transform: rotate(5deg); }
+}
+
+@keyframes nucleo-orbit {
+ 0% {
+ transform: rotate(0deg);
+ filter: drop-shadow(0 0 6px rgba(255, 255, 255, 0.6));
+ }
+ 50% {
+ filter: drop-shadow(0 0 10px rgba(255, 255, 255, 0.9));
+ }
+ 100% {
+ transform: rotate(360deg);
+ filter: drop-shadow(0 0 6px rgba(255, 255, 255, 0.6));
+ }
+}
+
+@keyframes connection-pulse {
+ 0%, 100% {
+ box-shadow: 0 0 8px rgba(16, 185, 129, 0.8);
+ transform: scale(1);
+ }
+ 50% {
+ box-shadow: 0 0 12px rgba(16, 185, 129, 1), 0 0 20px rgba(16, 185, 129, 0.4);
+ transform: scale(1.1);
+ }
+}
+
/* Voice FAB */
.voice-fab {
position: fixed;
@@ -681,13 +836,20 @@ watch(() => route.name, (newPage) => {
.terminal-fab {
bottom: 16px;
right: 16px;
- width: 52px;
- height: 52px;
+ width: 54px;
+ height: 54px;
+ border-radius: 16px;
}
- .terminal-fab.active:not(.processing):not(.reading):not(.writing) {
- opacity: 0;
- pointer-events: none;
+ .terminal-fab::before {
+ border-radius: 19px;
+ }
+
+ .connection-dot {
+ width: 8px;
+ height: 8px;
+ top: 6px;
+ right: 6px;
}
.orbital-ring {
diff --git a/frontend/src/components/FloatingTerminal.vue b/frontend/src/components/FloatingTerminal.vue
index b82d310..09d91bf 100644
--- a/frontend/src/components/FloatingTerminal.vue
+++ b/frontend/src/components/FloatingTerminal.vue
@@ -498,10 +498,40 @@ defineExpose({
-
-
-
-
Terminal
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Nucleo
connect
@@ -565,11 +595,28 @@ defineExpose({
.left {
display: flex;
align-items: center;
- gap: 5px;
+ gap: 6px;
color: #222;
font: 500 10px/1 system-ui, sans-serif;
}
+.nucleo-logo {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ filter: drop-shadow(0 1px 2px rgba(99, 102, 241, 0.3));
+}
+
+.nucleo-name {
+ font-weight: 600;
+ font-size: 11px;
+ background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+ letter-spacing: 0.5px;
+}
+
.dot {
width: 5px; height: 5px;
border-radius: 50%;
diff --git a/server/routes/claude-status.ts b/server/routes/claude-status.ts
index 0b307c5..04e4bd8 100644
--- a/server/routes/claude-status.ts
+++ b/server/routes/claude-status.ts
@@ -3,16 +3,17 @@ import { PORT_TERMINAL } from '../config'
type ClaudeStatus =
| 'idle'
- | 'processing' // UserPromptSubmit - Claude is processing user input
- | 'toolUse' // PreToolUse - Using a tool (generic)
- | 'toolDone' // PostToolUse - Tool finished
- | 'reading' // PreToolUse(Read/Glob/Grep) - Reading files
- | 'writing' // PreToolUse(Edit/Write) - Writing files
- | 'sessionStart' // SessionStart - Session just started
- | 'subagentStart' // SubagentStart - Spawning subagent
- | 'subagentStop' // SubagentStop - Subagent finished
- | 'notification' // Notification - Claude sent notification
- | 'thinking' // Legacy support
+ | 'processing' // UserPromptSubmit - Claude is processing user input
+ | 'toolUse' // PreToolUse - Using a tool (generic)
+ | 'toolDone' // PostToolUse - Tool finished
+ | 'reading' // PreToolUse(Read/Glob/Grep) - Reading files
+ | 'writing' // PreToolUse(Edit/Write) - Writing files
+ | 'sessionStart' // SessionStart - Session just started
+ | 'subagentStart' // SubagentStart - Spawning subagent
+ | 'subagentStop' // SubagentStop - Subagent finished
+ | 'notification' // Notification - Claude sent notification
+ | 'permissionRequest' // PermissionRequest - Waiting for user permission
+ | 'thinking' // Legacy support
interface ClaudeStatusPayload {
status: ClaudeStatus
@@ -27,7 +28,8 @@ export async function handleClaudeStatus(req: Request): Promise
const validStatuses: ClaudeStatus[] = [
'idle', 'processing', 'toolUse', 'toolDone', 'reading', 'writing',
- 'sessionStart', 'subagentStart', 'subagentStop', 'notification', 'thinking'
+ 'sessionStart', 'subagentStart', 'subagentStop', 'notification',
+ 'permissionRequest', 'thinking'
]
if (!body.status || !validStatuses.includes(body.status)) {
return errorResponse(`Invalid status. Must be one of: ${validStatuses.join(', ')}`, 400)
diff --git a/server/services/terminal.ts b/server/services/terminal.ts
index 3bda363..50e2409 100644
--- a/server/services/terminal.ts
+++ b/server/services/terminal.ts
@@ -214,7 +214,7 @@ export function startTerminalServer() {
}
// Claude status types
-type ClaudeStatus = 'idle' | 'processing' | 'toolUse' | 'toolDone' | 'reading' | 'writing' | 'sessionStart' | 'subagentStart' | 'subagentStop' | 'notification' | 'thinking'
+type ClaudeStatus = 'idle' | 'processing' | 'toolUse' | 'toolDone' | 'reading' | 'writing' | 'sessionStart' | 'subagentStart' | 'subagentStop' | 'notification' | 'permissionRequest' | 'thinking'
// Broadcast Claude status to ALL clients across ALL sessions
export function broadcastClaudeStatus(status: ClaudeStatus, tool?: string) {