Phase 6: Terminal aesthetic refinements and notifications

Changes:
- Added blinking terminal cursor animation
- Smooth hover effects for execution/worker/workflow items
- Hover animation: background highlight + border expand + slide
- Loading pulse animation for loading states
- Slide-in animation for log entries
- Terminal beep sound using Web Audio API (different tones for success/error)
- Real-time terminal notifications for command completion
- Toast-style notifications with green glow effects
- Auto-dismiss after 3 seconds with fade-out
- Visual and audio feedback for user actions

Sound features:
- 800Hz tone for success (higher pitch)
- 200Hz tone for errors (lower pitch)
- 440Hz tone for info (standard A note)
- 100ms duration, exponential fade-out
- Graceful fallback if Web Audio API not supported

Notification features:
- Fixed position top-right
- Terminal-themed styling with glow
- Color-coded: green for success, red for errors
- Icons: ✓ success, ✗ error, ℹ info
- Smooth animations (slide-in, fade-out)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-07 22:52:51 -05:00
parent 9f972182b2
commit c6e3e5704e

View File

@@ -690,6 +690,66 @@
min-width: 120px; min-width: 120px;
font-weight: bold; font-weight: bold;
} }
/* Terminal Cursor Blink */
@keyframes cursor-blink {
0%, 49% { opacity: 1; }
50%, 100% { opacity: 0; }
}
.terminal-cursor::after {
content: '▋';
animation: cursor-blink 1s step-end infinite;
color: var(--terminal-green);
}
/* Hover effects for execution items */
.execution-item {
transition: all 0.2s ease;
cursor: pointer;
}
.execution-item:hover {
background: #001a00;
border-left-width: 5px;
transform: translateX(3px);
}
.worker-item:hover {
background: #001a00;
border-left-width: 5px;
}
.workflow-item:hover {
background: #001a00;
border-left-width: 5px;
}
/* Loading pulse effect */
.loading {
animation: loading-pulse 1.5s ease-in-out infinite;
}
@keyframes loading-pulse {
0%, 100% { opacity: 0.6; }
50% { opacity: 1; }
}
/* Success/Error message animations */
@keyframes slide-in {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.log-entry {
animation: slide-in 0.3s ease-out;
}
</style> </style>
</head> </head>
<body> <body>
@@ -1498,6 +1558,77 @@
loadExecutions(); loadExecutions();
} }
// Terminal beep sound (Web Audio API)
function terminalBeep(type = 'success') {
try {
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
// Different tones for different events
if (type === 'success') {
oscillator.frequency.value = 800; // Higher pitch for success
} else if (type === 'error') {
oscillator.frequency.value = 200; // Lower pitch for errors
} else {
oscillator.frequency.value = 440; // Standard A note
}
oscillator.type = 'sine';
gainNode.gain.setValueAtTime(0.1, audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.1);
oscillator.start(audioContext.currentTime);
oscillator.stop(audioContext.currentTime + 0.1);
} catch (error) {
// Silently fail if Web Audio API not supported
}
}
// Show terminal notification
function showTerminalNotification(message, type = 'info') {
const notification = document.createElement('div');
notification.style.cssText = `
position: fixed;
top: 80px;
right: 20px;
background: #001a00;
border: 2px solid var(--terminal-green);
color: var(--terminal-green);
padding: 15px 20px;
font-family: var(--font-mono);
z-index: 10000;
animation: slide-in 0.3s ease-out;
box-shadow: 0 0 20px rgba(0, 255, 65, 0.3);
`;
if (type === 'error') {
notification.style.borderColor = '#ff4444';
notification.style.color = '#ff4444';
message = '✗ ' + message;
} else if (type === 'success') {
message = '✓ ' + message;
} else {
message = ' ' + message;
}
notification.textContent = message;
document.body.appendChild(notification);
// Play beep
terminalBeep(type);
// Remove after 3 seconds
setTimeout(() => {
notification.style.opacity = '0';
notification.style.transition = 'opacity 0.5s';
setTimeout(() => notification.remove(), 500);
}, 3000);
}
function connectWebSocket() { function connectWebSocket() {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
ws = new WebSocket(`${protocol}//${window.location.host}`); ws = new WebSocket(`${protocol}//${window.location.host}`);
@@ -1516,6 +1647,13 @@
console.log(`Error: ${data.stderr}`); console.log(`Error: ${data.stderr}`);
} }
// Show terminal notification
if (data.success) {
showTerminalNotification('Command completed successfully', 'success');
} else {
showTerminalNotification('Command execution failed', 'error');
}
// If viewing execution details, refresh that specific execution // If viewing execution details, refresh that specific execution
const executionModal = document.getElementById('viewExecutionModal'); const executionModal = document.getElementById('viewExecutionModal');
if (executionModal && executionModal.classList.contains('show')) { if (executionModal && executionModal.classList.contains('show')) {