mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-21 13:55:54 +00:00
Add professional light mode with theme toggle (#314)
This commit is contained in:
parent
0a50ccc65e
commit
e3d0d9655b
44 changed files with 1373 additions and 875 deletions
|
|
@ -1,8 +1,8 @@
|
|||
// VibeTunnel Version Configuration
|
||||
// This file contains the version and build number for the app
|
||||
|
||||
MARKETING_VERSION = 1.0.0-beta.9
|
||||
CURRENT_PROJECT_VERSION = 173
|
||||
MARKETING_VERSION = 1.0.0-beta.10
|
||||
CURRENT_PROJECT_VERSION = 180
|
||||
|
||||
// Domain and GitHub configuration
|
||||
APP_DOMAIN = vibetunnel.sh
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vibetunnel/vibetunnel-cli",
|
||||
"version": "1.0.0-beta.9",
|
||||
"version": "1.0.0-beta.10",
|
||||
"description": "Web frontend for terminal multiplexer",
|
||||
"main": "dist/server.js",
|
||||
"bin": {
|
||||
|
|
|
|||
|
|
@ -496,15 +496,9 @@ export class VibeTunnelApp extends LitElement {
|
|||
return;
|
||||
}
|
||||
|
||||
// Add class to prevent flicker when closing modal
|
||||
document.body.classList.add('modal-closing');
|
||||
// Simply close the modal without animation
|
||||
this.showCreateModal = false;
|
||||
|
||||
// Remove the class after a short delay
|
||||
setTimeout(() => {
|
||||
document.body.classList.remove('modal-closing');
|
||||
}, 300);
|
||||
|
||||
// Check if this was a terminal spawn (not a web session)
|
||||
if (message?.includes('Terminal spawned successfully')) {
|
||||
// Don't try to switch to the session - it's running in a terminal window
|
||||
|
|
@ -684,40 +678,9 @@ export class VibeTunnelApp extends LitElement {
|
|||
}
|
||||
|
||||
private handleCreateModalClose() {
|
||||
// Immediately hide the modal
|
||||
// Simply close the modal without animation
|
||||
this.showCreateModal = false;
|
||||
|
||||
// Skip animation if we're in session detail view
|
||||
const isInSessionDetailView = this.currentView === 'session';
|
||||
|
||||
// Then apply view transition if supported (non-blocking)
|
||||
if (
|
||||
!isInSessionDetailView &&
|
||||
'startViewTransition' in document &&
|
||||
typeof document.startViewTransition === 'function'
|
||||
) {
|
||||
// Add a class to prevent flicker during transition
|
||||
document.body.classList.add('modal-closing');
|
||||
// Set data attribute to indicate transition is starting
|
||||
document.documentElement.setAttribute('data-view-transition', 'active');
|
||||
|
||||
try {
|
||||
const transition = document.startViewTransition(() => {
|
||||
// Force a re-render
|
||||
this.requestUpdate();
|
||||
});
|
||||
|
||||
// Clean up the class and attribute after transition
|
||||
transition.finished.finally(() => {
|
||||
document.body.classList.remove('modal-closing');
|
||||
document.documentElement.removeAttribute('data-view-transition');
|
||||
});
|
||||
} catch (_error) {
|
||||
// If view transition fails, clean up
|
||||
document.body.classList.remove('modal-closing');
|
||||
document.documentElement.removeAttribute('data-view-transition');
|
||||
}
|
||||
}
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private cleanupSessionViewStream(): void {
|
||||
|
|
@ -1236,7 +1199,7 @@ export class VibeTunnelApp extends LitElement {
|
|||
return 'w-full min-h-screen flex flex-col';
|
||||
}
|
||||
|
||||
const baseClasses = 'bg-dark-bg-secondary border-r border-dark-border flex flex-col';
|
||||
const baseClasses = 'bg-secondary border-r border-base flex flex-col';
|
||||
const isMobile = this.mediaState.isMobile;
|
||||
// Only apply transition class when animations are ready (not during initial load)
|
||||
const transitionClass = this.sidebarAnimationReady && !isMobile ? 'sidebar-transition' : '';
|
||||
|
|
@ -1359,7 +1322,7 @@ export class VibeTunnelApp extends LitElement {
|
|||
? html`
|
||||
<div class="fixed top-4 right-4" style="z-index: ${Z_INDEX.MODAL_BACKDROP};">
|
||||
<div
|
||||
class="bg-status-error text-dark-bg px-4 py-2 rounded shadow-lg font-mono text-sm"
|
||||
class="bg-status-error text-bg-elevated px-4 py-2 rounded shadow-lg font-mono text-sm"
|
||||
>
|
||||
${this.errorMessage}
|
||||
<button
|
||||
|
|
@ -1370,7 +1333,7 @@ export class VibeTunnelApp extends LitElement {
|
|||
}
|
||||
this.errorMessage = '';
|
||||
}}
|
||||
class="ml-2 text-dark-bg hover:text-dark-text"
|
||||
class="ml-2 text-bg-elevated hover:text-text-muted"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
|
|
@ -1384,7 +1347,7 @@ export class VibeTunnelApp extends LitElement {
|
|||
? html`
|
||||
<div class="fixed top-4 right-4" style="z-index: ${Z_INDEX.MODAL_BACKDROP};">
|
||||
<div
|
||||
class="bg-status-success text-dark-bg px-4 py-2 rounded shadow-lg font-mono text-sm"
|
||||
class="bg-status-success text-bg-elevated px-4 py-2 rounded shadow-lg font-mono text-sm"
|
||||
>
|
||||
${this.successMessage}
|
||||
<button
|
||||
|
|
@ -1395,7 +1358,7 @@ export class VibeTunnelApp extends LitElement {
|
|||
}
|
||||
this.successMessage = '';
|
||||
}}
|
||||
class="ml-2 text-dark-bg hover:text-dark-text"
|
||||
class="ml-2 text-bg-elevated hover:text-text-muted"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
|
|
@ -1437,7 +1400,7 @@ export class VibeTunnelApp extends LitElement {
|
|||
<div
|
||||
class="fixed inset-0 sm:hidden transition-all ${
|
||||
this.isInSidebarDismissMode
|
||||
? 'bg-black bg-opacity-50 backdrop-blur-sm'
|
||||
? 'bg-bg bg-opacity-50 backdrop-blur-sm'
|
||||
: 'bg-transparent pointer-events-none'
|
||||
}"
|
||||
style="z-index: ${Z_INDEX.MOBILE_OVERLAY}; transition-duration: ${TRANSITIONS.MOBILE_SLIDE}ms;"
|
||||
|
|
@ -1465,7 +1428,7 @@ export class VibeTunnelApp extends LitElement {
|
|||
@navigate-to-list=${this.handleNavigateToList}
|
||||
@toggle-sidebar=${this.handleToggleSidebar}
|
||||
></app-header>
|
||||
<div class="${this.showSplitView ? 'flex-1 overflow-y-auto' : 'flex-1'} bg-dark-bg-secondary">
|
||||
<div class="${this.showSplitView ? 'flex-1 overflow-y-auto' : 'flex-1'} bg-secondary">
|
||||
<session-list
|
||||
.sessions=${this.sessions}
|
||||
.loading=${this.loading}
|
||||
|
|
@ -1490,7 +1453,7 @@ export class VibeTunnelApp extends LitElement {
|
|||
this.shouldShowResizeHandle
|
||||
? html`
|
||||
<div
|
||||
class="w-1 bg-dark-border hover:bg-accent-green cursor-ew-resize transition-colors ${
|
||||
class="w-1 bg-border hover:bg-accent-green cursor-ew-resize transition-colors ${
|
||||
this.isResizing ? 'bg-accent-green' : ''
|
||||
}"
|
||||
style="transition-duration: ${TRANSITIONS.RESIZE_HANDLE}ms;"
|
||||
|
|
@ -1562,8 +1525,8 @@ export class VibeTunnelApp extends LitElement {
|
|||
${
|
||||
this.showLogLink
|
||||
? html`
|
||||
<div class="fixed ${this.getLogButtonPosition()} right-4 text-dark-text-muted text-xs font-mono bg-dark-bg-secondary px-3 py-1.5 rounded-lg border border-dark-border shadow-sm transition-all duration-200" style="z-index: ${Z_INDEX.LOG_BUTTON};">
|
||||
<a href="/logs" class="hover:text-dark-text transition-colors">Logs</a>
|
||||
<div class="fixed ${this.getLogButtonPosition()} right-4 text-muted text-xs font-mono bg-secondary px-3 py-1.5 rounded-lg border border-base shadow-sm transition-all duration-200" style="z-index: ${Z_INDEX.LOG_BUTTON};">
|
||||
<a href="/logs" class="hover:text-text transition-colors">Logs</a>
|
||||
<span class="ml-2 opacity-75">v${VERSION}</span>
|
||||
</div>
|
||||
`
|
||||
|
|
|
|||
|
|
@ -73,8 +73,33 @@
|
|||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Initialize theme immediately to prevent flash -->
|
||||
<script>
|
||||
(function() {
|
||||
// Load saved theme preference or use system preference
|
||||
const saved = localStorage.getItem('vibetunnel-theme');
|
||||
const theme = saved || 'system';
|
||||
|
||||
// Apply theme immediately
|
||||
if (theme === 'system') {
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
document.documentElement.setAttribute('data-theme', prefersDark ? 'dark' : 'light');
|
||||
} else {
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
}
|
||||
|
||||
// Apply immediate background to prevent flash
|
||||
const effectiveTheme = theme === 'system'
|
||||
? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
|
||||
: theme;
|
||||
|
||||
// Set initial background color
|
||||
document.documentElement.style.backgroundColor = effectiveTheme === 'dark' ? '#0a0a0a' : '#fafafa';
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
<body class="m-0 p-0" style="background: black">
|
||||
<body class="m-0 p-0 bg-bg text-text">
|
||||
<vibetunnel-app></vibetunnel-app>
|
||||
|
||||
<!-- Mobile viewport height fix -->
|
||||
|
|
|
|||
|
|
@ -39,8 +39,33 @@
|
|||
height: 100vh;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Initialize theme immediately to prevent flash -->
|
||||
<script>
|
||||
(function() {
|
||||
// Load saved theme preference or use system preference
|
||||
const saved = localStorage.getItem('vibetunnel-theme');
|
||||
const theme = saved || 'system';
|
||||
|
||||
// Apply theme immediately
|
||||
if (theme === 'system') {
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
document.documentElement.setAttribute('data-theme', prefersDark ? 'dark' : 'light');
|
||||
} else {
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
}
|
||||
|
||||
// Apply immediate background to prevent flash
|
||||
const effectiveTheme = theme === 'system'
|
||||
? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
|
||||
: theme;
|
||||
|
||||
// Set initial background color
|
||||
document.documentElement.style.backgroundColor = effectiveTheme === 'dark' ? '#0a0a0a' : '#fafafa';
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
<body class="m-0 p-0" style="background: black">
|
||||
<body class="m-0 p-0 bg-bg text-text">
|
||||
<log-viewer></log-viewer>
|
||||
|
||||
<script type="module" src="bundle/client-bundle.js"></script>
|
||||
|
|
|
|||
|
|
@ -162,7 +162,7 @@ export class AuthLogin extends LitElement {
|
|||
<div class="auth-container">
|
||||
<!-- Settings button in top right corner -->
|
||||
<button
|
||||
class="absolute top-4 right-4 p-2 text-dark-text-muted hover:text-dark-text transition-colors"
|
||||
class="absolute top-4 right-4 p-2 text-muted hover:text-primary transition-colors"
|
||||
@click=${this.handleOpenSettings}
|
||||
title="Settings"
|
||||
>
|
||||
|
|
@ -176,7 +176,7 @@ export class AuthLogin extends LitElement {
|
|||
<div class="flex flex-col items-center gap-2 sm:gap-3 mb-4 sm:mb-8">
|
||||
<terminal-icon
|
||||
size="${this.isMobile ? '48' : '56'}"
|
||||
style="filter: drop-shadow(0 0 15px rgba(16, 185, 129, 0.4));"
|
||||
style="filter: drop-shadow(0 0 15px rgb(var(--color-primary) / 0.4));"
|
||||
></terminal-icon>
|
||||
<h2 class="auth-title text-2xl sm:text-3xl mt-1 sm:mt-2">VibeTunnel</h2>
|
||||
<p class="auth-subtitle text-xs sm:text-sm">Please authenticate to continue</p>
|
||||
|
|
@ -187,7 +187,7 @@ export class AuthLogin extends LitElement {
|
|||
this.error
|
||||
? html`
|
||||
<div
|
||||
class="bg-status-error text-dark-bg px-3 py-1.5 rounded mb-3 font-mono text-xs sm:text-sm"
|
||||
class="bg-status-error text-base px-3 py-1.5 rounded mb-3 font-mono text-xs sm:text-sm"
|
||||
data-testid="error-message"
|
||||
>
|
||||
${this.error}
|
||||
|
|
@ -195,7 +195,7 @@ export class AuthLogin extends LitElement {
|
|||
@click=${() => {
|
||||
this.error = '';
|
||||
}}
|
||||
class="ml-2 text-dark-bg hover:text-dark-text"
|
||||
class="ml-2 text-base hover:text-primary"
|
||||
data-testid="error-close"
|
||||
>
|
||||
✕
|
||||
|
|
@ -208,14 +208,14 @@ export class AuthLogin extends LitElement {
|
|||
this.success
|
||||
? html`
|
||||
<div
|
||||
class="bg-status-success text-dark-bg px-3 py-1.5 rounded mb-3 font-mono text-xs sm:text-sm"
|
||||
class="bg-status-success text-base px-3 py-1.5 rounded mb-3 font-mono text-xs sm:text-sm"
|
||||
>
|
||||
${this.success}
|
||||
<button
|
||||
@click=${() => {
|
||||
this.success = '';
|
||||
}}
|
||||
class="ml-2 text-dark-bg hover:text-dark-text"
|
||||
class="ml-2 text-base hover:text-primary"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
|
|
@ -233,7 +233,7 @@ export class AuthLogin extends LitElement {
|
|||
<div class="flex flex-col items-center mb-4 sm:mb-6">
|
||||
<div
|
||||
class="w-24 h-24 sm:w-28 sm:h-28 rounded-full mb-3 sm:mb-4 overflow-hidden"
|
||||
style="box-shadow: 0 0 25px rgba(16, 185, 129, 0.3);"
|
||||
style="box-shadow: 0 0 25px rgb(var(--color-primary) / 0.3);"
|
||||
>
|
||||
${
|
||||
this.userAvatar
|
||||
|
|
@ -248,10 +248,10 @@ export class AuthLogin extends LitElement {
|
|||
`
|
||||
: html`
|
||||
<div
|
||||
class="w-full h-full bg-dark-bg-secondary flex items-center justify-center"
|
||||
class="w-full h-full bg-secondary flex items-center justify-center"
|
||||
>
|
||||
<svg
|
||||
class="w-12 h-12 sm:w-14 sm:h-14 text-dark-text-muted"
|
||||
class="w-12 h-12 sm:w-14 sm:h-14 text-muted"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
|
|
@ -261,7 +261,7 @@ export class AuthLogin extends LitElement {
|
|||
`
|
||||
}
|
||||
</div>
|
||||
<p class="text-dark-text text-base sm:text-lg font-medium">
|
||||
<p class="text-primary text-base sm:text-lg font-medium">
|
||||
Welcome back, ${this.currentUserId || '...'}
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -300,7 +300,7 @@ export class AuthLogin extends LitElement {
|
|||
<div class="ssh-key-item p-6 sm:p-8">
|
||||
<div class="flex flex-col items-center mb-4 sm:mb-6">
|
||||
<div
|
||||
class="w-16 h-16 sm:w-20 sm:h-20 rounded-full mb-2 sm:mb-3 overflow-hidden border-2 border-dark-border"
|
||||
class="w-16 h-16 sm:w-20 sm:h-20 rounded-full mb-2 sm:mb-3 overflow-hidden border-2 border-base"
|
||||
>
|
||||
${
|
||||
this.userAvatar
|
||||
|
|
@ -315,10 +315,10 @@ export class AuthLogin extends LitElement {
|
|||
`
|
||||
: html`
|
||||
<div
|
||||
class="w-full h-full bg-dark-bg-secondary flex items-center justify-center"
|
||||
class="w-full h-full bg-secondary flex items-center justify-center"
|
||||
>
|
||||
<svg
|
||||
class="w-8 h-8 sm:w-10 sm:h-10 text-dark-text-muted"
|
||||
class="w-8 h-8 sm:w-10 sm:h-10 text-muted"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
|
|
@ -328,14 +328,14 @@ export class AuthLogin extends LitElement {
|
|||
`
|
||||
}
|
||||
</div>
|
||||
<p class="text-dark-text text-xs sm:text-sm">
|
||||
<p class="text-primary text-xs sm:text-sm">
|
||||
${
|
||||
this.currentUserId
|
||||
? `Welcome back, ${this.currentUserId}`
|
||||
: 'Please authenticate to continue'
|
||||
}
|
||||
</p>
|
||||
<p class="text-dark-text-muted text-xs mt-1 sm:mt-2">
|
||||
<p class="text-muted text-xs mt-1 sm:mt-2">
|
||||
SSH key authentication required
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -374,11 +374,11 @@ export class AuthLogin extends LitElement {
|
|||
</div>
|
||||
|
||||
<div class="space-y-3">
|
||||
<div class="bg-dark-bg border border-dark-border rounded p-3">
|
||||
<p class="text-dark-text-muted text-xs mb-2">
|
||||
<div class="bg-base border border-base rounded p-3">
|
||||
<p class="text-muted text-xs mb-2">
|
||||
Generate SSH keys for browser-based authentication
|
||||
</p>
|
||||
<p class="text-dark-text-muted text-xs">
|
||||
<p class="text-muted text-xs">
|
||||
💡 SSH keys work in both browser and terminal
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -33,13 +33,13 @@ export class AuthQuickKeys extends LitElement {
|
|||
|
||||
render() {
|
||||
return html`
|
||||
<div class="quick-keys-bar bg-dark-bg-secondary border-t border-dark-border p-2">
|
||||
<div class="quick-keys-bar bg-secondary border-t border-base p-2">
|
||||
<div class="flex gap-1 overflow-x-auto scrollbar-hide">
|
||||
${QUICK_KEYS.map(
|
||||
({ key, label }) => html`
|
||||
<button
|
||||
type="button"
|
||||
class="quick-key-btn px-3 py-1.5 bg-dark-bg-tertiary text-dark-text text-xs font-mono rounded border border-dark-border hover:bg-dark-surface hover:border-primary transition-all whitespace-nowrap flex-shrink-0"
|
||||
class="quick-key-btn px-3 py-1.5 bg-tertiary text-primary text-xs font-mono rounded border border-base hover:bg-surface hover:border-primary transition-all whitespace-nowrap flex-shrink-0"
|
||||
@click=${() => this.handleKeyPress(key)}
|
||||
>
|
||||
${label}
|
||||
|
|
|
|||
|
|
@ -15,26 +15,26 @@ export class FileBrowserFAB extends LitElement {
|
|||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 50%;
|
||||
background: #007acc;
|
||||
color: white;
|
||||
background: rgb(var(--color-primary));
|
||||
color: rgb(var(--color-text-bright));
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
|
||||
box-shadow: 0 4px 8px rgb(var(--color-bg-base) / 0.3);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.fab:hover {
|
||||
background: #005a9e;
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.4);
|
||||
background: rgb(var(--color-primary-hover));
|
||||
box-shadow: 0 6px 12px rgb(var(--color-bg-base) / 0.4);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.fab:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
box-shadow: 0 2px 4px rgb(var(--color-bg-base) / 0.3);
|
||||
}
|
||||
|
||||
.icon {
|
||||
|
|
@ -46,8 +46,8 @@ export class FileBrowserFAB extends LitElement {
|
|||
bottom: 100%;
|
||||
right: 0;
|
||||
margin-bottom: 8px;
|
||||
background: #333;
|
||||
color: white;
|
||||
background: rgb(var(--color-surface));
|
||||
color: rgb(var(--color-text));
|
||||
padding: 6px 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
|
|
|
|||
|
|
@ -392,7 +392,7 @@ export class FileBrowser extends LitElement {
|
|||
private renderPreview() {
|
||||
if (this.previewLoading) {
|
||||
return html`
|
||||
<div class="flex items-center justify-center h-full text-dark-text-muted">
|
||||
<div class="flex items-center justify-center h-full text-muted">
|
||||
Loading preview...
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -404,7 +404,7 @@ export class FileBrowser extends LitElement {
|
|||
|
||||
if (!this.preview) {
|
||||
return html`
|
||||
<div class="flex flex-col items-center justify-center h-full text-dark-text-muted">
|
||||
<div class="flex flex-col items-center justify-center h-full text-muted">
|
||||
${UIIcons.preview}
|
||||
<div>Select a file to preview</div>
|
||||
</div>
|
||||
|
|
@ -438,11 +438,11 @@ export class FileBrowser extends LitElement {
|
|||
|
||||
case 'binary':
|
||||
return html`
|
||||
<div class="flex flex-col items-center justify-center h-full text-dark-text-muted">
|
||||
<div class="flex flex-col items-center justify-center h-full text-muted">
|
||||
${UIIcons.binary}
|
||||
<div class="text-lg mb-2">Binary File</div>
|
||||
<div class="text-sm">${this.preview.humanSize || `${this.preview.size} bytes`}</div>
|
||||
<div class="text-sm text-dark-text-muted mt-2">
|
||||
<div class="text-sm text-muted mt-2">
|
||||
${this.preview.mimeType || 'Unknown type'}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -454,7 +454,7 @@ export class FileBrowser extends LitElement {
|
|||
// For new files (added or untracked), we might not have a diff but we have diffContent
|
||||
if (!this.diffContent && (!this.diff || !this.diff.diff)) {
|
||||
return html`
|
||||
<div class="flex items-center justify-center h-full text-dark-text-muted">
|
||||
<div class="flex items-center justify-center h-full text-muted">
|
||||
No changes in this file
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -483,9 +483,9 @@ export class FileBrowser extends LitElement {
|
|||
return html`
|
||||
<div class="overflow-auto h-full p-4 font-mono text-xs">
|
||||
${lines.map((line) => {
|
||||
let className = 'text-dark-text-muted';
|
||||
if (line.startsWith('+')) className = 'text-status-success bg-green-900/20';
|
||||
else if (line.startsWith('-')) className = 'text-status-error bg-red-900/20';
|
||||
let className = 'text-muted';
|
||||
if (line.startsWith('+')) className = 'text-status-success bg-status-success/10';
|
||||
else if (line.startsWith('-')) className = 'text-status-error bg-status-error/10';
|
||||
else if (line.startsWith('@@')) className = 'text-status-info font-semibold';
|
||||
|
||||
return html`<div class="whitespace-pre ${className}">${line}</div>`;
|
||||
|
|
@ -500,12 +500,12 @@ export class FileBrowser extends LitElement {
|
|||
}
|
||||
|
||||
return html`
|
||||
<div class="fixed inset-0 bg-black/80 backdrop-blur-sm flex items-center justify-center" style="z-index: ${Z_INDEX.FILE_BROWSER};" @click=${this.handleCancel}>
|
||||
<div class="fixed inset-0 bg-dark-bg flex flex-col" style="z-index: ${Z_INDEX.FILE_BROWSER};" @click=${(e: Event) => e.stopPropagation()}>
|
||||
<div class="fixed inset-0 bg-bg/80 backdrop-blur-sm flex items-center justify-center" style="z-index: ${Z_INDEX.FILE_BROWSER};" @click=${this.handleCancel}>
|
||||
<div class="fixed inset-0 bg-bg flex flex-col" style="z-index: ${Z_INDEX.FILE_BROWSER};" @click=${(e: Event) => e.stopPropagation()}>
|
||||
${
|
||||
this.isMobile && this.mobileView === 'preview'
|
||||
? html`
|
||||
<div class="absolute top-1/2 left-2 -translate-y-1/2 text-dark-text-muted opacity-50">
|
||||
<div class="absolute top-1/2 left-2 -translate-y-1/2 text-muted opacity-50">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
|
|
@ -524,12 +524,12 @@ export class FileBrowser extends LitElement {
|
|||
>
|
||||
<!-- Compact Header (like session-view) -->
|
||||
<div
|
||||
class="flex items-center justify-between px-3 py-2 border-b border-dark-border text-sm min-w-0 bg-dark-bg-secondary"
|
||||
class="flex items-center justify-between px-3 py-2 border-b border-base text-sm min-w-0 bg-secondary"
|
||||
style="padding-top: max(0.5rem, env(safe-area-inset-top)); padding-left: max(0.75rem, env(safe-area-inset-left)); padding-right: max(0.75rem, env(safe-area-inset-right));"
|
||||
>
|
||||
<div class="flex items-center gap-3 min-w-0 flex-1">
|
||||
<button
|
||||
class="text-dark-text-muted hover:text-dark-text font-mono text-xs px-2 py-1 flex-shrink-0 transition-colors flex items-center gap-1"
|
||||
class="text-muted hover:text-primary font-mono text-xs px-2 py-1 flex-shrink-0 transition-colors flex items-center gap-1"
|
||||
@click=${this.handleCancel}
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
|
|
@ -542,7 +542,7 @@ export class FileBrowser extends LitElement {
|
|||
</svg>
|
||||
<span>Back</span>
|
||||
</button>
|
||||
<div class="text-dark-text min-w-0 flex-1 overflow-hidden">
|
||||
<div class="text-primary min-w-0 flex-1 overflow-hidden">
|
||||
${
|
||||
this.editingPath
|
||||
? html`
|
||||
|
|
@ -553,13 +553,13 @@ export class FileBrowser extends LitElement {
|
|||
@input=${this.handlePathInput}
|
||||
@keydown=${this.handlePathKeyDown}
|
||||
@blur=${this.handlePathBlur}
|
||||
class="bg-dark-bg border border-dark-border rounded px-2 py-1 text-status-info text-xs sm:text-sm font-mono w-full min-w-0 focus:outline-none focus:border-primary"
|
||||
class="bg-bg border border-base rounded px-2 py-1 text-status-info text-xs sm:text-sm font-mono w-full min-w-0 focus:outline-none focus:border-primary"
|
||||
placeholder="Enter path and press Enter"
|
||||
/>
|
||||
`
|
||||
: html`
|
||||
<div
|
||||
class="text-blue-400 text-xs sm:text-sm overflow-hidden text-ellipsis whitespace-nowrap font-mono cursor-pointer hover:bg-dark-bg-lighter rounded px-1 py-1 -mx-1"
|
||||
class="text-status-info text-xs sm:text-sm overflow-hidden text-ellipsis whitespace-nowrap font-mono cursor-pointer hover:bg-light rounded px-1 py-1 -mx-1"
|
||||
title="${
|
||||
this.currentFullPath || this.currentPath || 'File Browser'
|
||||
} (click to edit)"
|
||||
|
|
@ -576,7 +576,7 @@ export class FileBrowser extends LitElement {
|
|||
this.errorMessage
|
||||
? html`
|
||||
<div
|
||||
class="bg-red-500/20 border border-red-500 text-red-400 px-2 py-1 rounded text-xs"
|
||||
class="bg-status-error/20 border border-status-error text-status-error px-2 py-1 rounded text-xs"
|
||||
>
|
||||
${this.errorMessage}
|
||||
</div>
|
||||
|
|
@ -592,16 +592,16 @@ export class FileBrowser extends LitElement {
|
|||
<div
|
||||
class="${this.isMobile && this.mobileView === 'preview' ? 'hidden' : ''} ${
|
||||
this.isMobile ? 'w-full' : 'w-80'
|
||||
} bg-dark-bg-secondary border-r border-dark-border flex flex-col"
|
||||
} bg-secondary border-r border-base flex flex-col"
|
||||
>
|
||||
<!-- File list header with toggles -->
|
||||
<div
|
||||
class="bg-dark-bg-secondary border-b border-dark-border p-3 flex items-center justify-between"
|
||||
class="bg-secondary border-b border-base p-3 flex items-center justify-between"
|
||||
>
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
class="btn-secondary text-xs px-2 py-1 font-mono ${
|
||||
this.gitFilter === 'changed' ? 'bg-primary text-black' : ''
|
||||
this.gitFilter === 'changed' ? 'bg-primary text-text-bright' : ''
|
||||
}"
|
||||
@click=${this.toggleGitFilter}
|
||||
title="Show only Git changes"
|
||||
|
|
@ -610,7 +610,7 @@ export class FileBrowser extends LitElement {
|
|||
</button>
|
||||
<button
|
||||
class="btn-secondary text-xs px-2 py-1 font-mono ${
|
||||
this.showHidden ? 'bg-primary text-black' : ''
|
||||
this.showHidden ? 'bg-primary text-text-bright' : ''
|
||||
}"
|
||||
@click=${this.toggleHidden}
|
||||
title="Show hidden files"
|
||||
|
|
@ -621,7 +621,7 @@ export class FileBrowser extends LitElement {
|
|||
${
|
||||
this.gitStatus?.branch
|
||||
? html`
|
||||
<span class="text-dark-text-muted text-xs flex items-center gap-1 font-mono">
|
||||
<span class="text-muted text-xs flex items-center gap-1 font-mono">
|
||||
${UIIcons.git} ${this.gitStatus.branch}
|
||||
</span>
|
||||
`
|
||||
|
|
@ -636,7 +636,7 @@ export class FileBrowser extends LitElement {
|
|||
${
|
||||
this.loading
|
||||
? html`
|
||||
<div class="flex items-center justify-center h-full text-dark-text-muted">
|
||||
<div class="flex items-center justify-center h-full text-muted">
|
||||
Loading...
|
||||
</div>
|
||||
`
|
||||
|
|
@ -645,11 +645,11 @@ export class FileBrowser extends LitElement {
|
|||
this.currentFullPath !== '/'
|
||||
? html`
|
||||
<div
|
||||
class="p-3 hover:bg-dark-bg-lighter cursor-pointer transition-colors flex items-center gap-2 border-b border-dark-border"
|
||||
class="p-3 hover:bg-light cursor-pointer transition-colors flex items-center gap-2 border-b border-base"
|
||||
@click=${this.handleParentClick}
|
||||
>
|
||||
${getParentDirectoryIcon()}
|
||||
<span class="text-dark-text-muted">..</span>
|
||||
<span class="text-muted">..</span>
|
||||
</div>
|
||||
`
|
||||
: ''
|
||||
|
|
@ -657,10 +657,10 @@ export class FileBrowser extends LitElement {
|
|||
${this.files.map(
|
||||
(file) => html`
|
||||
<div
|
||||
class="p-3 hover:bg-dark-bg-lighter cursor-pointer transition-colors flex items-center gap-2
|
||||
class="p-3 hover:bg-light cursor-pointer transition-colors flex items-center gap-2
|
||||
${
|
||||
this.selectedFile?.path === file.path
|
||||
? 'bg-dark-bg-lighter border-l-2 border-primary'
|
||||
? 'bg-light border-l-2 border-primary'
|
||||
: ''
|
||||
}"
|
||||
@click=${() => this.handleFileClick(file)}
|
||||
|
|
@ -671,7 +671,7 @@ export class FileBrowser extends LitElement {
|
|||
file.isSymlink
|
||||
? html`
|
||||
<svg
|
||||
class="w-3 h-3 text-dark-text-muted absolute -bottom-1 -right-1"
|
||||
class="w-3 h-3 text-muted absolute -bottom-1 -right-1"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
|
|
@ -687,7 +687,7 @@ export class FileBrowser extends LitElement {
|
|||
</span>
|
||||
<span
|
||||
class="flex-1 text-sm whitespace-nowrap ${
|
||||
file.type === 'directory' ? 'text-status-info' : 'text-dark-text'
|
||||
file.type === 'directory' ? 'text-status-info' : 'text-text'
|
||||
}"
|
||||
title="${file.name}${file.isSymlink ? ' (symlink)' : ''}"
|
||||
>${file.name}</span
|
||||
|
|
@ -707,13 +707,13 @@ export class FileBrowser extends LitElement {
|
|||
<div
|
||||
class="${this.isMobile && this.mobileView === 'list' ? 'hidden' : ''} ${
|
||||
this.isMobile ? 'w-full' : 'flex-1'
|
||||
} bg-dark-bg flex flex-col overflow-hidden"
|
||||
} bg-bg flex flex-col overflow-hidden"
|
||||
>
|
||||
${
|
||||
this.selectedFile
|
||||
? html`
|
||||
<div
|
||||
class="bg-dark-bg-secondary border-b border-dark-border p-3 ${
|
||||
class="bg-secondary border-b border-base p-3 ${
|
||||
this.isMobile ? 'space-y-2' : 'flex items-center justify-between'
|
||||
}"
|
||||
>
|
||||
|
|
@ -725,7 +725,7 @@ export class FileBrowser extends LitElement {
|
|||
@click=${() => {
|
||||
this.mobileView = 'list';
|
||||
}}
|
||||
class="text-dark-text-muted hover:text-dark-text transition-colors flex-shrink-0"
|
||||
class="text-muted hover:text-primary transition-colors flex-shrink-0"
|
||||
title="Back to files"
|
||||
>
|
||||
<svg
|
||||
|
|
@ -751,7 +751,7 @@ export class FileBrowser extends LitElement {
|
|||
this.selectedFile.isSymlink
|
||||
? html`
|
||||
<svg
|
||||
class="w-3 h-3 text-dark-text-muted absolute -bottom-1 -right-1"
|
||||
class="w-3 h-3 text-muted absolute -bottom-1 -right-1"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
|
|
@ -808,7 +808,7 @@ export class FileBrowser extends LitElement {
|
|||
? html`
|
||||
<button
|
||||
class="btn-secondary text-xs px-2 py-1 font-mono ${
|
||||
this.showDiff ? 'bg-primary text-black' : ''
|
||||
this.showDiff ? 'bg-primary text-text-bright' : ''
|
||||
} ${
|
||||
this.isMobile &&
|
||||
this.selectedFile.type === 'file' &&
|
||||
|
|
@ -835,7 +835,7 @@ export class FileBrowser extends LitElement {
|
|||
${
|
||||
this.mode === 'select'
|
||||
? html`
|
||||
<div class="p-4 border-t border-dark-border flex gap-4">
|
||||
<div class="p-4 border-t border-base flex gap-4">
|
||||
<button class="btn-ghost font-mono flex-1" @click=${this.handleCancel}>
|
||||
Cancel
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ describe('FilePicker Component', () => {
|
|||
const cancelEventSpy = vi.fn();
|
||||
element.addEventListener('file-cancel', cancelEventSpy);
|
||||
|
||||
const modal = element.querySelector('.bg-dark-bg-elevated');
|
||||
const modal = element.querySelector('.bg-elevated');
|
||||
expect(modal).toBeTruthy();
|
||||
|
||||
modal?.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
||||
|
|
|
|||
|
|
@ -216,9 +216,9 @@ export class FilePicker extends LitElement {
|
|||
}
|
||||
|
||||
return html`
|
||||
<div class="fixed inset-0 bg-black bg-opacity-80 backdrop-blur-sm flex items-center justify-center animate-fade-in" style="z-index: ${Z_INDEX.FILE_PICKER};" @click=${this.handleCancel}>
|
||||
<div class="bg-dark-bg-elevated border border-dark-border rounded-xl shadow-2xl p-8 m-4 max-w-sm w-full animate-scale-in" @click=${(e: Event) => e.stopPropagation()}>
|
||||
<h3 class="text-xl font-bold text-dark-text mb-6">
|
||||
<div class="fixed inset-0 bg-bg bg-opacity-80 backdrop-blur-sm flex items-center justify-center animate-fade-in" style="z-index: ${Z_INDEX.FILE_PICKER};" @click=${this.handleCancel}>
|
||||
<div class="bg-elevated border border-base rounded-xl shadow-2xl p-8 m-4 max-w-sm w-full animate-scale-in" @click=${(e: Event) => e.stopPropagation()}>
|
||||
<h3 class="text-xl font-bold text-primary mb-6">
|
||||
Select File
|
||||
</h3>
|
||||
|
||||
|
|
@ -227,12 +227,12 @@ export class FilePicker extends LitElement {
|
|||
? html`
|
||||
<div class="mb-6">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<span class="text-sm text-dark-text-muted font-mono">Uploading...</span>
|
||||
<span class="text-sm text-accent-primary font-mono font-medium">${Math.round(this.uploadProgress)}%</span>
|
||||
<span class="text-sm text-muted font-mono">Uploading...</span>
|
||||
<span class="text-sm text-primary font-mono font-medium">${Math.round(this.uploadProgress)}%</span>
|
||||
</div>
|
||||
<div class="w-full bg-dark-bg-secondary rounded-full h-2 overflow-hidden">
|
||||
<div class="w-full bg-secondary rounded-full h-2 overflow-hidden">
|
||||
<div
|
||||
class="bg-gradient-to-r from-accent-primary to-accent-primary-light h-2 rounded-full transition-all duration-300 shadow-glow-primary-sm"
|
||||
class="bg-gradient-to-r from-primary to-primary-light h-2 rounded-full transition-all duration-300 shadow-glow-sm"
|
||||
style="width: ${this.uploadProgress}%"
|
||||
></div>
|
||||
</div>
|
||||
|
|
@ -242,7 +242,7 @@ export class FilePicker extends LitElement {
|
|||
<div class="space-y-4">
|
||||
<button
|
||||
@click=${this.handleFileClick}
|
||||
class="w-full bg-accent-primary text-dark-bg font-medium py-4 px-6 rounded-lg flex items-center justify-center gap-3 transition-all duration-200 hover:bg-accent-primary-light hover:shadow-glow-primary active:scale-95"
|
||||
class="w-full bg-primary text-bg font-medium py-4 px-6 rounded-lg flex items-center justify-center gap-3 transition-all duration-200 hover:bg-primary-light hover:shadow-glow active:scale-95"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M4 4a2 2 0 00-2 2v8a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-5L9 2H4z" clip-rule="evenodd"/>
|
||||
|
|
@ -253,10 +253,10 @@ export class FilePicker extends LitElement {
|
|||
`
|
||||
}
|
||||
|
||||
<div class="mt-6 pt-6 border-t border-dark-border">
|
||||
<div class="mt-6 pt-6 border-t border-base">
|
||||
<button
|
||||
@click=${this.handleCancel}
|
||||
class="w-full bg-dark-bg-secondary border border-dark-border text-dark-text font-mono py-3 px-6 rounded-lg transition-all duration-200 hover:bg-dark-surface-hover hover:border-dark-border-light active:scale-95"
|
||||
class="w-full bg-secondary border border-base text-primary font-mono py-3 px-6 rounded-lg transition-all duration-200 hover:bg-surface hover:border-primary active:scale-95"
|
||||
?disabled=${this.uploading}
|
||||
>
|
||||
Cancel
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ export class FullHeader extends HeaderBase {
|
|||
|
||||
return html`
|
||||
<div
|
||||
class="app-header bg-dark-bg-secondary border-b border-dark-border p-3"
|
||||
class="app-header bg-bg-secondary border-b border-border p-3"
|
||||
style="padding-top: max(0.75rem, calc(0.75rem + env(safe-area-inset-top)));"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
|
|
@ -27,10 +27,10 @@ export class FullHeader extends HeaderBase {
|
|||
>
|
||||
<terminal-icon size="24"></terminal-icon>
|
||||
<div class="flex items-baseline gap-2">
|
||||
<h1 class="text-xl font-bold text-accent-green font-mono group-hover:underline">
|
||||
<h1 class="text-xl font-bold text-primary font-mono group-hover:underline">
|
||||
VibeTunnel
|
||||
</h1>
|
||||
<p class="text-dark-text-muted text-xs font-mono">
|
||||
<p class="text-text-muted text-xs font-mono">
|
||||
(${runningSessions.length})
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -41,7 +41,7 @@ export class FullHeader extends HeaderBase {
|
|||
@open-settings=${() => this.dispatchEvent(new CustomEvent('open-settings'))}
|
||||
></notification-status>
|
||||
<button
|
||||
class="p-2 text-dark-text border border-dark-border hover:border-accent-green hover:text-accent-green rounded-lg transition-all duration-200"
|
||||
class="p-2 text-text border border-border hover:border-primary hover:text-primary rounded-lg transition-all duration-200"
|
||||
@click=${() => this.dispatchEvent(new CustomEvent('open-file-browser'))}
|
||||
title="Browse Files (⌘O)"
|
||||
>
|
||||
|
|
@ -52,7 +52,7 @@ export class FullHeader extends HeaderBase {
|
|||
</svg>
|
||||
</button>
|
||||
<button
|
||||
class="p-2 text-dark-text border border-dark-border hover:border-accent-green hover:text-accent-green rounded-lg transition-all duration-200"
|
||||
class="p-2 text-text border border-border hover:border-primary hover:text-primary rounded-lg transition-all duration-200"
|
||||
@click=${this.handleScreenshare}
|
||||
title="Start Screenshare"
|
||||
>
|
||||
|
|
@ -64,7 +64,7 @@ export class FullHeader extends HeaderBase {
|
|||
</svg>
|
||||
</button>
|
||||
<button
|
||||
class="p-2 bg-accent-green text-dark-bg hover:bg-accent-green-light rounded-lg transition-all duration-200 vt-create-button"
|
||||
class="p-2 bg-primary text-text-bright hover:bg-primary-light rounded-lg transition-all duration-200 vt-create-button"
|
||||
@click=${this.handleCreateSession}
|
||||
title="Create New Session"
|
||||
data-testid="create-session-button"
|
||||
|
|
@ -89,7 +89,7 @@ export class FullHeader extends HeaderBase {
|
|||
return html`
|
||||
<div class="user-menu-container relative flex-shrink-0">
|
||||
<button
|
||||
class="font-mono text-sm px-3 py-2 text-dark-text border border-dark-border hover:bg-dark-bg-tertiary hover:text-dark-text rounded-lg transition-all duration-200 flex items-center gap-2"
|
||||
class="font-mono text-sm px-3 py-2 text-text border border-border hover:bg-bg-tertiary hover:text-text rounded-lg transition-all duration-200 flex items-center gap-2"
|
||||
@click=${this.toggleUserMenu}
|
||||
title="User menu"
|
||||
>
|
||||
|
|
@ -117,13 +117,13 @@ export class FullHeader extends HeaderBase {
|
|||
this.showUserMenu
|
||||
? html`
|
||||
<div
|
||||
class="absolute right-0 top-full mt-1 bg-dark-surface border border-dark-border rounded-lg shadow-lg py-1 z-50 min-w-36"
|
||||
class="absolute right-0 top-full mt-1 bg-surface border border-border rounded-lg shadow-lg py-1 z-50 min-w-36"
|
||||
>
|
||||
<div class="px-3 py-2 text-sm text-dark-text-muted border-b border-dark-border">
|
||||
<div class="px-3 py-2 text-sm text-text-muted border-b border-border">
|
||||
${this.authMethod || 'authenticated'}
|
||||
</div>
|
||||
<button
|
||||
class="w-full text-left px-3 py-2 text-sm font-mono text-status-warning hover:bg-dark-bg-secondary hover:text-status-error"
|
||||
class="w-full text-left px-3 py-2 text-sm font-mono text-status-warning hover:bg-bg-secondary hover:text-status-error"
|
||||
@click=${this.handleLogout}
|
||||
>
|
||||
Logout
|
||||
|
|
|
|||
|
|
@ -69,8 +69,8 @@ export class InlineEdit extends LitElement {
|
|||
}
|
||||
|
||||
input {
|
||||
background: var(--dark-bg-tertiary, #1a1a1a);
|
||||
border: 1px solid var(--dark-border, #333);
|
||||
background: rgb(var(--color-bg-tertiary));
|
||||
border: 1px solid rgb(var(--color-border));
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
padding: 0.125rem 0.25rem;
|
||||
|
|
@ -81,7 +81,7 @@ export class InlineEdit extends LitElement {
|
|||
}
|
||||
|
||||
input:focus {
|
||||
border-color: var(--accent-green, #10b981);
|
||||
border-color: rgb(var(--color-primary));
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
|
|
@ -96,7 +96,7 @@ export class InlineEdit extends LitElement {
|
|||
cursor: pointer;
|
||||
padding: 0.125rem;
|
||||
border-radius: 0.25rem;
|
||||
color: var(--dark-text-muted, #999);
|
||||
color: rgb(var(--color-text-muted));
|
||||
transition: all 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -106,24 +106,24 @@ export class InlineEdit extends LitElement {
|
|||
}
|
||||
|
||||
button:hover {
|
||||
background: var(--dark-bg-tertiary, #1a1a1a);
|
||||
background: rgb(var(--color-bg-tertiary));
|
||||
}
|
||||
|
||||
button.save {
|
||||
color: var(--accent-green, #10b981);
|
||||
color: rgb(var(--color-primary));
|
||||
}
|
||||
|
||||
button.save:hover {
|
||||
background: var(--accent-green, #10b981);
|
||||
background: rgb(var(--color-primary));
|
||||
background-opacity: 0.2;
|
||||
}
|
||||
|
||||
button.cancel {
|
||||
color: var(--status-error, #ef4444);
|
||||
color: rgb(var(--color-status-error));
|
||||
}
|
||||
|
||||
button.cancel:hover {
|
||||
background: var(--status-error, #ef4444);
|
||||
background: rgb(var(--color-status-error));
|
||||
background-opacity: 0.2;
|
||||
}
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -264,27 +264,27 @@ export class LogViewer extends LitElement {
|
|||
|
||||
/* Show scrollbar on hover */
|
||||
.log-container:hover::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
background: rgb(var(--color-text-bright) / 0.2);
|
||||
}
|
||||
|
||||
.log-container::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
background: rgb(var(--color-text-bright) / 0.3);
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
.log-container:hover {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(255, 255, 255, 0.2) transparent;
|
||||
scrollbar-color: rgb(var(--color-text-bright) / 0.2) transparent;
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
|
||||
if (this.loading) {
|
||||
return html`
|
||||
<div class="flex items-center justify-center h-screen bg-dark-bg text-dark-text">
|
||||
<div class="flex items-center justify-center h-screen bg-base text-primary">
|
||||
<div class="text-center">
|
||||
<div
|
||||
class="animate-spin rounded-full h-12 w-12 border-4 border-accent-green border-t-transparent mb-4"
|
||||
class="animate-spin rounded-full h-12 w-12 border-4 border-primary border-t-transparent mb-4"
|
||||
></div>
|
||||
<div>Loading logs...</div>
|
||||
</div>
|
||||
|
|
@ -296,16 +296,16 @@ export class LogViewer extends LitElement {
|
|||
|
||||
return html`
|
||||
${scrollbarStyles}
|
||||
<div class="flex flex-col h-full bg-dark-bg text-dark-text font-mono">
|
||||
<div class="flex flex-col h-full bg-base text-primary font-mono">
|
||||
<!-- Header - single row on desktop, two rows on mobile -->
|
||||
<div class="bg-dark-bg-secondary border-b border-dark-border p-3 sm:p-4">
|
||||
<div class="bg-secondary border-b border-base p-3 sm:p-4">
|
||||
<!-- Mobile layout (two rows) -->
|
||||
<div class="sm:hidden">
|
||||
<!-- Top row with back button and title -->
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<!-- Back button -->
|
||||
<button
|
||||
class="p-2 bg-dark-bg border border-dark-border rounded text-sm text-dark-text hover:border-accent-green hover:text-accent-green transition-colors flex items-center gap-1 flex-shrink-0"
|
||||
class="p-2 bg-base border border-base rounded text-sm text-primary hover:border-primary hover:text-primary transition-colors flex items-center gap-1 flex-shrink-0"
|
||||
@click=${() => {
|
||||
window.location.href = '/';
|
||||
}}
|
||||
|
|
@ -323,7 +323,7 @@ export class LogViewer extends LitElement {
|
|||
</button>
|
||||
|
||||
<h1
|
||||
class="text-base font-bold text-accent-green flex items-center gap-2 flex-shrink-0"
|
||||
class="text-base font-bold text-primary flex items-center gap-2 flex-shrink-0"
|
||||
>
|
||||
<terminal-icon size="20"></terminal-icon>
|
||||
<span>System Logs</span>
|
||||
|
|
@ -334,8 +334,8 @@ export class LogViewer extends LitElement {
|
|||
<button
|
||||
class="p-2 text-xs uppercase font-bold rounded transition-colors ${
|
||||
this.autoScroll
|
||||
? 'bg-accent-green text-dark-bg'
|
||||
: 'bg-dark-bg-tertiary text-dark-text-muted border border-dark-border'
|
||||
? 'bg-primary text-base'
|
||||
: 'bg-tertiary text-muted border border-base'
|
||||
}"
|
||||
@click=${() => {
|
||||
this.autoScroll = !this.autoScroll;
|
||||
|
|
@ -361,7 +361,7 @@ export class LogViewer extends LitElement {
|
|||
<!-- Search input -->
|
||||
<input
|
||||
type="text"
|
||||
class="px-3 py-1.5 bg-dark-bg border border-dark-border rounded text-sm text-dark-text placeholder-dark-text-muted focus:outline-none focus:border-accent-green transition-colors w-full"
|
||||
class="px-3 py-1.5 bg-base border border-base rounded text-sm text-primary placeholder-muted focus:outline-none focus:border-primary transition-colors w-full"
|
||||
placeholder="Filter logs..."
|
||||
.value=${this.filter}
|
||||
@input=${(e: Event) => {
|
||||
|
|
@ -379,13 +379,13 @@ export class LogViewer extends LitElement {
|
|||
class="px-1.5 py-1 text-xs uppercase font-bold rounded transition-colors ${
|
||||
this.levelFilter.has(level)
|
||||
? level === 'error'
|
||||
? 'bg-status-error text-dark-bg'
|
||||
? 'bg-status-error bg-opacity-20 text-status-error border border-status-error'
|
||||
: level === 'warn'
|
||||
? 'bg-status-warning text-dark-bg'
|
||||
? 'bg-status-warning bg-opacity-20 text-status-warning border border-status-warning'
|
||||
: level === 'debug'
|
||||
? 'bg-dark-text-muted text-dark-bg'
|
||||
: 'bg-dark-text text-dark-bg'
|
||||
: 'bg-dark-bg-tertiary text-dark-text-muted border border-dark-border'
|
||||
? 'bg-bg-tertiary text-text-muted border border-border'
|
||||
: 'bg-primary bg-opacity-20 text-primary border border-primary'
|
||||
: 'bg-bg-tertiary text-text-muted border border-border'
|
||||
}"
|
||||
@click=${() => this.toggleLevel(level)}
|
||||
title="${level} logs"
|
||||
|
|
@ -409,8 +409,8 @@ export class LogViewer extends LitElement {
|
|||
<button
|
||||
class="px-1.5 py-1 text-xs uppercase font-bold rounded transition-colors ${
|
||||
this.showClient
|
||||
? 'bg-orange-500 text-dark-bg'
|
||||
: 'bg-dark-bg-tertiary text-dark-text-muted border border-dark-border'
|
||||
? 'bg-status-warning bg-opacity-20 text-status-warning border border-status-warning'
|
||||
: 'bg-bg-tertiary text-text-muted border border-border'
|
||||
}"
|
||||
@click=${() => {
|
||||
this.showClient = !this.showClient;
|
||||
|
|
@ -422,8 +422,8 @@ export class LogViewer extends LitElement {
|
|||
<button
|
||||
class="px-1.5 py-1 text-xs uppercase font-bold rounded transition-colors ${
|
||||
this.showServer
|
||||
? 'bg-accent-green text-dark-bg'
|
||||
: 'bg-dark-bg-tertiary text-dark-text-muted border border-dark-border'
|
||||
? 'bg-primary bg-opacity-20 text-primary border border-primary'
|
||||
: 'bg-bg-tertiary text-text-muted border border-border'
|
||||
}"
|
||||
@click=${() => {
|
||||
this.showServer = !this.showServer;
|
||||
|
|
@ -441,7 +441,7 @@ export class LogViewer extends LitElement {
|
|||
<div class="hidden sm:flex items-center gap-3">
|
||||
<!-- Back button -->
|
||||
<button
|
||||
class="px-3 py-1.5 bg-dark-bg border border-dark-border rounded text-sm text-dark-text hover:border-accent-green hover:text-accent-green transition-colors flex items-center gap-2 flex-shrink-0"
|
||||
class="px-3 py-1.5 bg-bg border border-border rounded text-sm text-primary hover:border-primary hover:text-primary transition-colors flex items-center gap-2 flex-shrink-0"
|
||||
@click=${() => {
|
||||
window.location.href = '/';
|
||||
}}
|
||||
|
|
@ -459,7 +459,7 @@ export class LogViewer extends LitElement {
|
|||
Back
|
||||
</button>
|
||||
|
||||
<h1 class="text-lg font-bold text-accent-green flex items-center gap-2 flex-shrink-0">
|
||||
<h1 class="text-lg font-bold text-primary flex items-center gap-2 flex-shrink-0">
|
||||
<terminal-icon size="24"></terminal-icon>
|
||||
<span>System Logs</span>
|
||||
</h1>
|
||||
|
|
@ -468,7 +468,7 @@ export class LogViewer extends LitElement {
|
|||
<!-- Search input -->
|
||||
<input
|
||||
type="text"
|
||||
class="px-3 py-1.5 bg-dark-bg border border-dark-border rounded text-sm text-dark-text placeholder-dark-text-muted focus:outline-none focus:border-accent-green transition-colors flex-1 sm:flex-initial sm:w-64 md:w-80"
|
||||
class="px-3 py-1.5 bg-bg border border-border rounded text-sm text-primary placeholder-text-muted focus:outline-none focus:border-primary transition-colors flex-1 sm:flex-initial sm:w-64 md:w-80"
|
||||
placeholder="Filter logs..."
|
||||
.value=${this.filter}
|
||||
@input=${(e: Event) => {
|
||||
|
|
@ -484,13 +484,13 @@ export class LogViewer extends LitElement {
|
|||
class="px-2 py-1 text-xs uppercase font-bold rounded transition-colors ${
|
||||
this.levelFilter.has(level)
|
||||
? level === 'error'
|
||||
? 'bg-status-error text-dark-bg'
|
||||
? 'bg-status-error bg-opacity-20 text-status-error border border-status-error'
|
||||
: level === 'warn'
|
||||
? 'bg-status-warning text-dark-bg'
|
||||
? 'bg-status-warning bg-opacity-20 text-status-warning border border-status-warning'
|
||||
: level === 'debug'
|
||||
? 'bg-dark-text-muted text-dark-bg'
|
||||
: 'bg-dark-text text-dark-bg'
|
||||
: 'bg-dark-bg-tertiary text-dark-text-muted border border-dark-border'
|
||||
? 'bg-bg-tertiary text-text-muted border border-border'
|
||||
: 'bg-primary bg-opacity-20 text-primary border border-primary'
|
||||
: 'bg-bg-tertiary text-text-muted border border-border'
|
||||
}"
|
||||
@click=${() => this.toggleLevel(level)}
|
||||
>
|
||||
|
|
@ -505,8 +505,8 @@ export class LogViewer extends LitElement {
|
|||
<button
|
||||
class="px-2 py-1 text-xs uppercase font-bold rounded transition-colors ${
|
||||
this.showClient
|
||||
? 'bg-orange-500 text-dark-bg'
|
||||
: 'bg-dark-bg-tertiary text-dark-text-muted border border-dark-border'
|
||||
? 'bg-status-warning bg-opacity-20 text-status-warning border border-status-warning'
|
||||
: 'bg-bg-tertiary text-text-muted border border-border'
|
||||
}"
|
||||
@click=${() => {
|
||||
this.showClient = !this.showClient;
|
||||
|
|
@ -517,8 +517,8 @@ export class LogViewer extends LitElement {
|
|||
<button
|
||||
class="px-2 py-1 text-xs uppercase font-bold rounded transition-colors ${
|
||||
this.showServer
|
||||
? 'bg-accent-green text-dark-bg'
|
||||
: 'bg-dark-bg-tertiary text-dark-text-muted border border-dark-border'
|
||||
? 'bg-primary bg-opacity-20 text-primary border border-primary'
|
||||
: 'bg-bg-tertiary text-text-muted border border-border'
|
||||
}"
|
||||
@click=${() => {
|
||||
this.showServer = !this.showServer;
|
||||
|
|
@ -532,8 +532,8 @@ export class LogViewer extends LitElement {
|
|||
<button
|
||||
class="px-3 py-1 text-xs uppercase font-bold rounded transition-colors ${
|
||||
this.autoScroll
|
||||
? 'bg-accent-green text-dark-bg'
|
||||
: 'bg-dark-bg-tertiary text-dark-text-muted border border-dark-border'
|
||||
? 'bg-primary bg-opacity-20 text-primary border border-primary'
|
||||
: 'bg-bg-tertiary text-text-muted border border-border'
|
||||
}"
|
||||
@click=${() => {
|
||||
this.autoScroll = !this.autoScroll;
|
||||
|
|
@ -547,12 +547,12 @@ export class LogViewer extends LitElement {
|
|||
|
||||
<!-- Log container -->
|
||||
<div
|
||||
class="log-container flex-1 overflow-y-auto p-4 bg-dark-bg font-mono text-xs leading-relaxed"
|
||||
class="log-container flex-1 overflow-y-auto p-4 bg-bg font-mono text-xs leading-relaxed"
|
||||
>
|
||||
${
|
||||
this.filteredLogs.length === 0
|
||||
? html`
|
||||
<div class="flex items-center justify-center h-full text-dark-text-muted">
|
||||
<div class="flex items-center justify-center h-full text-text-muted">
|
||||
<div class="text-center">
|
||||
<div>No logs to display</div>
|
||||
</div>
|
||||
|
|
@ -564,14 +564,14 @@ export class LogViewer extends LitElement {
|
|||
|
||||
return html`
|
||||
<div
|
||||
class="group hover:bg-dark-bg-secondary/50 transition-colors rounded ${
|
||||
log.isClient ? 'bg-orange-500/5 pl-2' : 'pl-2'
|
||||
class="group hover:bg-bg-secondary/50 transition-colors rounded ${
|
||||
log.isClient ? 'bg-status-warning/5 pl-2' : 'pl-2'
|
||||
}"
|
||||
>
|
||||
<!-- Desktop layout (hidden on mobile) -->
|
||||
<div class="hidden sm:flex items-start gap-2 py-0.5">
|
||||
<!-- Timestamp -->
|
||||
<span class="text-dark-text-muted w-16 flex-shrink-0 opacity-50"
|
||||
<span class="text-text-muted w-16 flex-shrink-0 opacity-50"
|
||||
>${this.formatRelativeTime(log.timestamp)}</span
|
||||
>
|
||||
|
||||
|
|
@ -579,12 +579,12 @@ export class LogViewer extends LitElement {
|
|||
<span
|
||||
class="w-10 text-center font-mono uppercase tracking-wider flex-shrink-0 ${
|
||||
log.level === 'error'
|
||||
? 'text-red-500 bg-red-500/20 px-1 rounded font-bold'
|
||||
? 'text-status-error bg-status-error/20 px-1 rounded font-bold'
|
||||
: log.level === 'warn'
|
||||
? 'text-yellow-500 bg-yellow-500/20 px-1 rounded font-bold'
|
||||
? 'text-status-warning bg-status-warning/20 px-1 rounded font-bold'
|
||||
: log.level === 'debug'
|
||||
? 'text-gray-600'
|
||||
: 'text-gray-500'
|
||||
? 'text-text-muted'
|
||||
: 'text-primary'
|
||||
}"
|
||||
>${
|
||||
log.level === 'error'
|
||||
|
|
@ -600,29 +600,29 @@ export class LogViewer extends LitElement {
|
|||
<!-- Source indicator -->
|
||||
<span
|
||||
class="flex-shrink-0 ${
|
||||
log.isClient ? 'text-orange-400 font-bold' : 'text-green-600'
|
||||
log.isClient ? 'text-status-warning font-bold' : 'text-primary'
|
||||
}"
|
||||
>${log.isClient ? '◆ C' : '▸ S'}</span
|
||||
>
|
||||
|
||||
<!-- Module -->
|
||||
<span class="text-gray-600 flex-shrink-0 font-mono">${log.module}</span>
|
||||
<span class="text-text-muted flex-shrink-0 font-mono">${log.module}</span>
|
||||
|
||||
<!-- Separator -->
|
||||
<span class="text-gray-700 flex-shrink-0">│</span>
|
||||
<span class="text-text-muted flex-shrink-0">│</span>
|
||||
|
||||
<!-- Message -->
|
||||
<span
|
||||
class="flex-1 ${
|
||||
log.level === 'error'
|
||||
? 'text-red-400'
|
||||
? 'text-status-error'
|
||||
: log.level === 'warn'
|
||||
? 'text-yellow-400'
|
||||
? 'text-status-warning'
|
||||
: log.level === 'debug'
|
||||
? 'text-gray-600'
|
||||
? 'text-text-muted'
|
||||
: log.isClient
|
||||
? 'text-orange-200'
|
||||
: 'text-gray-300'
|
||||
? 'text-status-warning opacity-80'
|
||||
: 'text-primary'
|
||||
}"
|
||||
>${messageLines[0]}</span
|
||||
>
|
||||
|
|
@ -631,37 +631,37 @@ export class LogViewer extends LitElement {
|
|||
<!-- Mobile layout (visible only on mobile) -->
|
||||
<div class="sm:hidden py-1">
|
||||
<div class="flex items-center gap-2 text-xs">
|
||||
<span class="text-dark-text-muted opacity-50"
|
||||
<span class="text-text-muted opacity-50"
|
||||
>${this.formatRelativeTime(log.timestamp)}</span
|
||||
>
|
||||
<span
|
||||
class="${
|
||||
log.level === 'error'
|
||||
? 'text-red-500 font-bold'
|
||||
? 'text-status-error font-bold'
|
||||
: log.level === 'warn'
|
||||
? 'text-yellow-500 font-bold'
|
||||
? 'text-status-warning font-bold'
|
||||
: log.level === 'debug'
|
||||
? 'text-gray-600'
|
||||
: 'text-gray-500'
|
||||
? 'text-text-muted'
|
||||
: 'text-primary'
|
||||
} uppercase"
|
||||
>${log.level}</span
|
||||
>
|
||||
<span class="${log.isClient ? 'text-orange-400' : 'text-green-600'}"
|
||||
<span class="${log.isClient ? 'text-status-warning' : 'text-primary'}"
|
||||
>${log.isClient ? '[C]' : '[S]'}</span
|
||||
>
|
||||
<span class="text-gray-600">${log.module}</span>
|
||||
<span class="text-text-muted">${log.module}</span>
|
||||
</div>
|
||||
<div
|
||||
class="mt-1 ${
|
||||
log.level === 'error'
|
||||
? 'text-red-400'
|
||||
? 'text-status-error'
|
||||
: log.level === 'warn'
|
||||
? 'text-yellow-400'
|
||||
? 'text-status-warning'
|
||||
: log.level === 'debug'
|
||||
? 'text-gray-600'
|
||||
? 'text-text-muted'
|
||||
: log.isClient
|
||||
? 'text-orange-200'
|
||||
: 'text-gray-300'
|
||||
? 'text-status-warning opacity-80'
|
||||
: 'text-primary'
|
||||
}"
|
||||
>
|
||||
${messageLines[0]}
|
||||
|
|
@ -673,10 +673,10 @@ export class LogViewer extends LitElement {
|
|||
<div
|
||||
class="hidden sm:block ml-36 ${
|
||||
log.level === 'error'
|
||||
? 'text-red-400'
|
||||
? 'text-status-error'
|
||||
: log.level === 'warn'
|
||||
? 'text-yellow-400'
|
||||
: 'text-gray-500'
|
||||
? 'text-status-warning'
|
||||
: 'text-text-muted'
|
||||
}"
|
||||
>
|
||||
${messageLines
|
||||
|
|
@ -686,10 +686,10 @@ export class LogViewer extends LitElement {
|
|||
<div
|
||||
class="sm:hidden mt-1 ${
|
||||
log.level === 'error'
|
||||
? 'text-red-400'
|
||||
? 'text-status-error'
|
||||
: log.level === 'warn'
|
||||
? 'text-yellow-400'
|
||||
: 'text-gray-500'
|
||||
? 'text-status-warning'
|
||||
: 'text-text-muted'
|
||||
}"
|
||||
>
|
||||
${messageLines
|
||||
|
|
@ -707,23 +707,21 @@ export class LogViewer extends LitElement {
|
|||
|
||||
<!-- Footer -->
|
||||
<div
|
||||
class="flex items-center justify-between p-3 bg-dark-bg-secondary border-t border-dark-border text-xs"
|
||||
class="flex items-center justify-between p-3 bg-bg-secondary border-t border-border text-xs"
|
||||
>
|
||||
<div class="text-dark-text-muted">
|
||||
<div class="text-text-muted">
|
||||
${this.filteredLogs.length} / ${this.logs.length} logs
|
||||
${
|
||||
this.logSize ? html` <span class="text-dark-text-muted">• ${this.logSize}</span>` : ''
|
||||
}
|
||||
${this.logSize ? html` <span class="text-text-muted">• ${this.logSize}</span>` : ''}
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
class="px-3 py-1 bg-dark-bg border border-dark-border rounded hover:border-accent-green hover:text-accent-green transition-colors"
|
||||
class="px-3 py-1 bg-bg border border-border rounded hover:border-primary hover:text-primary transition-colors"
|
||||
@click=${this.downloadLogs}
|
||||
>
|
||||
Download
|
||||
</button>
|
||||
<button
|
||||
class="px-3 py-1 bg-dark-bg border border-status-error text-status-error rounded hover:bg-status-error hover:text-dark-bg transition-colors"
|
||||
class="px-3 py-1 bg-bg border border-status-error text-status-error rounded hover:bg-status-error hover:text-text-bright transition-colors"
|
||||
@click=${this.clearLogs}
|
||||
>
|
||||
Clear
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ export class MagicWandButton extends LitElement {
|
|||
|
||||
return html`
|
||||
<button
|
||||
class="btn-ghost text-accent-primary ${buttonClasses} rounded-md transition-all hover:bg-dark-bg-elevated hover:shadow-sm hover:scale-110 disabled:opacity-50"
|
||||
class="btn-ghost text-primary ${buttonClasses} rounded-md transition-all hover:bg-elevated hover:shadow-sm hover:scale-110 disabled:opacity-50"
|
||||
@click=${this.handleClick}
|
||||
?disabled=${this.sending}
|
||||
title="Send prompt to update terminal title"
|
||||
|
|
|
|||
|
|
@ -296,7 +296,7 @@ export class MonacoEditor extends LitElement {
|
|||
private setupTheme() {
|
||||
// Use the default VS dark theme
|
||||
if (!window.monaco) return;
|
||||
window.monaco.editor.setTheme('vs-dark');
|
||||
// Theme is already handled by monaco-loader.ts with theme change observer
|
||||
}
|
||||
|
||||
private detectLanguage(): string {
|
||||
|
|
@ -472,14 +472,14 @@ export class MonacoEditor extends LitElement {
|
|||
<div
|
||||
class="editor-container"
|
||||
${ref(this.containerRef)}
|
||||
style="width: 100%; height: 100%; position: relative; background: #1e1e1e;"
|
||||
style="width: 100%; height: 100%; position: relative; background: rgb(var(--color-bg-secondary));"
|
||||
>
|
||||
${
|
||||
this.isLoading
|
||||
? html`
|
||||
<div
|
||||
class="loading"
|
||||
style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #666; font-family: ui-monospace, monospace;"
|
||||
style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: rgb(var(--color-text-muted)); font-family: ui-monospace, monospace;"
|
||||
>
|
||||
Loading editor...
|
||||
</div>
|
||||
|
|
@ -491,18 +491,18 @@ export class MonacoEditor extends LitElement {
|
|||
? html`
|
||||
<button
|
||||
class="mode-toggle"
|
||||
style="position: absolute; top: 10px; right: 10px; z-index: 10; background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.2); color: #fff; padding: 4px 8px; border-radius: 4px; font-size: 12px; cursor: pointer;"
|
||||
style="position: absolute; top: 10px; right: 10px; z-index: 10; background: rgb(var(--color-surface)); border: 1px solid rgb(var(--color-border)); color: rgb(var(--color-text)); padding: 4px 8px; border-radius: 4px; font-size: 12px; cursor: pointer;"
|
||||
@click=${this.toggleDiffMode}
|
||||
title="Toggle between inline and side-by-side diff"
|
||||
@mouseenter=${(e: MouseEvent) => {
|
||||
const btn = e.target as HTMLButtonElement;
|
||||
btn.style.background = 'rgba(255, 255, 255, 0.2)';
|
||||
btn.style.borderColor = 'rgba(255, 255, 255, 0.3)';
|
||||
btn.style.background = 'rgb(var(--color-surface-hover))';
|
||||
btn.style.borderColor = 'rgb(var(--color-border-focus))';
|
||||
}}
|
||||
@mouseleave=${(e: MouseEvent) => {
|
||||
const btn = e.target as HTMLButtonElement;
|
||||
btn.style.background = 'rgba(255, 255, 255, 0.1)';
|
||||
btn.style.borderColor = 'rgba(255, 255, 255, 0.2)';
|
||||
btn.style.background = 'rgb(var(--color-surface))';
|
||||
btn.style.borderColor = 'rgb(var(--color-border))';
|
||||
}}
|
||||
>
|
||||
${this.diffMode === 'inline' ? 'Side by Side' : 'Inline'}
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ export class NotificationStatus extends LitElement {
|
|||
return html`
|
||||
<button
|
||||
@click=${this.handleClick}
|
||||
class="p-2 ${color} hover:text-dark-text transition-colors relative"
|
||||
class="p-2 ${color} hover:text-primary transition-colors relative"
|
||||
title="${tooltip}"
|
||||
>
|
||||
${this.renderIcon()}
|
||||
|
|
|
|||
|
|
@ -11,17 +11,17 @@ export class ScreencapSidebar extends LitElement {
|
|||
:host {
|
||||
display: block;
|
||||
height: 100%;
|
||||
background: #0f0f0f;
|
||||
border-right: 1px solid #2a2a2a;
|
||||
background: rgb(var(--color-bg));
|
||||
border-right: 1px solid rgb(var(--color-border));
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #2a2a2a #0f0f0f;
|
||||
scrollbar-color: rgb(var(--color-border)) rgb(var(--color-bg));
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
padding: 1rem;
|
||||
border-bottom: 1px solid #2a2a2a;
|
||||
background: linear-gradient(to bottom, #141414, #0f0f0f);
|
||||
border-bottom: 1px solid rgb(var(--color-border));
|
||||
background: linear-gradient(to bottom, rgb(var(--color-bg-secondary)), rgb(var(--color-bg-base)));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
|
@ -32,7 +32,7 @@ export class ScreencapSidebar extends LitElement {
|
|||
margin: 0;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: #e4e4e4;
|
||||
color: rgb(var(--color-text));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
|
|
@ -48,17 +48,17 @@ export class ScreencapSidebar extends LitElement {
|
|||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0.75rem;
|
||||
color: #a3a3a3;
|
||||
color: rgb(var(--color-text-muted));
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.refresh-btn {
|
||||
padding: 0.25rem 0.5rem;
|
||||
border: 1px solid #2a2a2a;
|
||||
border: 1px solid rgb(var(--color-border));
|
||||
border-radius: 0.375rem;
|
||||
background: transparent;
|
||||
color: #a3a3a3;
|
||||
color: rgb(var(--color-text-muted));
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
font-size: 0.75rem;
|
||||
|
|
@ -68,8 +68,8 @@ export class ScreencapSidebar extends LitElement {
|
|||
}
|
||||
|
||||
.refresh-btn:hover {
|
||||
border-color: #10B981;
|
||||
color: #10B981;
|
||||
border-color: rgb(var(--color-primary));
|
||||
color: rgb(var(--color-primary));
|
||||
}
|
||||
|
||||
.refresh-btn.loading {
|
||||
|
|
@ -85,15 +85,15 @@ export class ScreencapSidebar extends LitElement {
|
|||
}
|
||||
|
||||
.process-item {
|
||||
background: #1a1a1a;
|
||||
border: 1px solid #2a2a2a;
|
||||
background: rgb(var(--color-surface));
|
||||
border: 1px solid rgb(var(--color-border));
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.process-item:hover {
|
||||
border-color: #3a3a3a;
|
||||
border-color: rgb(var(--color-border-light));
|
||||
}
|
||||
|
||||
.process-header {
|
||||
|
|
@ -106,14 +106,14 @@ export class ScreencapSidebar extends LitElement {
|
|||
}
|
||||
|
||||
.process-header:hover {
|
||||
background: #262626;
|
||||
background: rgb(var(--color-bg-elevated));
|
||||
}
|
||||
|
||||
.process-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 0.375rem;
|
||||
background: #262626;
|
||||
background: rgb(var(--color-bg-elevated));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
|
@ -132,7 +132,7 @@ export class ScreencapSidebar extends LitElement {
|
|||
|
||||
.process-name {
|
||||
font-weight: 500;
|
||||
color: #e4e4e4;
|
||||
color: rgb(var(--color-text));
|
||||
font-size: 0.875rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
|
@ -144,7 +144,7 @@ export class ScreencapSidebar extends LitElement {
|
|||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.75rem;
|
||||
color: #737373;
|
||||
color: rgb(var(--color-text-dim));
|
||||
margin-top: 0.125rem;
|
||||
}
|
||||
|
||||
|
|
@ -152,8 +152,8 @@ export class ScreencapSidebar extends LitElement {
|
|||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #262626;
|
||||
color: #a3a3a3;
|
||||
background: rgb(var(--color-bg-elevated));
|
||||
color: rgb(var(--color-text-muted));
|
||||
padding: 0.125rem 0.5rem;
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.75rem;
|
||||
|
|
@ -164,7 +164,7 @@ export class ScreencapSidebar extends LitElement {
|
|||
.expand-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: #737373;
|
||||
color: rgb(var(--color-text-dim));
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
|
|
@ -182,7 +182,7 @@ export class ScreencapSidebar extends LitElement {
|
|||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
padding: 0.5rem 0.75rem 0.75rem 0.75rem;
|
||||
background: #0a0a0a;
|
||||
background: rgb(var(--color-bg));
|
||||
}
|
||||
|
||||
.process-item.expanded .window-list {
|
||||
|
|
@ -194,26 +194,26 @@ export class ScreencapSidebar extends LitElement {
|
|||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
padding: 0.75rem;
|
||||
background: #141414;
|
||||
border: 1px solid #262626;
|
||||
background: rgb(var(--color-bg-secondary));
|
||||
border: 1px solid rgb(var(--color-border));
|
||||
border-radius: 0.375rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
font-size: 0.875rem;
|
||||
color: #e4e4e4;
|
||||
color: rgb(var(--color-text));
|
||||
gap: 0.25rem;
|
||||
min-height: 3.5rem;
|
||||
}
|
||||
|
||||
.window-item:hover {
|
||||
background: #1a1a1a;
|
||||
border-color: #3a3a3a;
|
||||
background: rgb(var(--color-surface));
|
||||
border-color: rgb(var(--color-border-light));
|
||||
}
|
||||
|
||||
.window-item.selected {
|
||||
background: #10B981;
|
||||
border-color: #10B981;
|
||||
color: #0a0a0a;
|
||||
background: rgb(var(--color-primary));
|
||||
border-color: rgb(var(--color-primary));
|
||||
color: rgb(var(--color-bg));
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
|
|
@ -238,30 +238,30 @@ export class ScreencapSidebar extends LitElement {
|
|||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.75rem;
|
||||
background: #1a1a1a;
|
||||
border: 1px solid #2a2a2a;
|
||||
background: rgb(var(--color-surface));
|
||||
border: 1px solid rgb(var(--color-border));
|
||||
border-radius: 0.5rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.display-item:hover {
|
||||
background: #262626;
|
||||
border-color: #3a3a3a;
|
||||
background: rgb(var(--color-bg-elevated));
|
||||
border-color: rgb(var(--color-border-light));
|
||||
}
|
||||
|
||||
.display-item.selected {
|
||||
background: #10B981;
|
||||
border-color: #10B981;
|
||||
background: rgb(var(--color-primary));
|
||||
border-color: rgb(var(--color-primary));
|
||||
}
|
||||
|
||||
.display-item.selected .display-name {
|
||||
color: #0a0a0a;
|
||||
color: rgb(var(--color-bg));
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.display-item.selected .display-size {
|
||||
color: #0a0a0a;
|
||||
color: rgb(var(--color-bg));
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
|
|
@ -274,7 +274,7 @@ export class ScreencapSidebar extends LitElement {
|
|||
.display-icon {
|
||||
width: 32px;
|
||||
height: 24px;
|
||||
background: #262626;
|
||||
background: rgb(var(--color-bg-elevated));
|
||||
border-radius: 0.25rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -283,28 +283,28 @@ export class ScreencapSidebar extends LitElement {
|
|||
}
|
||||
|
||||
.display-item.selected .display-icon {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
background: rgb(var(--color-text-bright) / 0.2);
|
||||
}
|
||||
|
||||
.display-name {
|
||||
font-weight: 500;
|
||||
color: #e4e4e4;
|
||||
color: rgb(var(--color-text));
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.display-size {
|
||||
font-size: 0.75rem;
|
||||
color: #737373;
|
||||
color: rgb(var(--color-text-dim));
|
||||
margin-top: 0.125rem;
|
||||
}
|
||||
|
||||
.all-displays-btn {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid #2a2a2a;
|
||||
border: 1px solid rgb(var(--color-border));
|
||||
border-radius: 0.5rem;
|
||||
background: linear-gradient(135deg, #1a1a1a, #262626);
|
||||
color: #e4e4e4;
|
||||
background: linear-gradient(135deg, rgb(var(--color-bg-secondary)), rgb(var(--color-bg-tertiary)));
|
||||
color: rgb(var(--color-text));
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
font-family: inherit;
|
||||
|
|
@ -317,14 +317,14 @@ export class ScreencapSidebar extends LitElement {
|
|||
}
|
||||
|
||||
.all-displays-btn:hover {
|
||||
border-color: #10B981;
|
||||
background: linear-gradient(135deg, #262626, #2a2a2a);
|
||||
border-color: rgb(var(--color-primary));
|
||||
background: linear-gradient(135deg, rgb(var(--color-bg-tertiary)), rgb(var(--color-surface)));
|
||||
}
|
||||
|
||||
.all-displays-btn.selected {
|
||||
background: #10B981;
|
||||
border-color: #10B981;
|
||||
color: #0a0a0a;
|
||||
background: rgb(var(--color-primary));
|
||||
border-color: rgb(var(--color-primary));
|
||||
color: rgb(var(--color-bg));
|
||||
font-weight: 500;
|
||||
}
|
||||
`;
|
||||
|
|
@ -474,7 +474,7 @@ export class ScreencapSidebar extends LitElement {
|
|||
${
|
||||
process.iconData
|
||||
? html`<img src="data:image/png;base64,${process.iconData}" alt="${process.processName}">`
|
||||
: html`<svg width="20" height="20" viewBox="0 0 24 24" fill="#737373">
|
||||
: html`<svg width="20" height="20" viewBox="0 0 24 24" fill="rgb(var(--color-text-dim))">
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" />
|
||||
</svg>`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,13 +25,13 @@ export class ScreencapStats extends LitElement {
|
|||
position: absolute;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
background: rgba(15, 15, 15, 0.95);
|
||||
background: rgba(var(--color-bg), 0.95);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid #2a2a2a;
|
||||
border: 1px solid rgb(var(--color-border));
|
||||
border-radius: 0.75rem;
|
||||
padding: 1rem;
|
||||
min-width: 250px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
|
||||
box-shadow: 0 4px 6px rgb(var(--color-bg-base) / 0.3);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
|
|
@ -39,7 +39,7 @@ export class ScreencapStats extends LitElement {
|
|||
margin: 0 0 0.75rem 0;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: #e4e4e4;
|
||||
color: rgb(var(--color-text));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
|
|
@ -50,7 +50,7 @@ export class ScreencapStats extends LitElement {
|
|||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.375rem 0;
|
||||
border-bottom: 1px solid rgba(42, 42, 42, 0.5);
|
||||
border-bottom: 1px solid rgba(var(--color-border), 0.5);
|
||||
}
|
||||
|
||||
.stat-row:last-child {
|
||||
|
|
@ -58,43 +58,43 @@ export class ScreencapStats extends LitElement {
|
|||
}
|
||||
|
||||
.stat-label {
|
||||
color: #a3a3a3;
|
||||
color: rgb(var(--color-text-muted));
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
color: #e4e4e4;
|
||||
color: rgb(var(--color-text));
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.stat-value.codec-h264,
|
||||
.stat-value.codec-h265 {
|
||||
color: #10B981;
|
||||
color: rgb(var(--color-primary));
|
||||
}
|
||||
|
||||
.stat-value.codec-vp8,
|
||||
.stat-value.codec-vp9 {
|
||||
color: #3B82F6;
|
||||
color: rgb(var(--color-status-info));
|
||||
}
|
||||
|
||||
.stat-value.codec-av1 {
|
||||
color: #8B5CF6;
|
||||
color: rgb(var(--color-status-info));
|
||||
}
|
||||
|
||||
.stat-value.latency-good {
|
||||
color: #10B981;
|
||||
color: rgb(var(--color-primary));
|
||||
}
|
||||
|
||||
.stat-value.latency-warning {
|
||||
color: #F59E0B;
|
||||
color: rgb(var(--color-status-warning));
|
||||
}
|
||||
|
||||
.stat-value.latency-bad {
|
||||
color: #EF4444;
|
||||
color: rgb(var(--color-status-error));
|
||||
}
|
||||
|
||||
.loading-message {
|
||||
color: #a3a3a3;
|
||||
color: rgb(var(--color-text-muted));
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
|
@ -121,19 +121,19 @@ export class ScreencapStats extends LitElement {
|
|||
}
|
||||
|
||||
.quality-excellent .quality-dot {
|
||||
background: #10B981;
|
||||
background: rgb(var(--color-primary));
|
||||
}
|
||||
|
||||
.quality-good .quality-dot {
|
||||
background: #3B82F6;
|
||||
background: rgb(var(--color-status-info));
|
||||
}
|
||||
|
||||
.quality-fair .quality-dot {
|
||||
background: #F59E0B;
|
||||
background: rgb(var(--color-status-warning));
|
||||
}
|
||||
|
||||
.quality-poor .quality-dot {
|
||||
background: #EF4444;
|
||||
background: rgb(var(--color-status-error));
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
|
|||
|
|
@ -33,8 +33,8 @@ export class ScreencapView extends LitElement {
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
background: #0a0a0a;
|
||||
color: #e4e4e4;
|
||||
background: rgb(var(--color-bg));
|
||||
color: rgb(var(--color-text));
|
||||
font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Menlo, Consolas, 'DejaVu Sans Mono', monospace;
|
||||
|
||||
/* Honor safe areas on mobile devices */
|
||||
|
|
@ -48,17 +48,17 @@ export class ScreencapView extends LitElement {
|
|||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.75rem 1.5rem;
|
||||
background: linear-gradient(to right, #141414, #1f1f1f);
|
||||
border-bottom: 1px solid #2a2a2a;
|
||||
background: linear-gradient(to right, rgb(var(--color-bg-secondary)), rgb(var(--color-bg-tertiary)));
|
||||
border-bottom: 1px solid rgb(var(--color-border));
|
||||
gap: 1rem;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
||||
box-shadow: 0 1px 3px rgb(var(--color-bg-base) / 0.3);
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
color: #10B981;
|
||||
color: rgb(var(--color-primary));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
|
|
@ -72,10 +72,10 @@ export class ScreencapView extends LitElement {
|
|||
|
||||
.btn {
|
||||
padding: 0.5rem 1rem;
|
||||
border: 1px solid #2a2a2a;
|
||||
border: 1px solid rgb(var(--color-border));
|
||||
border-radius: 0.5rem;
|
||||
background: transparent;
|
||||
color: #e4e4e4;
|
||||
color: rgb(var(--color-text));
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
font-family: inherit;
|
||||
|
|
@ -87,31 +87,31 @@ export class ScreencapView extends LitElement {
|
|||
}
|
||||
|
||||
.btn:hover {
|
||||
border-color: #10B981;
|
||||
color: #10B981;
|
||||
border-color: rgb(var(--color-primary));
|
||||
color: rgb(var(--color-primary));
|
||||
}
|
||||
|
||||
.btn.primary {
|
||||
background: #10B981;
|
||||
color: #0a0a0a;
|
||||
border-color: #10B981;
|
||||
background: rgb(var(--color-primary));
|
||||
color: rgb(var(--color-bg));
|
||||
border-color: rgb(var(--color-primary));
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.btn.primary:hover {
|
||||
background: #0D9668;
|
||||
border-color: #0D9668;
|
||||
background: rgb(var(--color-primary-hover));
|
||||
border-color: rgb(var(--color-primary-hover));
|
||||
}
|
||||
|
||||
.btn.danger {
|
||||
background: #EF4444;
|
||||
color: white;
|
||||
border-color: #EF4444;
|
||||
background: rgb(var(--color-status-error));
|
||||
color: rgb(var(--color-text-bright));
|
||||
border-color: rgb(var(--color-status-error));
|
||||
}
|
||||
|
||||
.btn.danger:hover {
|
||||
background: #DC2626;
|
||||
border-color: #DC2626;
|
||||
background: rgb(var(--color-status-error));
|
||||
border-color: rgb(var(--color-status-error));
|
||||
}
|
||||
|
||||
.main-container {
|
||||
|
|
@ -147,7 +147,7 @@ export class ScreencapView extends LitElement {
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #0a0a0a;
|
||||
background: rgb(var(--color-bg));
|
||||
overflow: hidden;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
|
@ -171,11 +171,11 @@ export class ScreencapView extends LitElement {
|
|||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
background: #000;
|
||||
background: rgb(var(--color-bg));
|
||||
}
|
||||
|
||||
:host(:focus) {
|
||||
outline: 2px solid #60a5fa;
|
||||
outline: 2px solid rgb(var(--color-status-info));
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
|
|
@ -198,7 +198,7 @@ export class ScreencapView extends LitElement {
|
|||
}
|
||||
|
||||
video.capture-preview {
|
||||
background: #000;
|
||||
background: rgb(var(--color-bg));
|
||||
}
|
||||
|
||||
.capture-overlay {
|
||||
|
|
@ -213,23 +213,23 @@ export class ScreencapView extends LitElement {
|
|||
|
||||
.status-message {
|
||||
font-size: 1.125rem;
|
||||
color: #a3a3a3;
|
||||
color: rgb(var(--color-text-muted));
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.status-message.error {
|
||||
color: #EF4444;
|
||||
color: rgb(var(--color-status-error));
|
||||
font-weight: 500;
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
background: rgb(var(--color-status-error) / 0.1);
|
||||
padding: 1rem 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid rgba(239, 68, 68, 0.3);
|
||||
border: 1px solid rgb(var(--color-status-error) / 0.3);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.status-message.loading,
|
||||
.status-message.starting {
|
||||
color: #F59E0B;
|
||||
color: rgb(var(--color-status-warning));
|
||||
}
|
||||
|
||||
.fps-indicator {
|
||||
|
|
@ -244,14 +244,14 @@ export class ScreencapView extends LitElement {
|
|||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
color: #10B981;
|
||||
color: rgb(var(--color-primary));
|
||||
border: 1px solid rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #a3a3a3;
|
||||
color: rgb(var(--color-text-muted));
|
||||
cursor: pointer;
|
||||
padding: 0.5rem;
|
||||
display: flex;
|
||||
|
|
@ -262,13 +262,13 @@ export class ScreencapView extends LitElement {
|
|||
}
|
||||
|
||||
.back-btn:hover {
|
||||
color: #10B981;
|
||||
color: rgb(var(--color-primary));
|
||||
}
|
||||
|
||||
.toggle-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #a3a3a3;
|
||||
color: rgb(var(--color-text-muted));
|
||||
cursor: pointer;
|
||||
padding: 0.5rem;
|
||||
display: flex;
|
||||
|
|
@ -279,11 +279,11 @@ export class ScreencapView extends LitElement {
|
|||
}
|
||||
|
||||
.toggle-btn:hover {
|
||||
color: #e4e4e4;
|
||||
color: rgb(var(--color-text));
|
||||
}
|
||||
|
||||
.toggle-btn.active {
|
||||
color: #10B981;
|
||||
color: rgb(var(--color-primary));
|
||||
}
|
||||
|
||||
.status-log {
|
||||
|
|
@ -294,15 +294,15 @@ export class ScreencapView extends LitElement {
|
|||
max-width: 600px;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
background: rgba(10, 10, 10, 0.95);
|
||||
background: rgb(var(--color-bg-elevated) / 0.95);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(64, 64, 64, 0.3);
|
||||
border: 1px solid rgb(var(--color-border) / 0.3);
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.5;
|
||||
color: #a3a3a3;
|
||||
color: rgb(var(--color-text-muted));
|
||||
}
|
||||
|
||||
.status-log-entry {
|
||||
|
|
@ -312,7 +312,7 @@ export class ScreencapView extends LitElement {
|
|||
}
|
||||
|
||||
.status-log-time {
|
||||
color: #737373;
|
||||
color: rgb(var(--color-text-dim));
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
|
|
@ -320,10 +320,10 @@ export class ScreencapView extends LitElement {
|
|||
flex: 1;
|
||||
}
|
||||
|
||||
.status-log-entry.info { color: #60a5fa; }
|
||||
.status-log-entry.success { color: #10b981; }
|
||||
.status-log-entry.warning { color: #f59e0b; }
|
||||
.status-log-entry.error { color: #ef4444; }
|
||||
.status-log-entry.info { color: rgb(var(--color-status-info)); }
|
||||
.status-log-entry.success { color: rgb(var(--color-status-success)); }
|
||||
.status-log-entry.warning { color: rgb(var(--color-status-warning)); }
|
||||
.status-log-entry.error { color: rgb(var(--color-status-error)); }
|
||||
|
||||
.switch {
|
||||
display: flex;
|
||||
|
|
@ -337,7 +337,7 @@ export class ScreencapView extends LitElement {
|
|||
width: 36px;
|
||||
height: 20px;
|
||||
border-radius: 10px;
|
||||
background: #3f3f46;
|
||||
background: rgb(var(--color-surface-hover));
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
|
|
@ -349,14 +349,14 @@ export class ScreencapView extends LitElement {
|
|||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background: white;
|
||||
background: rgb(var(--color-text-bright));
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.switch input:checked {
|
||||
background-color: #10B981;
|
||||
background-color: rgb(var(--color-primary));
|
||||
}
|
||||
|
||||
.switch input:checked::before {
|
||||
|
|
@ -368,8 +368,8 @@ export class ScreencapView extends LitElement {
|
|||
bottom: calc(1rem + env(safe-area-inset-bottom));
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
color: #a1a1aa;
|
||||
background: rgb(var(--color-bg-base) / 0.8);
|
||||
color: rgb(var(--color-text-muted));
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
|
|
@ -387,26 +387,26 @@ export class ScreencapView extends LitElement {
|
|||
right: calc(20px + env(safe-area-inset-right));
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background: #10B981;
|
||||
background: rgb(var(--color-primary));
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
|
||||
box-shadow: 0 4px 6px rgb(var(--color-bg-base) / 0.3);
|
||||
z-index: 1000;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.keyboard-button:hover {
|
||||
background: #0D9668;
|
||||
background: rgb(var(--color-primary-hover));
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.keyboard-button svg {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
color: white;
|
||||
color: rgb(var(--color-text-bright));
|
||||
}
|
||||
|
||||
.mobile-keyboard-input {
|
||||
|
|
|
|||
|
|
@ -371,9 +371,9 @@ export class SessionCard extends LitElement {
|
|||
>
|
||||
<!-- Compact Header -->
|
||||
<div
|
||||
class="flex justify-between items-center px-3 py-2 border-b border-dark-border bg-gradient-to-r from-dark-bg-secondary to-dark-bg-tertiary"
|
||||
class="flex justify-between items-center px-3 py-2 border-b border-base bg-gradient-to-r from-secondary to-tertiary"
|
||||
>
|
||||
<div class="text-xs font-mono pr-2 flex-1 min-w-0 text-accent-green">
|
||||
<div class="text-xs font-mono pr-2 flex-1 min-w-0 text-primary">
|
||||
<inline-edit
|
||||
.value=${this.session.name || this.session.command?.join(' ') || ''}
|
||||
.placeholder=${this.session.command?.join(' ') || ''}
|
||||
|
|
@ -392,7 +392,7 @@ export class SessionCard extends LitElement {
|
|||
this.session.status === 'running' && isAIAssistantSession(this.session)
|
||||
? html`
|
||||
<button
|
||||
class="bg-transparent border-0 p-0 cursor-pointer opacity-50 hover:opacity-100 transition-opacity duration-200 text-accent-primary"
|
||||
class="bg-transparent border-0 p-0 cursor-pointer opacity-50 hover:opacity-100 transition-opacity duration-200 text-primary"
|
||||
@click=${(e: Event) => {
|
||||
e.stopPropagation();
|
||||
this.handleMagicButton();
|
||||
|
|
@ -456,10 +456,10 @@ export class SessionCard extends LitElement {
|
|||
|
||||
<!-- Terminal display (main content) -->
|
||||
<div
|
||||
class="session-preview bg-black overflow-hidden flex-1 relative ${
|
||||
class="session-preview bg-bg overflow-hidden flex-1 relative ${
|
||||
this.session.status === 'exited' ? 'session-exited' : ''
|
||||
}"
|
||||
style="background: linear-gradient(to bottom, #0a0a0a, #080808); box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.5);"
|
||||
style="background: linear-gradient(to bottom, rgb(var(--color-bg)), rgb(var(--color-bg-secondary))); box-shadow: inset 0 1px 3px rgb(var(--color-bg) / 0.5);"
|
||||
>
|
||||
${
|
||||
this.killing
|
||||
|
|
@ -484,7 +484,7 @@ export class SessionCard extends LitElement {
|
|||
|
||||
<!-- Compact Footer -->
|
||||
<div
|
||||
class="px-3 py-2 text-dark-text-muted text-xs border-t border-dark-border bg-gradient-to-r from-dark-bg-tertiary to-dark-bg-secondary"
|
||||
class="px-3 py-2 text-muted text-xs border-t border-base bg-gradient-to-r from-tertiary to-secondary"
|
||||
>
|
||||
<div class="flex justify-between items-center min-w-0">
|
||||
<span
|
||||
|
|
@ -498,7 +498,7 @@ export class SessionCard extends LitElement {
|
|||
this.session.status === 'running' &&
|
||||
this.isActive &&
|
||||
!this.session.activityStatus?.specificStatus
|
||||
? html`<span class="text-accent-green animate-pulse ml-1">●</span>`
|
||||
? html`<span class="text-primary animate-pulse ml-1">●</span>`
|
||||
: ''
|
||||
}
|
||||
</span>
|
||||
|
|
@ -506,7 +506,7 @@ export class SessionCard extends LitElement {
|
|||
this.session.pid
|
||||
? html`
|
||||
<span
|
||||
class="cursor-pointer hover:text-accent-green transition-colors text-xs flex-shrink-0 ml-2 inline-flex items-center gap-1"
|
||||
class="cursor-pointer hover:text-primary transition-colors text-xs flex-shrink-0 ml-2 inline-flex items-center gap-1"
|
||||
@click=${this.handlePidClick}
|
||||
title="Click to copy PID"
|
||||
>
|
||||
|
|
@ -549,7 +549,7 @@ export class SessionCard extends LitElement {
|
|||
return 'text-status-error';
|
||||
}
|
||||
if (this.session.active === false) {
|
||||
return 'text-dark-text-muted';
|
||||
return 'text-muted';
|
||||
}
|
||||
return this.session.status === 'running' ? 'text-status-success' : 'text-status-warning';
|
||||
}
|
||||
|
|
@ -559,7 +559,7 @@ export class SessionCard extends LitElement {
|
|||
return 'text-status-error';
|
||||
}
|
||||
if (this.session.active === false) {
|
||||
return 'text-dark-text-muted';
|
||||
return 'text-muted';
|
||||
}
|
||||
if (this.session.status === 'running' && this.session.activityStatus?.specificStatus) {
|
||||
return 'text-status-warning';
|
||||
|
|
@ -572,7 +572,7 @@ export class SessionCard extends LitElement {
|
|||
return 'bg-status-error animate-pulse';
|
||||
}
|
||||
if (this.session.active === false) {
|
||||
return 'bg-dark-text-muted';
|
||||
return 'bg-muted';
|
||||
}
|
||||
if (this.session.status === 'running') {
|
||||
if (this.session.activityStatus?.specificStatus) {
|
||||
|
|
|
|||
|
|
@ -165,9 +165,6 @@ export class SessionCreateForm extends LitElement {
|
|||
// Handle visibility changes
|
||||
if (changedProperties.has('visible')) {
|
||||
if (this.visible) {
|
||||
// Remove any lingering modal-closing class that might make the modal invisible
|
||||
document.body.classList.remove('modal-closing');
|
||||
|
||||
// Reset to defaults first to ensure clean state
|
||||
this.workingDir = '~/';
|
||||
this.command = 'zsh';
|
||||
|
|
@ -409,28 +406,18 @@ export class SessionCreateForm extends LitElement {
|
|||
return html``;
|
||||
}
|
||||
|
||||
// Ensure modal-closing class is removed when rendering visible modal
|
||||
if (this.visible) {
|
||||
// Remove immediately
|
||||
document.body.classList.remove('modal-closing');
|
||||
// Also check if element has data-testid
|
||||
requestAnimationFrame(() => {
|
||||
document.body.classList.remove('modal-closing');
|
||||
});
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="modal-backdrop flex items-center justify-center" @click=${this.handleBackdropClick} role="dialog" aria-modal="true">
|
||||
<div
|
||||
class="modal-content font-mono text-sm w-full max-w-[calc(100vw-1rem)] sm:max-w-md lg:max-w-[576px] mx-2 sm:mx-4"
|
||||
style="view-transition-name: create-session-modal; pointer-events: auto;"
|
||||
style="pointer-events: auto;"
|
||||
@click=${(e: Event) => e.stopPropagation()}
|
||||
data-testid="session-create-modal"
|
||||
>
|
||||
<div class="p-3 sm:p-4 lg:p-6 mb-1 sm:mb-2 lg:mb-3 border-b border-dark-border relative bg-gradient-to-r from-dark-bg-secondary to-dark-bg-tertiary flex-shrink-0">
|
||||
<div class="p-3 sm:p-4 lg:p-6 mb-1 sm:mb-2 lg:mb-3 border-b border-base relative bg-gradient-to-r from-secondary to-tertiary flex-shrink-0">
|
||||
<h2 id="modal-title" class="text-primary text-base sm:text-lg lg:text-xl font-bold">New Session</h2>
|
||||
<button
|
||||
class="absolute top-2 right-2 sm:top-3 sm:right-3 lg:top-5 lg:right-5 text-dark-text-muted hover:text-dark-text transition-all duration-200 p-1.5 sm:p-2 hover:bg-dark-bg-tertiary rounded-lg"
|
||||
class="absolute top-2 right-2 sm:top-3 sm:right-3 lg:top-5 lg:right-5 text-muted hover:text-primary transition-all duration-200 p-1.5 sm:p-2 hover:bg-tertiary rounded-lg"
|
||||
@click=${this.handleCancel}
|
||||
title="Close (Esc)"
|
||||
aria-label="Close modal"
|
||||
|
|
@ -455,7 +442,7 @@ export class SessionCreateForm extends LitElement {
|
|||
<div class="p-3 sm:p-4 lg:p-6 overflow-y-auto flex-grow max-h-[65vh] sm:max-h-[75vh] lg:max-h-[80vh]">
|
||||
<!-- Session Name -->
|
||||
<div class="mb-2 sm:mb-3 lg:mb-5">
|
||||
<label class="form-label text-dark-text-muted text-[10px] sm:text-xs lg:text-sm">Session Name (Optional):</label>
|
||||
<label class="form-label text-muted text-[10px] sm:text-xs lg:text-sm">Session Name (Optional):</label>
|
||||
<input
|
||||
type="text"
|
||||
class="input-field py-1.5 sm:py-2 lg:py-3 text-xs sm:text-sm"
|
||||
|
|
@ -469,7 +456,7 @@ export class SessionCreateForm extends LitElement {
|
|||
|
||||
<!-- Command -->
|
||||
<div class="mb-2 sm:mb-3 lg:mb-5">
|
||||
<label class="form-label text-dark-text-muted text-[10px] sm:text-xs lg:text-sm">Command:</label>
|
||||
<label class="form-label text-muted text-[10px] sm:text-xs lg:text-sm">Command:</label>
|
||||
<input
|
||||
type="text"
|
||||
class="input-field py-1.5 sm:py-2 lg:py-3 text-xs sm:text-sm"
|
||||
|
|
@ -483,7 +470,7 @@ export class SessionCreateForm extends LitElement {
|
|||
|
||||
<!-- Working Directory -->
|
||||
<div class="mb-2 sm:mb-3 lg:mb-5">
|
||||
<label class="form-label text-dark-text-muted text-[10px] sm:text-xs lg:text-sm">Working Directory:</label>
|
||||
<label class="form-label text-muted text-[10px] sm:text-xs lg:text-sm">Working Directory:</label>
|
||||
<div class="flex gap-1.5 sm:gap-2">
|
||||
<input
|
||||
type="text"
|
||||
|
|
@ -495,7 +482,7 @@ export class SessionCreateForm extends LitElement {
|
|||
data-testid="working-dir-input"
|
||||
/>
|
||||
<button
|
||||
class="bg-dark-bg-elevated border border-dark-border rounded-lg p-1.5 sm:p-2 lg:p-3 font-mono text-dark-text-muted transition-all duration-200 hover:text-primary hover:bg-dark-surface-hover hover:border-primary hover:shadow-sm flex-shrink-0"
|
||||
class="bg-elevated border border-base rounded-lg p-1.5 sm:p-2 lg:p-3 font-mono text-muted transition-all duration-200 hover:text-primary hover:bg-hover hover:border-primary hover:shadow-sm flex-shrink-0"
|
||||
@click=${this.handleBrowse}
|
||||
?disabled=${this.disabled || this.isCreating}
|
||||
title="Browse directories"
|
||||
|
|
@ -511,23 +498,23 @@ export class SessionCreateForm extends LitElement {
|
|||
</div>
|
||||
|
||||
<!-- Spawn Window Toggle -->
|
||||
<div class="mb-2 sm:mb-3 lg:mb-5 flex items-center justify-between bg-dark-bg-elevated border border-dark-border rounded-lg p-2 sm:p-3 lg:p-4">
|
||||
<div class="mb-2 sm:mb-3 lg:mb-5 flex items-center justify-between bg-elevated border border-base rounded-lg p-2 sm:p-3 lg:p-4">
|
||||
<div class="flex-1 pr-2 sm:pr-3 lg:pr-4">
|
||||
<span class="text-dark-text text-[10px] sm:text-xs lg:text-sm font-medium">Spawn window</span>
|
||||
<p class="text-[9px] sm:text-[10px] lg:text-xs text-dark-text-muted mt-0.5 hidden sm:block">Opens native terminal window</p>
|
||||
<span class="text-primary text-[10px] sm:text-xs lg:text-sm font-medium">Spawn window</span>
|
||||
<p class="text-[9px] sm:text-[10px] lg:text-xs text-muted mt-0.5 hidden sm:block">Opens native terminal window</p>
|
||||
</div>
|
||||
<button
|
||||
role="switch"
|
||||
aria-checked="${this.spawnWindow}"
|
||||
@click=${this.handleSpawnWindowChange}
|
||||
class="relative inline-flex h-4 w-8 sm:h-5 sm:w-10 lg:h-6 lg:w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 focus:ring-offset-dark-bg ${
|
||||
this.spawnWindow ? 'bg-primary' : 'bg-dark-border'
|
||||
class="relative inline-flex h-4 w-8 sm:h-5 sm:w-10 lg:h-6 lg:w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 focus:ring-offset-base ${
|
||||
this.spawnWindow ? 'bg-primary' : 'bg-border'
|
||||
}"
|
||||
?disabled=${this.disabled || this.isCreating}
|
||||
data-testid="spawn-window-toggle"
|
||||
>
|
||||
<span
|
||||
class="inline-block h-3 w-3 sm:h-4 sm:w-4 lg:h-5 lg:w-5 transform rounded-full bg-white transition-transform ${
|
||||
class="inline-block h-3 w-3 sm:h-4 sm:w-4 lg:h-5 lg:w-5 transform rounded-full bg-bg-elevated transition-transform ${
|
||||
this.spawnWindow ? 'translate-x-4 sm:translate-x-5' : 'translate-x-0.5'
|
||||
}"
|
||||
></span>
|
||||
|
|
@ -535,10 +522,10 @@ export class SessionCreateForm extends LitElement {
|
|||
</div>
|
||||
|
||||
<!-- Terminal Title Mode -->
|
||||
<div class="mb-2 sm:mb-4 lg:mb-6 flex items-center justify-between bg-dark-bg-elevated border border-dark-border rounded-lg p-2 sm:p-3 lg:p-4">
|
||||
<div class="mb-2 sm:mb-4 lg:mb-6 flex items-center justify-between bg-elevated border border-base rounded-lg p-2 sm:p-3 lg:p-4">
|
||||
<div class="flex-1 pr-2 sm:pr-3 lg:pr-4">
|
||||
<span class="text-dark-text text-[10px] sm:text-xs lg:text-sm font-medium">Terminal Title Mode</span>
|
||||
<p class="text-[9px] sm:text-[10px] lg:text-xs text-dark-text-muted mt-0.5 hidden sm:block">
|
||||
<span class="text-primary text-[10px] sm:text-xs lg:text-sm font-medium">Terminal Title Mode</span>
|
||||
<p class="text-[9px] sm:text-[10px] lg:text-xs text-muted mt-0.5 hidden sm:block">
|
||||
${this.getTitleModeDescription()}
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -546,16 +533,16 @@ export class SessionCreateForm extends LitElement {
|
|||
<select
|
||||
.value=${this.titleMode}
|
||||
@change=${this.handleTitleModeChange}
|
||||
class="bg-dark-bg-secondary border border-dark-border rounded-lg px-1.5 py-1 pr-6 sm:px-2 sm:py-1.5 sm:pr-7 lg:px-3 lg:py-2 lg:pr-8 text-dark-text text-[10px] sm:text-xs lg:text-sm transition-all duration-200 hover:border-primary-hover focus:border-primary focus:outline-none appearance-none cursor-pointer"
|
||||
class="bg-secondary border border-base rounded-lg px-1.5 py-1 pr-6 sm:px-2 sm:py-1.5 sm:pr-7 lg:px-3 lg:py-2 lg:pr-8 text-primary text-[10px] sm:text-xs lg:text-sm transition-all duration-200 hover:border-primary-hover focus:border-primary focus:outline-none appearance-none cursor-pointer"
|
||||
style="min-width: 80px"
|
||||
?disabled=${this.disabled || this.isCreating}
|
||||
>
|
||||
<option value="${TitleMode.NONE}" class="bg-dark-bg-secondary text-dark-text" ?selected=${this.titleMode === TitleMode.NONE}>None</option>
|
||||
<option value="${TitleMode.FILTER}" class="bg-dark-bg-secondary text-dark-text" ?selected=${this.titleMode === TitleMode.FILTER}>Filter</option>
|
||||
<option value="${TitleMode.STATIC}" class="bg-dark-bg-secondary text-dark-text" ?selected=${this.titleMode === TitleMode.STATIC}>Static</option>
|
||||
<option value="${TitleMode.DYNAMIC}" class="bg-dark-bg-secondary text-dark-text" ?selected=${this.titleMode === TitleMode.DYNAMIC}>Dynamic</option>
|
||||
<option value="${TitleMode.NONE}" class="bg-secondary text-primary" ?selected=${this.titleMode === TitleMode.NONE}>None</option>
|
||||
<option value="${TitleMode.FILTER}" class="bg-secondary text-primary" ?selected=${this.titleMode === TitleMode.FILTER}>Filter</option>
|
||||
<option value="${TitleMode.STATIC}" class="bg-secondary text-primary" ?selected=${this.titleMode === TitleMode.STATIC}>Static</option>
|
||||
<option value="${TitleMode.DYNAMIC}" class="bg-secondary text-primary" ?selected=${this.titleMode === TitleMode.DYNAMIC}>Dynamic</option>
|
||||
</select>
|
||||
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-1 sm:px-1.5 lg:px-2 text-dark-text-muted">
|
||||
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-1 sm:px-1.5 lg:px-2 text-muted">
|
||||
<svg class="h-2.5 w-2.5 sm:h-3 sm:w-3 lg:h-4 lg:w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
|
|
@ -565,7 +552,7 @@ export class SessionCreateForm extends LitElement {
|
|||
|
||||
<!-- Quick Start Section -->
|
||||
<div class="mb-2 sm:mb-4 lg:mb-6">
|
||||
<label class="form-label text-dark-text-muted uppercase text-[9px] sm:text-[10px] lg:text-xs tracking-wider mb-1 sm:mb-2 lg:mb-3"
|
||||
<label class="form-label text-muted uppercase text-[9px] sm:text-[10px] lg:text-xs tracking-wider mb-1 sm:mb-2 lg:mb-3"
|
||||
>Quick Start</label
|
||||
>
|
||||
<div class="grid grid-cols-2 gap-2 sm:gap-2.5 lg:gap-3 mt-1.5 sm:mt-2">
|
||||
|
|
@ -576,7 +563,7 @@ export class SessionCreateForm extends LitElement {
|
|||
class="${
|
||||
this.command === command
|
||||
? 'px-2 py-1.5 sm:px-3 sm:py-2 lg:px-4 lg:py-3 rounded-lg border text-left transition-all bg-primary bg-opacity-10 border-primary text-primary hover:bg-opacity-20 font-medium text-[10px] sm:text-xs lg:text-sm'
|
||||
: 'px-2 py-1.5 sm:px-3 sm:py-2 lg:px-4 lg:py-3 rounded-lg border text-left transition-all bg-dark-bg-elevated border-dark-border text-dark-text hover:bg-dark-surface-hover hover:border-primary hover:text-primary text-[10px] sm:text-xs lg:text-sm'
|
||||
: 'px-2 py-1.5 sm:px-3 sm:py-2 lg:px-4 lg:py-3 rounded-lg border text-left transition-all bg-elevated border-base text-primary hover:bg-hover hover:border-primary hover:text-primary text-[10px] sm:text-xs lg:text-sm'
|
||||
}"
|
||||
?disabled=${this.disabled || this.isCreating}
|
||||
>
|
||||
|
|
@ -591,14 +578,14 @@ export class SessionCreateForm extends LitElement {
|
|||
|
||||
<div class="flex gap-1.5 sm:gap-2 lg:gap-3 mt-2 sm:mt-3 lg:mt-4 xl:mt-6">
|
||||
<button
|
||||
class="flex-1 bg-dark-bg-elevated border border-dark-border text-dark-text px-2 py-1 sm:px-3 sm:py-1.5 lg:px-4 lg:py-2 xl:px-6 xl:py-3 rounded-lg font-mono text-[10px] sm:text-xs lg:text-sm transition-all duration-200 hover:bg-dark-surface-hover hover:border-dark-border-light"
|
||||
class="flex-1 bg-elevated border border-base text-primary px-2 py-1 sm:px-3 sm:py-1.5 lg:px-4 lg:py-2 xl:px-6 xl:py-3 rounded-lg font-mono text-[10px] sm:text-xs lg:text-sm transition-all duration-200 hover:bg-hover hover:border-light"
|
||||
@click=${this.handleCancel}
|
||||
?disabled=${this.isCreating}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
class="flex-1 bg-primary text-black px-2 py-1 sm:px-3 sm:py-1.5 lg:px-4 lg:py-2 xl:px-6 xl:py-3 rounded-lg font-mono text-[10px] sm:text-xs lg:text-sm font-medium transition-all duration-200 hover:bg-primary-hover hover:shadow-glow disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
class="flex-1 bg-primary text-text-bright px-2 py-1 sm:px-3 sm:py-1.5 lg:px-4 lg:py-2 xl:px-6 xl:py-3 rounded-lg font-mono text-[10px] sm:text-xs lg:text-sm font-medium transition-all duration-200 hover:bg-primary-hover hover:shadow-glow disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
@click=${this.handleCreate}
|
||||
?disabled=${
|
||||
this.disabled ||
|
||||
|
|
|
|||
|
|
@ -265,70 +265,70 @@ export class SessionList extends LitElement {
|
|||
${
|
||||
!hasRunningSessions && (!hasExitedSessions || this.hideExited)
|
||||
? html`
|
||||
<div class="text-dark-text-muted text-center py-8">
|
||||
<div class="text-text-muted text-center py-8">
|
||||
${
|
||||
this.loading
|
||||
? 'Loading sessions...'
|
||||
: this.hideExited && this.sessions.length > 0
|
||||
? html`
|
||||
<div class="space-y-4 max-w-2xl mx-auto text-left">
|
||||
<div class="text-lg font-semibold text-dark-text">
|
||||
<div class="text-lg font-semibold text-text">
|
||||
No running sessions
|
||||
</div>
|
||||
<div class="text-sm text-dark-text-muted">
|
||||
<div class="text-sm text-text-muted">
|
||||
There are exited sessions. Show them by toggling "Hide exited" above.
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
<div class="space-y-6 max-w-2xl mx-auto text-left">
|
||||
<div class="text-lg font-semibold text-dark-text">
|
||||
<div class="text-lg font-semibold text-text">
|
||||
No terminal sessions yet!
|
||||
</div>
|
||||
|
||||
<div class="space-y-3">
|
||||
<div class="text-sm text-dark-text-muted">
|
||||
<div class="text-sm text-text-muted">
|
||||
Get started by using the
|
||||
<code class="bg-dark-bg-secondary px-2 py-1 rounded">vt</code> command
|
||||
<code class="bg-bg-secondary px-2 py-1 rounded">vt</code> command
|
||||
in your terminal:
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="bg-dark-bg-secondary p-4 rounded-lg font-mono text-xs space-y-2"
|
||||
class="bg-bg-secondary p-4 rounded-lg font-mono text-xs space-y-2"
|
||||
>
|
||||
<div class="text-green-400">vt pnpm run dev</div>
|
||||
<div class="text-dark-text-muted pl-4"># Monitor your dev server</div>
|
||||
<div class="text-status-success">vt pnpm run dev</div>
|
||||
<div class="text-text-muted pl-4"># Monitor your dev server</div>
|
||||
|
||||
<div class="text-green-400">vt claude --dangerously...</div>
|
||||
<div class="text-dark-text-muted pl-4">
|
||||
<div class="text-status-success">vt claude --dangerously...</div>
|
||||
<div class="text-text-muted pl-4">
|
||||
# Keep an eye on AI agents
|
||||
</div>
|
||||
|
||||
<div class="text-green-400">vt --shell</div>
|
||||
<div class="text-dark-text-muted pl-4">
|
||||
<div class="text-status-success">vt --shell</div>
|
||||
<div class="text-text-muted pl-4">
|
||||
# Open an interactive shell
|
||||
</div>
|
||||
|
||||
<div class="text-green-400">vt python train.py</div>
|
||||
<div class="text-dark-text-muted pl-4">
|
||||
<div class="text-status-success">vt python train.py</div>
|
||||
<div class="text-text-muted pl-4">
|
||||
# Watch long-running scripts
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-3 border-t border-dark-border pt-4">
|
||||
<div class="text-sm font-semibold text-dark-text">
|
||||
<div class="space-y-3 border-t border-border pt-4">
|
||||
<div class="text-sm font-semibold text-text">
|
||||
Haven't installed the CLI yet?
|
||||
</div>
|
||||
<div class="text-sm text-dark-text-muted space-y-1">
|
||||
<div class="text-sm text-text-muted space-y-1">
|
||||
<div>→ Click the VibeTunnel menu bar icon</div>
|
||||
<div>→ Go to Settings → Advanced → Install CLI Tools</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-xs text-dark-text-muted mt-4">
|
||||
<div class="text-xs text-text-muted mt-4">
|
||||
Once installed, any command prefixed with
|
||||
<code class="bg-dark-bg-secondary px-1 rounded">vt</code> will appear
|
||||
<code class="bg-bg-secondary px-1 rounded">vt</code> will appear
|
||||
here, accessible from any browser at localhost:4020.
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -342,8 +342,8 @@ export class SessionList extends LitElement {
|
|||
hasRunningSessions
|
||||
? html`
|
||||
<div class="mb-6">
|
||||
<h3 class="text-xs font-semibold text-dark-text-muted uppercase tracking-wider mb-4">
|
||||
Active <span class="text-dark-text-dim">(${runningSessions.length})</span>
|
||||
<h3 class="text-xs font-semibold text-text-muted uppercase tracking-wider mb-4">
|
||||
Active <span class="text-text-dim">(${runningSessions.length})</span>
|
||||
</h3>
|
||||
<div class="${this.compactMode ? 'space-y-2' : 'session-flex-responsive'} relative">
|
||||
${repeat(
|
||||
|
|
@ -357,8 +357,8 @@ export class SessionList extends LitElement {
|
|||
<div
|
||||
class="group flex items-center gap-3 p-3 rounded-lg cursor-pointer ${
|
||||
session.id === this.selectedSessionId
|
||||
? 'bg-dark-bg-elevated border border-accent-primary shadow-card-hover'
|
||||
: 'bg-dark-bg-secondary border border-dark-border hover:bg-dark-bg-tertiary hover:border-dark-border-light hover:shadow-card'
|
||||
? 'bg-bg-elevated border border-accent-primary shadow-card-hover'
|
||||
: 'bg-bg-secondary border border-border hover:bg-bg-tertiary hover:border-border-light hover:shadow-card'
|
||||
}"
|
||||
@click=${() =>
|
||||
this.handleSessionSelect({ detail: session } as CustomEvent)}
|
||||
|
|
@ -394,7 +394,7 @@ export class SessionList extends LitElement {
|
|||
</div>
|
||||
|
||||
<!-- Elegant divider line -->
|
||||
<div class="w-px h-8 bg-gradient-to-b from-transparent via-dark-border to-transparent"></div>
|
||||
<div class="w-px h-8 bg-gradient-to-b from-transparent via-border to-transparent"></div>
|
||||
|
||||
<!-- Session content -->
|
||||
<div class="flex-1 min-w-0">
|
||||
|
|
@ -402,7 +402,7 @@ export class SessionList extends LitElement {
|
|||
class="text-sm font-mono truncate ${
|
||||
session.id === this.selectedSessionId
|
||||
? 'text-accent-primary font-medium'
|
||||
: 'text-dark-text group-hover:text-accent-primary transition-colors'
|
||||
: 'text-text group-hover:text-accent-primary transition-colors'
|
||||
}"
|
||||
>
|
||||
<inline-edit
|
||||
|
|
@ -420,7 +420,7 @@ export class SessionList extends LitElement {
|
|||
.onSave=${(newName: string) => this.handleRename(session.id, newName)}
|
||||
></inline-edit>
|
||||
</div>
|
||||
<div class="text-xs text-dark-text-muted truncate flex items-center gap-1">
|
||||
<div class="text-xs text-text-muted truncate flex items-center gap-1">
|
||||
${(() => {
|
||||
// Debug logging for activity status
|
||||
if (session.status === 'running' && session.activityStatus) {
|
||||
|
|
@ -436,7 +436,7 @@ export class SessionList extends LitElement {
|
|||
<span class="text-status-warning flex-shrink-0">
|
||||
${session.activityStatus.specificStatus.status}
|
||||
</span>
|
||||
<span class="text-dark-text-muted/50">·</span>
|
||||
<span class="text-text-muted/50">·</span>
|
||||
<span class="truncate">
|
||||
${formatPathForDisplay(session.workingDir)}
|
||||
</span>
|
||||
|
|
@ -458,7 +458,7 @@ export class SessionList extends LitElement {
|
|||
session.status === 'running' || session.status === 'exited'
|
||||
? html`
|
||||
<button
|
||||
class="btn-ghost text-status-error p-1.5 rounded-md transition-all hover:bg-dark-bg-elevated hover:shadow-sm hover:scale-110"
|
||||
class="btn-ghost text-status-error p-1.5 rounded-md transition-all hover:bg-elevated hover:shadow-sm hover:scale-110"
|
||||
@click=${async (e: Event) => {
|
||||
e.stopPropagation();
|
||||
// Kill the session
|
||||
|
|
@ -477,13 +477,13 @@ export class SessionList extends LitElement {
|
|||
`
|
||||
: ''
|
||||
}
|
||||
<div class="text-xs text-dark-text-muted font-mono">
|
||||
<div class="text-xs text-text-muted font-mono">
|
||||
${session.startedAt ? formatSessionDuration(session.startedAt) : ''}
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
<!-- Desktop: Time that hides on hover -->
|
||||
<div class="text-xs text-dark-text-muted font-mono transition-opacity group-hover:opacity-0">
|
||||
<div class="text-xs text-text-muted font-mono transition-opacity group-hover:opacity-0">
|
||||
${session.startedAt ? formatSessionDuration(session.startedAt) : ''}
|
||||
</div>
|
||||
|
||||
|
|
@ -494,7 +494,7 @@ export class SessionList extends LitElement {
|
|||
session.status === 'running' || session.status === 'exited'
|
||||
? html`
|
||||
<button
|
||||
class="btn-ghost text-status-error p-1.5 rounded-md transition-all hover:bg-dark-bg-elevated hover:shadow-sm hover:scale-110"
|
||||
class="btn-ghost text-status-error p-1.5 rounded-md transition-all hover:bg-elevated hover:shadow-sm hover:scale-110"
|
||||
@click=${async (e: Event) => {
|
||||
e.stopPropagation();
|
||||
// Kill the session
|
||||
|
|
@ -546,8 +546,8 @@ export class SessionList extends LitElement {
|
|||
showExitedSection
|
||||
? html`
|
||||
<div>
|
||||
<h3 class="text-xs font-semibold text-dark-text-muted uppercase tracking-wider mb-4">
|
||||
Idle <span class="text-dark-text-dim">(${exitedSessions.length})</span>
|
||||
<h3 class="text-xs font-semibold text-text-muted uppercase tracking-wider mb-4">
|
||||
Idle <span class="text-text-dim">(${exitedSessions.length})</span>
|
||||
</h3>
|
||||
<div class="${this.compactMode ? 'space-y-2' : 'session-flex-responsive'} relative">
|
||||
${repeat(
|
||||
|
|
@ -561,8 +561,8 @@ export class SessionList extends LitElement {
|
|||
<div
|
||||
class="group flex items-center gap-3 p-3 rounded-lg cursor-pointer ${
|
||||
session.id === this.selectedSessionId
|
||||
? 'bg-dark-bg-elevated border border-accent-primary shadow-card-hover'
|
||||
: 'bg-dark-bg-secondary border border-dark-border hover:bg-dark-bg-tertiary hover:border-dark-border-light hover:shadow-card opacity-75'
|
||||
? 'bg-bg-elevated border border-accent-primary shadow-card-hover'
|
||||
: 'bg-bg-secondary border border-border hover:bg-bg-tertiary hover:border-border-light hover:shadow-card opacity-75'
|
||||
}"
|
||||
@click=${() =>
|
||||
this.handleSessionSelect({ detail: session } as CustomEvent)}
|
||||
|
|
@ -573,7 +573,7 @@ export class SessionList extends LitElement {
|
|||
</div>
|
||||
|
||||
<!-- Elegant divider line -->
|
||||
<div class="w-px h-8 bg-gradient-to-b from-transparent via-dark-border to-transparent"></div>
|
||||
<div class="w-px h-8 bg-gradient-to-b from-transparent via-border to-transparent"></div>
|
||||
|
||||
<!-- Session content -->
|
||||
<div class="flex-1 min-w-0">
|
||||
|
|
@ -581,7 +581,7 @@ export class SessionList extends LitElement {
|
|||
class="text-sm font-mono truncate ${
|
||||
session.id === this.selectedSessionId
|
||||
? 'text-accent-primary font-medium'
|
||||
: 'text-dark-text-muted group-hover:text-dark-text transition-colors'
|
||||
: 'text-text-muted group-hover:text-text transition-colors'
|
||||
}"
|
||||
title="${
|
||||
session.name ||
|
||||
|
|
@ -597,7 +597,7 @@ export class SessionList extends LitElement {
|
|||
: session.command)
|
||||
}
|
||||
</div>
|
||||
<div class="text-xs text-dark-text-dim truncate">
|
||||
<div class="text-xs text-text-dim truncate">
|
||||
${formatPathForDisplay(session.workingDir)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -605,13 +605,13 @@ export class SessionList extends LitElement {
|
|||
<!-- Right side: duration and close button -->
|
||||
<div class="relative flex items-center flex-shrink-0 gap-1">
|
||||
<!-- Session duration -->
|
||||
<div class="text-xs text-dark-text-dim font-mono">
|
||||
<div class="text-xs text-text-dim font-mono">
|
||||
${session.startedAt ? formatSessionDuration(session.startedAt) : ''}
|
||||
</div>
|
||||
|
||||
<!-- Clean up button -->
|
||||
<button
|
||||
class="btn-ghost text-dark-text-muted p-1.5 rounded-md transition-all flex-shrink-0 hover:text-status-warning hover:bg-dark-bg-elevated hover:shadow-sm"
|
||||
class="btn-ghost text-text-muted p-1.5 rounded-md transition-all flex-shrink-0 hover:text-status-warning hover:bg-bg-elevated hover:shadow-sm"
|
||||
@click=${async (e: Event) => {
|
||||
e.stopPropagation();
|
||||
try {
|
||||
|
|
@ -688,7 +688,7 @@ export class SessionList extends LitElement {
|
|||
if (exitedSessions.length === 0 && runningSessions.length === 0) return '';
|
||||
|
||||
return html`
|
||||
<div class="sticky bottom-0 border-t border-dark-border bg-dark-bg-secondary p-3 flex flex-wrap gap-2 shadow-lg" style="z-index: ${Z_INDEX.SESSION_LIST_BOTTOM_BAR};">
|
||||
<div class="sticky bottom-0 border-t border-border bg-bg-secondary p-3 flex flex-wrap gap-2 shadow-lg" style="z-index: ${Z_INDEX.SESSION_LIST_BOTTOM_BAR};">
|
||||
<!-- Control buttons with consistent styling -->
|
||||
${
|
||||
exitedSessions.length > 0
|
||||
|
|
@ -697,8 +697,8 @@ export class SessionList extends LitElement {
|
|||
<button
|
||||
class="font-mono text-xs px-4 py-2 rounded-lg border transition-all duration-200 ${
|
||||
this.hideExited
|
||||
? 'border-dark-border bg-dark-bg-elevated text-dark-text-muted hover:bg-dark-surface-hover hover:text-accent-primary hover:border-accent-primary hover:shadow-sm'
|
||||
: 'border-accent-primary bg-accent-primary bg-opacity-10 text-accent-primary hover:bg-opacity-20 hover:shadow-glow-primary-sm'
|
||||
? 'border-border bg-bg-elevated text-text-muted hover:bg-surface-hover hover:text-accent-primary hover:border-accent-primary hover:shadow-sm active:scale-95'
|
||||
: 'border-accent-primary bg-accent-primary bg-opacity-10 text-accent-primary hover:bg-opacity-20 hover:shadow-glow-primary-sm active:scale-95'
|
||||
}"
|
||||
@click=${() =>
|
||||
this.dispatchEvent(
|
||||
|
|
@ -707,7 +707,7 @@ export class SessionList extends LitElement {
|
|||
data-testid="${this.hideExited ? 'show-exited-button' : 'hide-exited-button'}"
|
||||
>
|
||||
${this.hideExited ? 'Show' : 'Hide'} Exited
|
||||
<span class="text-dark-text-dim">(${exitedSessions.length})</span>
|
||||
<span class="text-text-dim">(${exitedSessions.length})</span>
|
||||
</button>
|
||||
|
||||
<!-- Clean Exited button (only when Show Exited is active) -->
|
||||
|
|
@ -715,7 +715,7 @@ export class SessionList extends LitElement {
|
|||
!this.hideExited
|
||||
? html`
|
||||
<button
|
||||
class="font-mono text-xs px-4 py-2 rounded-lg border transition-all duration-200 border-status-warning bg-status-warning bg-opacity-10 text-status-warning hover:bg-opacity-20 hover:shadow-glow-warning-sm disabled:opacity-50"
|
||||
class="font-mono text-xs px-4 py-2 rounded-lg border transition-all duration-200 border-status-warning bg-status-warning bg-opacity-10 text-status-warning hover:bg-opacity-20 hover:shadow-glow-warning-sm active:scale-95 disabled:opacity-50"
|
||||
@click=${this.handleCleanupExited}
|
||||
?disabled=${this.cleaningExited}
|
||||
data-testid="clean-exited-button"
|
||||
|
|
@ -734,11 +734,11 @@ export class SessionList extends LitElement {
|
|||
runningSessions.length > 0
|
||||
? html`
|
||||
<button
|
||||
class="font-mono text-xs px-4 py-2 rounded-lg border transition-all duration-200 border-status-error bg-status-error bg-opacity-10 text-status-error hover:bg-opacity-20"
|
||||
class="font-mono text-xs px-4 py-2 rounded-lg border transition-all duration-200 border-status-error bg-status-error bg-opacity-10 text-status-error hover:bg-opacity-20 hover:shadow-glow-error-sm active:scale-95"
|
||||
@click=${() => this.dispatchEvent(new CustomEvent('kill-all-sessions'))}
|
||||
data-testid="kill-all-button"
|
||||
>
|
||||
Kill All <span class="text-dark-text-dim">(${runningSessions.length})</span>
|
||||
Kill All <span class="text-text-dim">(${runningSessions.length})</span>
|
||||
</button>
|
||||
`
|
||||
: ''
|
||||
|
|
|
|||
|
|
@ -1194,10 +1194,10 @@ export class SessionView extends LitElement {
|
|||
render() {
|
||||
if (!this.session) {
|
||||
return html`
|
||||
<div class="fixed inset-0 bg-dark-bg flex items-center justify-center">
|
||||
<div class="text-dark-text font-mono text-center">
|
||||
<div class="fixed inset-0 bg-base flex items-center justify-center">
|
||||
<div class="text-primary font-mono text-center">
|
||||
<div class="text-2xl mb-2">${this.loadingAnimationManager.getLoadingText()}</div>
|
||||
<div class="text-sm text-dark-text-muted">Waiting for session...</div>
|
||||
<div class="text-sm text-muted">Waiting for session...</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -1212,12 +1212,12 @@ export class SessionView extends LitElement {
|
|||
box-shadow: none !important;
|
||||
}
|
||||
session-view:focus {
|
||||
outline: 2px solid rgb(16 185 129) !important;
|
||||
outline: 2px solid rgb(var(--color-primary)) !important;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
</style>
|
||||
<div
|
||||
class="flex flex-col bg-dark-bg font-mono relative"
|
||||
class="flex flex-col bg-base font-mono relative"
|
||||
style="height: 100vh; height: 100dvh; outline: none !important; box-shadow: none !important;"
|
||||
>
|
||||
<!-- Session Header -->
|
||||
|
|
@ -1252,7 +1252,7 @@ export class SessionView extends LitElement {
|
|||
|
||||
<!-- Enhanced Terminal Container -->
|
||||
<div
|
||||
class="${this.terminalContainerHeight === '100%' ? 'flex-1' : ''} bg-dark-bg overflow-hidden min-h-0 relative ${
|
||||
class="${this.terminalContainerHeight === '100%' ? 'flex-1' : ''} bg-bg overflow-hidden min-h-0 relative ${
|
||||
this.session?.status === 'exited' ? 'session-exited opacity-90' : ''
|
||||
} ${
|
||||
// Add safe area padding for landscape mode on mobile to handle notch
|
||||
|
|
@ -1266,11 +1266,11 @@ export class SessionView extends LitElement {
|
|||
? html`
|
||||
<!-- Enhanced Loading overlay -->
|
||||
<div
|
||||
class="absolute inset-0 bg-dark-bg bg-opacity-90 backdrop-filter backdrop-blur-sm flex items-center justify-center z-10 animate-fade-in"
|
||||
class="absolute inset-0 bg-bg bg-opacity-90 backdrop-filter backdrop-blur-sm flex items-center justify-center z-10 animate-fade-in"
|
||||
>
|
||||
<div class="text-dark-text font-mono text-center">
|
||||
<div class="text-2xl mb-3 text-accent-primary animate-pulse-primary">${this.loadingAnimationManager.getLoadingText()}</div>
|
||||
<div class="text-sm text-dark-text-muted">Connecting to session...</div>
|
||||
<div class="text-primary font-mono text-center">
|
||||
<div class="text-2xl mb-3 text-primary animate-pulse-primary">${this.loadingAnimationManager.getLoadingText()}</div>
|
||||
<div class="text-sm text-muted">Connecting to session...</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
|
@ -1303,7 +1303,7 @@ export class SessionView extends LitElement {
|
|||
class="fixed inset-0 flex items-center justify-center pointer-events-none z-[25]"
|
||||
>
|
||||
<div
|
||||
class="bg-dark-bg-elevated border border-status-warning text-status-warning font-medium text-sm tracking-wide px-6 py-3 rounded-lg shadow-elevated animate-scale-in"
|
||||
class="bg-elevated border border-status-warning text-status-warning font-medium text-sm tracking-wide px-6 py-3 rounded-lg shadow-elevated animate-scale-in"
|
||||
>
|
||||
<span class="flex items-center gap-2">
|
||||
<span class="w-2 h-2 rounded-full bg-status-warning"></span>
|
||||
|
|
@ -1319,7 +1319,7 @@ export class SessionView extends LitElement {
|
|||
${
|
||||
this.isMobile && !this.showMobileInput && !this.useDirectKeyboard
|
||||
? html`
|
||||
<div class="flex-shrink-0 p-4" style="background: black;">
|
||||
<div class="flex-shrink-0 p-4 bg-secondary">
|
||||
<!-- First row: Arrow keys -->
|
||||
<div class="flex gap-2 mb-2">
|
||||
<button
|
||||
|
|
@ -1492,21 +1492,21 @@ export class SessionView extends LitElement {
|
|||
${
|
||||
this.isDragOver
|
||||
? html`
|
||||
<div class="fixed inset-0 bg-black bg-opacity-90 backdrop-blur-sm flex items-center justify-center z-50 pointer-events-none animate-fade-in">
|
||||
<div class="bg-dark-bg-elevated border-2 border-dashed border-accent-primary rounded-xl p-10 text-center max-w-md mx-4 shadow-2xl animate-scale-in">
|
||||
<div class="fixed inset-0 bg-bg bg-opacity-90 backdrop-blur-sm flex items-center justify-center z-50 pointer-events-none animate-fade-in">
|
||||
<div class="bg-elevated border-2 border-dashed border-primary rounded-xl p-10 text-center max-w-md mx-4 shadow-2xl animate-scale-in">
|
||||
<div class="relative mb-6">
|
||||
<div class="w-24 h-24 mx-auto bg-gradient-to-br from-accent-primary to-accent-primary-light rounded-full flex items-center justify-center shadow-glow-primary">
|
||||
<svg class="w-12 h-12 text-dark-bg" fill="currentColor" viewBox="0 0 20 20">
|
||||
<div class="w-24 h-24 mx-auto bg-gradient-to-br from-primary to-primary-light rounded-full flex items-center justify-center shadow-glow">
|
||||
<svg class="w-12 h-12 text-base" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M2 6a2 2 0 012-2h5l2 2h5a2 2 0 012 2v6a2 2 0 01-2 2H4a2 2 0 01-2-2V6z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="absolute -bottom-2 left-1/2 transform -translate-x-1/2 w-32 h-1 bg-gradient-to-r from-transparent via-accent-primary to-transparent opacity-50"></div>
|
||||
<div class="absolute -bottom-2 left-1/2 transform -translate-x-1/2 w-32 h-1 bg-gradient-to-r from-transparent via-primary to-transparent opacity-50"></div>
|
||||
</div>
|
||||
<h3 class="text-2xl font-bold text-dark-text mb-3">Drop files here</h3>
|
||||
<p class="text-sm text-dark-text-muted mb-4">Files will be uploaded and the path sent to terminal</p>
|
||||
<div class="inline-flex items-center gap-2 text-xs text-dark-text-dim bg-dark-bg-secondary px-4 py-2 rounded-lg">
|
||||
<h3 class="text-2xl font-bold text-primary mb-3">Drop files here</h3>
|
||||
<p class="text-sm text-muted mb-4">Files will be uploaded and the path sent to terminal</p>
|
||||
<div class="inline-flex items-center gap-2 text-xs text-dim bg-secondary px-4 py-2 rounded-lg">
|
||||
<span class="opacity-75">Or press</span>
|
||||
<kbd class="px-2 py-1 bg-dark-bg-tertiary border border-dark-border rounded text-accent-primary font-mono text-xs">⌘V</kbd>
|
||||
<kbd class="px-2 py-1 bg-tertiary border border-base rounded text-primary font-mono text-xs">⌘V</kbd>
|
||||
<span class="opacity-75">to paste from clipboard</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -45,12 +45,12 @@ export class CtrlAlphaOverlay extends LitElement {
|
|||
|
||||
<div
|
||||
class="font-mono text-sm mx-4 max-w-sm w-full self-center"
|
||||
style="background: black; border: 1px solid #569cd6; border-radius: 8px; padding: 10px; margin-bottom: ${this.keyboardHeight > 0 ? `${this.keyboardHeight + 180}px` : 'calc(env(keyboard-inset-height, 0px) + 180px)'};/* 180px = estimated quick keyboard height (3 rows) */"
|
||||
style="background: rgb(var(--color-bg)); border: 1px solid rgb(var(--color-primary)); border-radius: 8px; padding: 10px; margin-bottom: ${this.keyboardHeight > 0 ? `${this.keyboardHeight + 180}px` : 'calc(env(keyboard-inset-height, 0px) + 180px)'};/* 180px = estimated quick keyboard height (3 rows) */"
|
||||
>
|
||||
<div class="text-primary text-center mb-2 font-bold">Ctrl + Key</div>
|
||||
|
||||
<!-- Help text -->
|
||||
<div class="text-xs text-dark-text-muted text-center mb-3 opacity-70">
|
||||
<div class="text-xs text-muted text-center mb-3 opacity-70">
|
||||
Build sequences like ctrl+c ctrl+c
|
||||
</div>
|
||||
|
||||
|
|
@ -58,8 +58,8 @@ export class CtrlAlphaOverlay extends LitElement {
|
|||
${
|
||||
this.ctrlSequence.length > 0
|
||||
? html`
|
||||
<div class="text-center mb-4 p-2 border border-dark-border rounded bg-dark-bg">
|
||||
<div class="text-xs text-dark-text-muted mb-1">Current sequence:</div>
|
||||
<div class="text-center mb-4 p-2 border border-base rounded bg-base">
|
||||
<div class="text-xs text-muted mb-1">Current sequence:</div>
|
||||
<div class="text-sm text-primary font-bold">
|
||||
${this.ctrlSequence.map((letter) => `Ctrl+${letter}`).join(' ')}
|
||||
</div>
|
||||
|
|
@ -110,7 +110,7 @@ export class CtrlAlphaOverlay extends LitElement {
|
|||
</div>
|
||||
|
||||
<!-- Common shortcuts info -->
|
||||
<div class="text-xs text-dark-text-muted text-center mb-3">
|
||||
<div class="text-xs text-muted text-center mb-3">
|
||||
<div>Common: C=interrupt, X=exit, O=save, W=search</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -224,7 +224,7 @@ export class MobileInputOverlay extends LitElement {
|
|||
|
||||
<div
|
||||
class="mobile-input-container font-mono text-sm mx-4 flex flex-col"
|
||||
style="background: black; border: 1px solid #569cd6; border-radius: 8px; margin-bottom: ${this.keyboardHeight > 0 ? `${this.keyboardHeight + 180}px` : 'calc(env(keyboard-inset-height, 0px) + 180px)'};/* 180px = estimated quick keyboard height (3 rows) */"
|
||||
style="background: rgb(var(--color-bg)); border: 1px solid rgb(var(--color-primary)); border-radius: 8px; margin-bottom: ${this.keyboardHeight > 0 ? `${this.keyboardHeight + 180}px` : 'calc(env(keyboard-inset-height, 0px) + 180px)'};/* 180px = estimated quick keyboard height (3 rows) */"
|
||||
@click=${this.handleContainerClick}
|
||||
>
|
||||
<!-- Input Area -->
|
||||
|
|
@ -241,7 +241,7 @@ export class MobileInputOverlay extends LitElement {
|
|||
@compositionstart=${this.handleCompositionStart}
|
||||
@compositionupdate=${this.handleCompositionUpdate}
|
||||
@compositionend=${this.handleCompositionEnd}
|
||||
style="height: 120px; background: black; color: #d4d4d4; border: none; padding: 12px;"
|
||||
style="height: 120px; background: rgb(var(--color-bg)); color: rgb(var(--color-text)); border: none; padding: 12px;"
|
||||
autocomplete="off"
|
||||
autocorrect="off"
|
||||
autocapitalize="off"
|
||||
|
|
@ -250,7 +250,7 @@ export class MobileInputOverlay extends LitElement {
|
|||
</div>
|
||||
|
||||
<!-- Controls -->
|
||||
<div class="p-4 flex gap-2" style="border-top: 1px solid #444;">
|
||||
<div class="p-4 flex gap-2" style="border-top: 1px solid rgb(var(--color-border));">
|
||||
<button
|
||||
class="font-mono px-3 py-2 text-xs transition-colors btn-ghost"
|
||||
@click=${() => this.onCancel?.()}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { html, LitElement, nothing } from 'lit';
|
|||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { Z_INDEX } from '../../utils/constants.js';
|
||||
import type { Session } from '../session-list.js';
|
||||
import type { Theme } from '../theme-toggle-icon.js';
|
||||
|
||||
@customElement('mobile-menu')
|
||||
export class MobileMenu extends LitElement {
|
||||
|
|
@ -24,6 +25,7 @@ export class MobileMenu extends LitElement {
|
|||
@property({ type: Function }) onScreenshare?: () => void;
|
||||
@property({ type: Function }) onMaxWidthToggle?: () => void;
|
||||
@property({ type: Function }) onOpenSettings?: () => void;
|
||||
@property({ type: String }) currentTheme: Theme = 'system';
|
||||
|
||||
@state() private showMenu = false;
|
||||
@state() private focusedIndex = -1;
|
||||
|
|
@ -48,12 +50,80 @@ export class MobileMenu extends LitElement {
|
|||
}
|
||||
}
|
||||
|
||||
private handleThemeChange() {
|
||||
// Cycle through themes: light -> dark -> system
|
||||
const themes: Theme[] = ['light', 'dark', 'system'];
|
||||
const currentIndex = themes.indexOf(this.currentTheme);
|
||||
const nextIndex = (currentIndex + 1) % themes.length;
|
||||
const newTheme = themes[nextIndex];
|
||||
|
||||
// Update theme
|
||||
this.currentTheme = newTheme;
|
||||
localStorage.setItem('vibetunnel-theme', newTheme);
|
||||
|
||||
// Apply theme
|
||||
const root = document.documentElement;
|
||||
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
let effectiveTheme: 'light' | 'dark';
|
||||
|
||||
if (newTheme === 'system') {
|
||||
effectiveTheme = mediaQuery.matches ? 'dark' : 'light';
|
||||
} else {
|
||||
effectiveTheme = newTheme;
|
||||
}
|
||||
|
||||
root.setAttribute('data-theme', effectiveTheme);
|
||||
|
||||
// Update meta theme-color
|
||||
const metaTheme = document.querySelector('meta[name="theme-color"]');
|
||||
if (metaTheme) {
|
||||
metaTheme.setAttribute('content', effectiveTheme === 'dark' ? '#0a0a0a' : '#fafafa');
|
||||
}
|
||||
|
||||
// Dispatch event
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('theme-changed', {
|
||||
detail: { theme: newTheme },
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
})
|
||||
);
|
||||
|
||||
// Close menu
|
||||
this.showMenu = false;
|
||||
this.focusedIndex = -1;
|
||||
}
|
||||
|
||||
private getThemeIcon() {
|
||||
switch (this.currentTheme) {
|
||||
case 'light':
|
||||
return html`<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z" clip-rule="evenodd"/>
|
||||
</svg>`;
|
||||
case 'dark':
|
||||
return html`<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"/>
|
||||
</svg>`;
|
||||
case 'system':
|
||||
return html`<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M3 5a2 2 0 012-2h10a2 2 0 012 2v8a2 2 0 01-2 2h-2.22l.123.489.804.804A1 1 0 0113 18H7a1 1 0 01-.707-1.707l.804-.804L7.22 15H5a2 2 0 01-2-2V5zm5.771 7H5V5h10v7H8.771z" clip-rule="evenodd"/>
|
||||
</svg>`;
|
||||
}
|
||||
}
|
||||
|
||||
private getThemeLabel() {
|
||||
return this.currentTheme.charAt(0).toUpperCase() + this.currentTheme.slice(1);
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
// Close menu when clicking outside
|
||||
document.addEventListener('click', this.handleOutsideClick);
|
||||
// Add keyboard support
|
||||
document.addEventListener('keydown', this.handleKeyDown);
|
||||
// Load saved theme preference
|
||||
const saved = localStorage.getItem('vibetunnel-theme') as Theme | null;
|
||||
this.currentTheme = saved || 'system';
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
|
|
@ -137,7 +207,7 @@ export class MobileMenu extends LitElement {
|
|||
return html`
|
||||
<div class="relative w-[44px] flex-shrink-0">
|
||||
<button
|
||||
class="p-2 ${this.showMenu ? 'text-accent-primary border-accent-primary' : 'text-dark-text border-dark-border'} hover:border-accent-primary hover:text-accent-primary rounded-lg"
|
||||
class="p-2 ${this.showMenu ? 'text-primary border-primary' : 'text-primary border-base'} hover:border-primary hover:text-primary rounded-lg"
|
||||
@click=${this.toggleMenu}
|
||||
@keydown=${this.handleMenuButtonKeyDown}
|
||||
title="More actions"
|
||||
|
|
@ -157,13 +227,13 @@ export class MobileMenu extends LitElement {
|
|||
private renderDropdown() {
|
||||
return html`
|
||||
<div
|
||||
class="absolute right-0 top-full mt-2 bg-dark-surface border border-dark-border rounded-lg shadow-xl py-1 min-w-[200px]"
|
||||
class="absolute right-0 top-full mt-2 bg-surface border border-base rounded-lg shadow-xl py-1 min-w-[200px]"
|
||||
style="z-index: ${Z_INDEX.WIDTH_SELECTOR_DROPDOWN};"
|
||||
>
|
||||
|
||||
<!-- New Session -->
|
||||
<button
|
||||
class="w-full text-left px-4 py-3 text-sm font-mono text-dark-text hover:bg-dark-bg-secondary hover:text-accent-primary flex items-center gap-3 ${this.focusedIndex === 0 ? 'bg-dark-bg-secondary text-accent-primary' : ''}"
|
||||
class="w-full text-left px-4 py-3 text-sm font-mono text-primary hover:bg-secondary hover:text-primary flex items-center gap-3 ${this.focusedIndex === 0 ? 'bg-secondary text-primary' : ''}"
|
||||
@click=${() => this.handleAction(this.onCreateSession)}
|
||||
data-testid="mobile-new-session"
|
||||
tabindex="${this.showMenu ? '0' : '-1'}"
|
||||
|
|
@ -174,11 +244,11 @@ export class MobileMenu extends LitElement {
|
|||
New Session
|
||||
</button>
|
||||
|
||||
<div class="border-t border-dark-border my-1"></div>
|
||||
<div class="border-t border-base my-1"></div>
|
||||
|
||||
<!-- File Browser -->
|
||||
<button
|
||||
class="w-full text-left px-4 py-3 text-sm font-mono text-dark-text hover:bg-dark-bg-secondary hover:text-accent-primary flex items-center gap-3 ${this.focusedIndex === 1 ? 'bg-dark-bg-secondary text-accent-primary' : ''}"
|
||||
class="w-full text-left px-4 py-3 text-sm font-mono text-primary hover:bg-secondary hover:text-primary flex items-center gap-3 ${this.focusedIndex === 1 ? 'bg-secondary text-primary' : ''}"
|
||||
@click=${() => this.handleAction(this.onOpenFileBrowser)}
|
||||
data-testid="mobile-file-browser"
|
||||
tabindex="${this.showMenu ? '0' : '-1'}"
|
||||
|
|
@ -191,7 +261,7 @@ export class MobileMenu extends LitElement {
|
|||
|
||||
<!-- Screenshare -->
|
||||
<button
|
||||
class="w-full text-left px-4 py-3 text-sm font-mono text-dark-text hover:bg-dark-bg-secondary hover:text-accent-primary flex items-center gap-3 ${this.focusedIndex === 2 ? 'bg-dark-bg-secondary text-accent-primary' : ''}"
|
||||
class="w-full text-left px-4 py-3 text-sm font-mono text-primary hover:bg-secondary hover:text-primary flex items-center gap-3 ${this.focusedIndex === 2 ? 'bg-secondary text-primary' : ''}"
|
||||
@click=${() => this.handleAction(this.onScreenshare)}
|
||||
data-testid="mobile-screenshare"
|
||||
tabindex="${this.showMenu ? '0' : '-1'}"
|
||||
|
|
@ -207,7 +277,7 @@ export class MobileMenu extends LitElement {
|
|||
|
||||
<!-- Width Settings -->
|
||||
<button
|
||||
class="w-full text-left px-4 py-3 text-sm font-mono text-dark-text hover:bg-dark-bg-secondary hover:text-accent-primary flex items-center gap-3 ${this.focusedIndex === 3 ? 'bg-dark-bg-secondary text-accent-primary' : ''}"
|
||||
class="w-full text-left px-4 py-3 text-sm font-mono text-primary hover:bg-secondary hover:text-primary flex items-center gap-3 ${this.focusedIndex === 3 ? 'bg-secondary text-primary' : ''}"
|
||||
@click=${() => this.handleAction(this.onMaxWidthToggle)}
|
||||
data-testid="mobile-width-settings"
|
||||
tabindex="${this.showMenu ? '0' : '-1'}"
|
||||
|
|
@ -218,9 +288,20 @@ export class MobileMenu extends LitElement {
|
|||
Width: ${this.widthLabel}
|
||||
</button>
|
||||
|
||||
<!-- Theme Toggle -->
|
||||
<button
|
||||
class="w-full text-left px-4 py-3 text-sm font-mono text-primary hover:bg-secondary hover:text-primary flex items-center gap-3 ${this.focusedIndex === 4 ? 'bg-secondary text-primary' : ''}"
|
||||
@click=${() => this.handleThemeChange()}
|
||||
data-testid="mobile-theme-toggle"
|
||||
tabindex="${this.showMenu ? '0' : '-1'}"
|
||||
>
|
||||
${this.getThemeIcon()}
|
||||
Theme: ${this.getThemeLabel()}
|
||||
</button>
|
||||
|
||||
<!-- Settings -->
|
||||
<button
|
||||
class="w-full text-left px-4 py-3 text-sm font-mono text-dark-text hover:bg-dark-bg-secondary hover:text-accent-primary flex items-center gap-3 ${this.focusedIndex === 4 ? 'bg-dark-bg-secondary text-accent-primary' : ''}"
|
||||
class="w-full text-left px-4 py-3 text-sm font-mono text-primary hover:bg-secondary hover:text-primary flex items-center gap-3 ${this.focusedIndex === 5 ? 'bg-secondary text-primary' : ''}"
|
||||
@click=${() => this.handleAction(this.onOpenSettings)}
|
||||
data-testid="mobile-settings"
|
||||
tabindex="${this.showMenu ? '0' : '-1'}"
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { authClient } from '../../services/auth-client.js';
|
|||
import { isAIAssistantSession, sendAIPrompt } from '../../utils/ai-sessions.js';
|
||||
import { createLogger } from '../../utils/logger.js';
|
||||
import './mobile-menu.js';
|
||||
import '../theme-toggle-icon.js';
|
||||
|
||||
const logger = createLogger('session-header');
|
||||
|
||||
|
|
@ -45,8 +46,16 @@ export class SessionHeader extends LitElement {
|
|||
@property({ type: Function }) onFontSizeChange?: (size: number) => void;
|
||||
@property({ type: Function }) onScreenshare?: () => void;
|
||||
@property({ type: Function }) onOpenSettings?: () => void;
|
||||
@property({ type: String }) currentTheme = 'system';
|
||||
@state() private isHovered = false;
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
// Load saved theme preference
|
||||
const saved = localStorage.getItem('vibetunnel-theme');
|
||||
this.currentTheme = (saved as 'light' | 'dark' | 'system') || 'system';
|
||||
}
|
||||
|
||||
private getStatusText(): string {
|
||||
if (!this.session) return '';
|
||||
if ('active' in this.session && this.session.active === false) {
|
||||
|
|
@ -56,17 +65,17 @@ export class SessionHeader extends LitElement {
|
|||
}
|
||||
|
||||
private getStatusColor(): string {
|
||||
if (!this.session) return 'text-dark-text-muted';
|
||||
if (!this.session) return 'text-muted';
|
||||
if ('active' in this.session && this.session.active === false) {
|
||||
return 'text-dark-text-muted';
|
||||
return 'text-muted';
|
||||
}
|
||||
return this.session.status === 'running' ? 'text-status-success' : 'text-status-warning';
|
||||
}
|
||||
|
||||
private getStatusDotColor(): string {
|
||||
if (!this.session) return 'bg-dark-text-muted';
|
||||
if (!this.session) return 'bg-muted';
|
||||
if ('active' in this.session && this.session.active === false) {
|
||||
return 'bg-dark-text-muted';
|
||||
return 'bg-muted';
|
||||
}
|
||||
return this.session.status === 'running' ? 'bg-status-success' : 'bg-status-warning';
|
||||
}
|
||||
|
|
@ -86,7 +95,7 @@ export class SessionHeader extends LitElement {
|
|||
return html`
|
||||
<!-- Enhanced Header with gradient background -->
|
||||
<div
|
||||
class="flex items-center justify-between border-b border-dark-border text-sm min-w-0 bg-gradient-to-r from-dark-bg-secondary to-dark-bg-tertiary px-4 py-2 shadow-sm"
|
||||
class="flex items-center justify-between border-b border-base text-sm min-w-0 bg-gradient-to-r from-secondary to-tertiary px-4 py-2 shadow-sm"
|
||||
style="padding-top: max(0.5rem, env(safe-area-inset-top)); padding-left: max(1rem, env(safe-area-inset-left)); padding-right: max(1rem, env(safe-area-inset-right));"
|
||||
>
|
||||
<div class="flex items-center gap-3 min-w-0 flex-1 overflow-hidden">
|
||||
|
|
@ -95,7 +104,7 @@ export class SessionHeader extends LitElement {
|
|||
this.showSidebarToggle && this.sidebarCollapsed
|
||||
? html`
|
||||
<button
|
||||
class="bg-dark-bg-elevated border border-dark-border rounded-lg p-2 font-mono text-dark-text-muted transition-all duration-200 hover:text-accent-primary hover:bg-dark-surface-hover hover:border-accent-primary hover:shadow-sm flex-shrink-0"
|
||||
class="bg-elevated border border-base rounded-lg p-2 font-mono text-muted transition-all duration-200 hover:text-primary hover:bg-hover hover:border-primary hover:shadow-sm flex-shrink-0"
|
||||
@click=${() => this.onSidebarToggle?.()}
|
||||
title="Show sidebar (⌘B)"
|
||||
aria-label="Show sidebar"
|
||||
|
|
@ -110,7 +119,7 @@ export class SessionHeader extends LitElement {
|
|||
|
||||
<!-- Create Session button (desktop only) -->
|
||||
<button
|
||||
class="hidden sm:flex bg-accent-primary bg-opacity-10 border border-accent-primary text-accent-primary rounded-lg p-2 font-mono transition-all duration-200 hover:bg-accent-primary hover:text-dark-bg hover:shadow-glow-primary-sm flex-shrink-0"
|
||||
class="hidden sm:flex bg-primary bg-opacity-10 border border-primary text-primary rounded-lg p-2 font-mono transition-all duration-200 hover:bg-primary hover:text-base hover:shadow-glow-primary-sm flex-shrink-0"
|
||||
@click=${() => this.onCreateSession?.()}
|
||||
title="Create New Session (⌘K)"
|
||||
data-testid="create-session-button"
|
||||
|
|
@ -136,7 +145,7 @@ export class SessionHeader extends LitElement {
|
|||
this.showBackButton
|
||||
? html`
|
||||
<button
|
||||
class="bg-dark-bg-elevated border border-dark-border rounded-lg px-3 py-1.5 font-mono text-xs text-dark-text-muted transition-all duration-200 hover:text-accent-primary hover:bg-dark-surface-hover hover:border-accent-primary hover:shadow-sm flex-shrink-0"
|
||||
class="bg-elevated border border-base rounded-lg px-3 py-1.5 font-mono text-xs text-muted transition-all duration-200 hover:text-primary hover:bg-hover hover:border-primary hover:shadow-sm flex-shrink-0"
|
||||
@click=${() => this.onBack?.()}
|
||||
>
|
||||
Back
|
||||
|
|
@ -144,8 +153,8 @@ export class SessionHeader extends LitElement {
|
|||
`
|
||||
: ''
|
||||
}
|
||||
<div class="text-dark-text min-w-0 flex-1 overflow-hidden">
|
||||
<div class="text-dark-text-bright font-medium text-xs sm:text-sm min-w-0 overflow-hidden">
|
||||
<div class="text-primary min-w-0 flex-1 overflow-hidden">
|
||||
<div class="text-bright font-medium text-xs sm:text-sm min-w-0 overflow-hidden">
|
||||
<div class="grid grid-cols-[1fr_auto] items-center gap-2 min-w-0" @mouseenter=${this.handleMouseEnter} @mouseleave=${this.handleMouseLeave}>
|
||||
<inline-edit
|
||||
class="min-w-0"
|
||||
|
|
@ -166,7 +175,7 @@ export class SessionHeader extends LitElement {
|
|||
isAIAssistantSession(this.session)
|
||||
? html`
|
||||
<button
|
||||
class="bg-transparent border-0 p-0 cursor-pointer transition-opacity duration-200 text-accent-primary magic-button flex-shrink-0 ${this.isHovered ? 'opacity-50 hover:opacity-100' : 'opacity-0'}"
|
||||
class="bg-transparent border-0 p-0 cursor-pointer transition-opacity duration-200 text-primary magic-button flex-shrink-0 ${this.isHovered ? 'opacity-50 hover:opacity-100' : 'opacity-0'}"
|
||||
@click=${(e: Event) => {
|
||||
e.stopPropagation();
|
||||
this.handleMagicButton();
|
||||
|
|
@ -212,8 +221,16 @@ export class SessionHeader extends LitElement {
|
|||
|
||||
<!-- Desktop buttons - hidden on mobile -->
|
||||
<div class="hidden sm:flex items-center gap-2">
|
||||
<!-- Theme toggle -->
|
||||
<theme-toggle-icon
|
||||
.theme=${this.currentTheme}
|
||||
@theme-changed=${(e: CustomEvent) => {
|
||||
this.currentTheme = e.detail.theme;
|
||||
}}
|
||||
></theme-toggle-icon>
|
||||
|
||||
<button
|
||||
class="bg-dark-bg-elevated border border-dark-border rounded-lg p-2 font-mono text-dark-text-muted transition-all duration-200 hover:text-accent-primary hover:bg-dark-surface-hover hover:border-accent-primary hover:shadow-sm flex-shrink-0"
|
||||
class="bg-elevated border border-base rounded-lg p-2 font-mono text-muted transition-all duration-200 hover:text-primary hover:bg-hover hover:border-primary hover:shadow-sm flex-shrink-0"
|
||||
@click=${(e: Event) => {
|
||||
e.stopPropagation();
|
||||
this.onOpenFileBrowser?.();
|
||||
|
|
@ -228,7 +245,7 @@ export class SessionHeader extends LitElement {
|
|||
</svg>
|
||||
</button>
|
||||
<button
|
||||
class="bg-dark-bg-elevated border border-dark-border rounded-lg p-2 font-mono text-dark-text-muted transition-all duration-200 hover:text-accent-primary hover:bg-dark-surface-hover hover:border-accent-primary hover:shadow-sm flex-shrink-0"
|
||||
class="bg-elevated border border-base rounded-lg p-2 font-mono text-muted transition-all duration-200 hover:text-primary hover:bg-hover hover:border-primary hover:shadow-sm flex-shrink-0"
|
||||
@click=${() => this.onScreenshare?.()}
|
||||
title="Start Screenshare"
|
||||
>
|
||||
|
|
@ -240,7 +257,7 @@ export class SessionHeader extends LitElement {
|
|||
</svg>
|
||||
</button>
|
||||
<button
|
||||
class="bg-dark-bg-elevated border border-dark-border rounded-lg px-3 py-2 font-mono text-xs text-dark-text-muted transition-all duration-200 hover:text-accent-primary hover:bg-dark-surface-hover hover:border-accent-primary hover:shadow-sm flex-shrink-0 width-selector-button"
|
||||
class="bg-elevated border border-base rounded-lg px-3 py-2 font-mono text-xs text-muted transition-all duration-200 hover:text-primary hover:bg-hover hover:border-primary hover:shadow-sm flex-shrink-0 width-selector-button"
|
||||
@click=${() => this.onMaxWidthToggle?.()}
|
||||
title="${this.widthTooltip}"
|
||||
>
|
||||
|
|
@ -259,6 +276,7 @@ export class SessionHeader extends LitElement {
|
|||
.onMaxWidthToggle=${this.onMaxWidthToggle}
|
||||
.onOpenSettings=${this.onOpenSettings}
|
||||
.onCreateSession=${this.onCreateSession}
|
||||
.currentTheme=${this.currentTheme}
|
||||
></mobile-menu>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ export class TerminalDimensions extends LitElement {
|
|||
|
||||
return html`
|
||||
<span
|
||||
class="hidden sm:inline text-dark-text-muted text-xs opacity-60"
|
||||
class="hidden sm:inline text-muted text-xs opacity-60"
|
||||
style="font-size: 10px; line-height: 1;"
|
||||
>
|
||||
${this.cols}×${this.rows}
|
||||
|
|
|
|||
|
|
@ -60,15 +60,15 @@ export class WidthSelector extends LitElement {
|
|||
|
||||
<!-- Width selector modal -->
|
||||
<div
|
||||
class="width-selector-container fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-dark-bg-elevated border border-dark-border rounded-lg shadow-elevated min-w-[280px] max-w-[90vw] animate-fade-in"
|
||||
class="width-selector-container fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-surface border border-border rounded-lg shadow-elevated min-w-[280px] max-w-[90vw] animate-fade-in"
|
||||
style="z-index: ${Z_INDEX.WIDTH_SELECTOR_DROPDOWN};"
|
||||
>
|
||||
<div class="p-4">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<div class="text-sm font-semibold text-dark-text">Terminal Width</div>
|
||||
<div class="text-sm font-semibold text-text-bright">Terminal Width</div>
|
||||
<!-- Close button for mobile -->
|
||||
<button
|
||||
class="sm:hidden p-1.5 rounded-md text-dark-text-muted hover:text-dark-text hover:bg-dark-surface-hover transition-all duration-200"
|
||||
class="sm:hidden p-1.5 rounded-md text-text-muted hover:text-text hover:bg-surface transition-all duration-200"
|
||||
@click=${() => this.onClose?.()}
|
||||
aria-label="Close width selector"
|
||||
>
|
||||
|
|
@ -83,18 +83,18 @@ export class WidthSelector extends LitElement {
|
|||
class="w-full text-left px-3 py-2 text-sm rounded-md flex justify-between items-center transition-all duration-200
|
||||
${
|
||||
this.terminalMaxCols === width.value
|
||||
? 'bg-accent-primary bg-opacity-10 text-accent-primary border border-accent-primary'
|
||||
: 'text-dark-text hover:bg-dark-surface-hover hover:text-dark-text-bright border border-transparent'
|
||||
? 'bg-primary bg-opacity-20 text-primary font-semibold border border-primary'
|
||||
: 'text-text hover:bg-surface hover:text-text-bright border border-transparent'
|
||||
}"
|
||||
@click=${() => this.onWidthSelect?.(width.value)}
|
||||
>
|
||||
<span class="font-mono font-medium">${width.label}</span>
|
||||
<span class="text-dark-text-muted text-xs ml-4">${width.description}</span>
|
||||
<span class="text-text-muted text-xs ml-4">${width.description}</span>
|
||||
</button>
|
||||
`
|
||||
)}
|
||||
<div class="border-t border-dark-border mt-3 pt-3">
|
||||
<div class="text-sm font-semibold text-dark-text mb-2">Custom (20-500)</div>
|
||||
<div class="border-t border-border mt-3 pt-3">
|
||||
<div class="text-sm font-semibold text-text-bright mb-2">Custom (20-500)</div>
|
||||
<div class="flex gap-2">
|
||||
<input
|
||||
type="number"
|
||||
|
|
@ -105,7 +105,7 @@ export class WidthSelector extends LitElement {
|
|||
@input=${this.handleCustomWidthInput}
|
||||
@keydown=${this.handleCustomWidthKeydown}
|
||||
@click=${(e: Event) => e.stopPropagation()}
|
||||
class="flex-1 bg-dark-bg-secondary border border-dark-border rounded-md px-3 py-2 text-sm font-mono text-dark-text placeholder:text-dark-text-dim focus:border-accent-primary focus:shadow-glow-primary-sm transition-all"
|
||||
class="flex-1 bg-bg-secondary border border-border rounded-md px-3 py-2 text-sm font-mono text-text placeholder:text-text-dim focus:border-primary focus:shadow-glow-sm transition-all"
|
||||
/>
|
||||
<button
|
||||
class="px-4 py-2 rounded-md text-sm font-medium transition-all duration-200
|
||||
|
|
@ -113,8 +113,8 @@ export class WidthSelector extends LitElement {
|
|||
!this.customWidth ||
|
||||
Number.parseInt(this.customWidth) < 20 ||
|
||||
Number.parseInt(this.customWidth) > 500
|
||||
? 'bg-dark-bg-secondary border border-dark-border text-dark-text-muted cursor-not-allowed'
|
||||
: 'bg-accent-primary text-dark-bg hover:bg-accent-primary-light active:scale-95'
|
||||
? 'bg-bg-secondary border border-border text-text-muted cursor-not-allowed'
|
||||
: 'bg-primary text-text-bright hover:bg-primary-hover active:scale-95'
|
||||
}"
|
||||
@click=${this.handleCustomWidthSubmit}
|
||||
?disabled=${
|
||||
|
|
@ -127,15 +127,15 @@ export class WidthSelector extends LitElement {
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-t border-dark-border mt-3 pt-3">
|
||||
<div class="text-sm font-semibold text-dark-text mb-3">Font Size</div>
|
||||
<div class="border-t border-border mt-3 pt-3">
|
||||
<div class="text-sm font-semibold text-text-bright mb-3">Font Size</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<button
|
||||
class="w-10 h-10 rounded-md border transition-all duration-200 flex items-center justify-center
|
||||
${
|
||||
this.terminalFontSize <= 8
|
||||
? 'border-dark-border bg-dark-bg-secondary text-dark-text-muted cursor-not-allowed'
|
||||
: 'border-dark-border bg-dark-bg-elevated text-dark-text hover:border-accent-primary hover:text-accent-primary active:scale-95'
|
||||
? 'border-border bg-bg-secondary text-text-muted cursor-not-allowed'
|
||||
: 'border-border bg-bg-elevated text-text hover:border-primary hover:text-primary active:scale-95'
|
||||
}"
|
||||
@click=${() => this.onFontSizeChange?.(this.terminalFontSize - 1)}
|
||||
?disabled=${this.terminalFontSize <= 8}
|
||||
|
|
@ -144,15 +144,15 @@ export class WidthSelector extends LitElement {
|
|||
<path fill-rule="evenodd" d="M3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
</button>
|
||||
<span class="font-mono text-lg font-medium text-dark-text min-w-[60px] text-center">
|
||||
<span class="font-mono text-lg font-medium text-text-bright min-w-[60px] text-center">
|
||||
${this.terminalFontSize}px
|
||||
</span>
|
||||
<button
|
||||
class="w-10 h-10 rounded-md border transition-all duration-200 flex items-center justify-center
|
||||
${
|
||||
this.terminalFontSize >= 32
|
||||
? 'border-dark-border bg-dark-bg-secondary text-dark-text-muted cursor-not-allowed'
|
||||
: 'border-dark-border bg-dark-bg-elevated text-dark-text hover:border-accent-primary hover:text-accent-primary active:scale-95'
|
||||
? 'border-border bg-bg-secondary text-text-muted cursor-not-allowed'
|
||||
: 'border-border bg-bg-elevated text-text hover:border-primary hover:text-primary active:scale-95'
|
||||
}"
|
||||
@click=${() => this.onFontSizeChange?.(this.terminalFontSize + 1)}
|
||||
?disabled=${this.terminalFontSize >= 32}
|
||||
|
|
@ -165,8 +165,8 @@ export class WidthSelector extends LitElement {
|
|||
class="ml-auto px-3 py-2 rounded-md text-sm transition-all duration-200
|
||||
${
|
||||
this.terminalFontSize === 14
|
||||
? 'text-dark-text-muted cursor-not-allowed'
|
||||
: 'text-dark-text-muted hover:text-dark-text hover:bg-dark-surface-hover'
|
||||
? 'text-text-muted cursor-not-allowed'
|
||||
: 'text-text-muted hover:text-text hover:bg-surface'
|
||||
}"
|
||||
@click=${() => this.onFontSizeChange?.(14)}
|
||||
?disabled=${this.terminalFontSize === 14}
|
||||
|
|
|
|||
|
|
@ -16,14 +16,14 @@ export class SidebarHeader extends HeaderBase {
|
|||
|
||||
return html`
|
||||
<div
|
||||
class="app-header sidebar-header bg-gradient-to-r from-dark-bg-secondary to-dark-bg-tertiary border-b border-dark-border px-4 py-2 shadow-sm"
|
||||
class="app-header sidebar-header bg-gradient-to-r from-bg-secondary to-bg-tertiary border-b border-border px-4 py-2 shadow-sm"
|
||||
style="padding-top: max(0.625rem, env(safe-area-inset-top));"
|
||||
>
|
||||
<!-- Compact layout for sidebar -->
|
||||
<div class="flex items-center gap-2">
|
||||
<!-- Toggle button -->
|
||||
<button
|
||||
class="p-2 text-dark-text-muted hover:text-dark-text rounded-lg hover:bg-dark-bg-tertiary transition-all duration-200 flex-shrink-0"
|
||||
class="p-2 text-text-muted hover:text-text rounded-lg hover:bg-bg-tertiary transition-all duration-200 flex-shrink-0"
|
||||
@click=${() => this.dispatchEvent(new CustomEvent('toggle-sidebar'))}
|
||||
title="Collapse sidebar (⌘B)"
|
||||
aria-label="Collapse sidebar"
|
||||
|
|
@ -44,11 +44,11 @@ export class SidebarHeader extends HeaderBase {
|
|||
<terminal-icon size="20"></terminal-icon>
|
||||
<div class="min-w-0">
|
||||
<h1
|
||||
class="text-sm font-bold text-accent-primary font-mono group-hover:underline truncate"
|
||||
class="text-sm font-bold text-primary font-mono group-hover:underline truncate"
|
||||
>
|
||||
VibeTunnel
|
||||
</h1>
|
||||
<p class="text-dark-text-muted text-xs font-mono">
|
||||
<p class="text-text-muted text-xs font-mono">
|
||||
${runningSessions.length} ${runningSessions.length === 1 ? 'session' : 'sessions'}
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -58,7 +58,7 @@ export class SidebarHeader extends HeaderBase {
|
|||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<!-- Create Session button with primary styling -->
|
||||
<button
|
||||
class="p-2 text-accent-primary bg-accent-primary bg-opacity-10 border border-accent-primary hover:bg-opacity-20 rounded-md transition-all duration-200 flex-shrink-0"
|
||||
class="p-2 text-primary bg-primary bg-opacity-10 border border-primary hover:bg-opacity-20 rounded-md transition-all duration-200 flex-shrink-0"
|
||||
@click=${this.handleCreateSession}
|
||||
title="Create New Session (⌘K)"
|
||||
data-testid="create-session-button"
|
||||
|
|
@ -85,7 +85,7 @@ export class SidebarHeader extends HeaderBase {
|
|||
return html`
|
||||
<div class="user-menu-container relative">
|
||||
<button
|
||||
class="font-mono text-xs px-2 py-1 text-dark-text-muted hover:text-dark-text rounded border border-dark-border hover:bg-dark-bg-tertiary transition-all duration-200"
|
||||
class="font-mono text-xs px-2 py-1 text-text-muted hover:text-text rounded border border-border hover:bg-bg-tertiary transition-all duration-200"
|
||||
@click=${this.toggleUserMenu}
|
||||
title="User menu"
|
||||
>
|
||||
|
|
@ -99,15 +99,15 @@ export class SidebarHeader extends HeaderBase {
|
|||
this.showUserMenu
|
||||
? html`
|
||||
<div
|
||||
class="absolute right-0 top-full mt-1 bg-dark-surface border border-dark-border rounded-lg shadow-lg py-1 z-50 min-w-32"
|
||||
class="absolute right-0 top-full mt-1 bg-surface border border-border rounded-lg shadow-lg py-1 z-50 min-w-32"
|
||||
>
|
||||
<div
|
||||
class="px-3 py-1.5 text-xs text-dark-text-muted border-b border-dark-border font-mono"
|
||||
class="px-3 py-1.5 text-xs text-text-muted border-b border-border font-mono"
|
||||
>
|
||||
${this.currentUser}
|
||||
</div>
|
||||
<button
|
||||
class="w-full text-left px-3 py-1.5 text-xs font-mono text-status-warning hover:bg-dark-bg-secondary hover:text-status-error"
|
||||
class="w-full text-left px-3 py-1.5 text-xs font-mono text-status-warning hover:bg-bg-secondary hover:text-status-error"
|
||||
@click=${this.handleLogout}
|
||||
>
|
||||
Logout
|
||||
|
|
|
|||
|
|
@ -146,15 +146,15 @@ export class SSHKeyManager extends LitElement {
|
|||
<modal-wrapper
|
||||
.visible=${this.visible}
|
||||
modalClass=""
|
||||
contentClass="modal-content modal-positioned bg-dark-bg border border-dark-border rounded-lg p-6 w-full max-w-4xl max-h-[80vh] overflow-y-auto"
|
||||
contentClass="modal-content modal-positioned bg-bg border border-base rounded-lg p-6 w-full max-w-4xl max-h-[80vh] overflow-y-auto"
|
||||
ariaLabel="SSH Key Manager"
|
||||
@close=${this.handleClose}
|
||||
.closeOnBackdrop=${true}
|
||||
.closeOnEscape=${true}
|
||||
>
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 class="text-xl font-mono text-dark-text">SSH Key Manager</h2>
|
||||
<button @click=${this.handleClose} class="text-dark-text-muted hover:text-dark-text">
|
||||
<h2 class="text-xl font-mono text-primary">SSH Key Manager</h2>
|
||||
<button @click=${this.handleClose} class="text-muted hover:text-primary">
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -162,13 +162,13 @@ export class SSHKeyManager extends LitElement {
|
|||
${
|
||||
this.error
|
||||
? html`
|
||||
<div class="bg-status-error text-dark-bg px-4 py-2 rounded mb-4 font-mono text-sm">
|
||||
<div class="bg-status-error text-bg px-4 py-2 rounded mb-4 font-mono text-sm">
|
||||
${this.error}
|
||||
<button
|
||||
@click=${() => {
|
||||
this.error = '';
|
||||
}}
|
||||
class="ml-2 text-dark-bg hover:text-dark-text"
|
||||
class="ml-2 text-bg hover:text-primary"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
|
|
@ -180,14 +180,14 @@ export class SSHKeyManager extends LitElement {
|
|||
this.success
|
||||
? html`
|
||||
<div
|
||||
class="bg-status-success text-dark-bg px-4 py-2 rounded mb-4 font-mono text-sm"
|
||||
class="bg-status-success text-bg px-4 py-2 rounded mb-4 font-mono text-sm"
|
||||
>
|
||||
${this.success}
|
||||
<button
|
||||
@click=${() => {
|
||||
this.success = '';
|
||||
}}
|
||||
class="ml-2 text-dark-bg hover:text-dark-text"
|
||||
class="ml-2 text-bg hover:text-primary"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
|
|
@ -198,7 +198,7 @@ export class SSHKeyManager extends LitElement {
|
|||
|
||||
<div class="mb-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="font-mono text-lg text-dark-text">SSH Keys</h3>
|
||||
<h3 class="font-mono text-lg text-primary">SSH Keys</h3>
|
||||
<button
|
||||
@click=${() => {
|
||||
this.showAddForm = !this.showAddForm;
|
||||
|
|
@ -215,8 +215,8 @@ export class SSHKeyManager extends LitElement {
|
|||
? html`
|
||||
<div class="space-y-6 mb-4">
|
||||
<!-- Generate New Key Section -->
|
||||
<div class="bg-dark-surface border border-dark-border rounded p-4">
|
||||
<h4 class="text-dark-text font-mono text-lg mb-4 flex items-center gap-2">
|
||||
<div class="bg-surface border border-base rounded p-4">
|
||||
<h4 class="text-primary font-mono text-lg mb-4 flex items-center gap-2">
|
||||
🔑 Generate New SSH Key
|
||||
</h4>
|
||||
|
||||
|
|
@ -239,7 +239,7 @@ export class SSHKeyManager extends LitElement {
|
|||
<div>
|
||||
<label class="form-label">Algorithm</label>
|
||||
<div
|
||||
class="input-field bg-dark-bg-secondary text-dark-text-muted cursor-not-allowed"
|
||||
class="input-field bg-secondary text-muted cursor-not-allowed"
|
||||
>
|
||||
Ed25519 (recommended)
|
||||
</div>
|
||||
|
|
@ -258,7 +258,7 @@ export class SSHKeyManager extends LitElement {
|
|||
}}
|
||||
?disabled=${this.loading}
|
||||
/>
|
||||
<p class="text-dark-text-muted text-xs mt-1">
|
||||
<p class="text-muted text-xs mt-1">
|
||||
💡 Leave empty for unencrypted key. Password is required when using the
|
||||
key for signing.
|
||||
</p>
|
||||
|
|
@ -273,8 +273,8 @@ export class SSHKeyManager extends LitElement {
|
|||
</div>
|
||||
|
||||
<!-- Import Existing Key Section -->
|
||||
<div class="bg-dark-surface border border-dark-border rounded p-4">
|
||||
<h4 class="text-dark-text font-mono text-lg mb-4 flex items-center gap-2">
|
||||
<div class="bg-surface border border-base rounded p-4">
|
||||
<h4 class="text-primary font-mono text-lg mb-4 flex items-center gap-2">
|
||||
📁 Import Existing SSH Key
|
||||
</h4>
|
||||
|
||||
|
|
@ -308,7 +308,7 @@ export class SSHKeyManager extends LitElement {
|
|||
}}
|
||||
?disabled=${this.loading}
|
||||
></textarea>
|
||||
<p class="text-dark-text-muted text-xs mt-1">
|
||||
<p class="text-muted text-xs mt-1">
|
||||
💡 If the key is password-protected, you'll be prompted for the password
|
||||
when using it for authentication.
|
||||
</p>
|
||||
|
|
@ -336,26 +336,26 @@ export class SSHKeyManager extends LitElement {
|
|||
${
|
||||
this.showInstructions && this.instructionsKeyId
|
||||
? html`
|
||||
<div class="bg-dark-surface border border-dark-border rounded p-4 mb-6">
|
||||
<div class="bg-surface border border-base rounded p-4 mb-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h4 class="text-dark-text font-mono text-lg">Setup Instructions</h4>
|
||||
<h4 class="text-primary font-mono text-lg">Setup Instructions</h4>
|
||||
<button
|
||||
@click=${() => {
|
||||
this.showInstructions = false;
|
||||
}}
|
||||
class="text-dark-text-muted hover:text-dark-text"
|
||||
class="text-muted hover:text-primary"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<div class="bg-dark-bg border border-dark-border rounded p-3">
|
||||
<p class="text-dark-text-muted text-xs mb-2">
|
||||
<div class="bg-bg border border-base rounded p-3">
|
||||
<p class="text-muted text-xs mb-2">
|
||||
1. Add the public key to your authorized_keys file:
|
||||
</p>
|
||||
<div class="relative">
|
||||
<pre
|
||||
class="bg-dark-bg-secondary p-2 rounded text-xs overflow-x-auto text-dark-text pr-20"
|
||||
class="bg-secondary p-2 rounded text-xs overflow-x-auto text-primary pr-20"
|
||||
>
|
||||
echo "${this.sshAgent.getPublicKey(this.instructionsKeyId)}" >> ~/.ssh/authorized_keys</pre
|
||||
>
|
||||
|
|
@ -373,11 +373,11 @@ echo "${this.sshAgent.getPublicKey(this.instructionsKeyId)}" >> ~/.ssh/authorize
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-dark-bg border border-dark-border rounded p-3">
|
||||
<p class="text-dark-text-muted text-xs mb-2">2. Or copy the public key:</p>
|
||||
<div class="bg-bg border border-base rounded p-3">
|
||||
<p class="text-muted text-xs mb-2">2. Or copy the public key:</p>
|
||||
<div class="relative">
|
||||
<pre
|
||||
class="bg-dark-bg-secondary p-2 rounded text-xs overflow-x-auto text-dark-text pr-20"
|
||||
class="bg-secondary p-2 rounded text-xs overflow-x-auto text-primary pr-20"
|
||||
>
|
||||
${this.sshAgent.getPublicKey(this.instructionsKeyId)}</pre
|
||||
>
|
||||
|
|
@ -396,7 +396,7 @@ ${this.sshAgent.getPublicKey(this.instructionsKeyId)}</pre
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-dark-text-muted text-xs font-mono">
|
||||
<p class="text-muted text-xs font-mono">
|
||||
💡 Tip: Make sure ~/.ssh/authorized_keys has correct permissions (600)
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -410,7 +410,7 @@ ${this.sshAgent.getPublicKey(this.instructionsKeyId)}</pre
|
|||
${
|
||||
this.keys.length === 0
|
||||
? html`
|
||||
<div class="text-center py-8 text-dark-text-muted">
|
||||
<div class="text-center py-8 text-muted">
|
||||
<p class="font-mono text-lg mb-2">No SSH keys found</p>
|
||||
<p class="text-sm">Generate or import a key to get started</p>
|
||||
</div>
|
||||
|
|
@ -421,7 +421,7 @@ ${this.sshAgent.getPublicKey(this.instructionsKeyId)}</pre
|
|||
<div class="flex items-start justify-between">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<h4 class="font-mono font-semibold text-dark-text">${key.name}</h4>
|
||||
<h4 class="font-mono font-semibold text-primary">${key.name}</h4>
|
||||
<span class="badge badge-ed25519">${key.algorithm}</span>
|
||||
${
|
||||
key.encrypted
|
||||
|
|
@ -429,7 +429,7 @@ ${this.sshAgent.getPublicKey(this.instructionsKeyId)}</pre
|
|||
: ''
|
||||
}
|
||||
</div>
|
||||
<div class="text-sm text-dark-text-muted font-mono space-y-1">
|
||||
<div class="text-sm text-muted font-mono space-y-1">
|
||||
<div>ID: ${key.id}</div>
|
||||
<div>Fingerprint: ${key.fingerprint}</div>
|
||||
<div>Created: ${new Date(key.createdAt).toLocaleString()}</div>
|
||||
|
|
@ -445,7 +445,7 @@ ${this.sshAgent.getPublicKey(this.instructionsKeyId)}</pre
|
|||
</button>
|
||||
<button
|
||||
@click=${() => this.handleRemoveKey(key.id, key.name)}
|
||||
class="btn-ghost text-xs text-status-error hover:bg-status-error hover:text-dark-bg"
|
||||
class="btn-ghost text-xs text-status-error hover:bg-status-error hover:text-bg"
|
||||
title="Remove Key"
|
||||
>
|
||||
🗑️
|
||||
|
|
|
|||
|
|
@ -21,9 +21,9 @@ export class TerminalIcon extends LitElement {
|
|||
.terminal-icon {
|
||||
border-radius: 20%;
|
||||
box-shadow:
|
||||
0 2px 8px rgba(0, 0, 0, 0.3),
|
||||
0 1px 3px rgba(0, 0, 0, 0.2);
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
0 2px 8px rgb(var(--color-bg-base) / 0.3),
|
||||
0 1px 3px rgb(var(--color-bg-base) / 0.2);
|
||||
background: rgb(var(--color-text-bright) / 0.05);
|
||||
padding: 2px;
|
||||
}
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -272,14 +272,14 @@ export class TerminalQuickKeys extends LitElement {
|
|||
|
||||
/* The actual bar with buttons */
|
||||
.quick-keys-bar {
|
||||
background: #0a0a0a;
|
||||
border-top: 1px solid #2a2a2a;
|
||||
background: rgb(var(--color-bg-base));
|
||||
border-top: 1px solid rgb(var(--color-border-base));
|
||||
padding: 0.5rem 0.25rem;
|
||||
/* Prevent iOS from adding its own styling */
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
/* Add shadow for visibility */
|
||||
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.5);
|
||||
box-shadow: 0 -2px 10px rgb(var(--color-bg-base) / 0.5);
|
||||
}
|
||||
|
||||
/* Quick key buttons */
|
||||
|
|
@ -294,23 +294,23 @@ export class TerminalQuickKeys extends LitElement {
|
|||
|
||||
/* Modifier key styling */
|
||||
.modifier-key {
|
||||
background-color: #141414;
|
||||
border-color: #444;
|
||||
background-color: rgb(var(--color-bg-tertiary));
|
||||
border-color: rgb(var(--color-border-base));
|
||||
}
|
||||
|
||||
.modifier-key:hover {
|
||||
background-color: #1f1f1f;
|
||||
background-color: rgb(var(--color-bg-secondary));
|
||||
}
|
||||
|
||||
/* Active modifier styling */
|
||||
.modifier-key.active {
|
||||
background-color: rgb(59, 130, 246);
|
||||
border-color: rgb(59, 130, 246);
|
||||
color: white;
|
||||
background-color: rgb(var(--color-primary));
|
||||
border-color: rgb(var(--color-primary));
|
||||
color: rgb(var(--color-text-bright));
|
||||
}
|
||||
|
||||
.modifier-key.active:hover {
|
||||
background-color: rgb(37, 99, 235);
|
||||
background-color: rgb(var(--color-primary-hover));
|
||||
}
|
||||
|
||||
/* Arrow key styling */
|
||||
|
|
@ -321,23 +321,23 @@ export class TerminalQuickKeys extends LitElement {
|
|||
|
||||
/* Combo key styling (like ^C, ^Z) */
|
||||
.combo-key {
|
||||
background-color: #141414;
|
||||
border-color: #555;
|
||||
background-color: rgb(var(--color-bg-tertiary));
|
||||
border-color: rgb(var(--color-border-accent));
|
||||
}
|
||||
|
||||
.combo-key:hover {
|
||||
background-color: #1f1f1f;
|
||||
background-color: rgb(var(--color-bg-secondary));
|
||||
}
|
||||
|
||||
/* Special key styling (like ABC) */
|
||||
.special-key {
|
||||
background-color: rgb(16, 185, 129);
|
||||
border-color: rgb(16, 185, 129);
|
||||
color: white;
|
||||
background-color: rgb(var(--color-primary));
|
||||
border-color: rgb(var(--color-primary));
|
||||
color: rgb(var(--color-text-bright));
|
||||
}
|
||||
|
||||
.special-key:hover {
|
||||
background-color: rgb(5, 150, 105);
|
||||
background-color: rgb(var(--color-primary-hover));
|
||||
}
|
||||
|
||||
/* Function key styling */
|
||||
|
|
@ -351,22 +351,22 @@ export class TerminalQuickKeys extends LitElement {
|
|||
|
||||
/* Toggle button styling */
|
||||
.toggle-key {
|
||||
background-color: #1f1f1f;
|
||||
border-color: #666;
|
||||
background-color: rgb(var(--color-bg-secondary));
|
||||
border-color: rgb(var(--color-border-accent));
|
||||
}
|
||||
|
||||
.toggle-key:hover {
|
||||
background-color: #2a2a2a;
|
||||
background-color: rgb(var(--color-bg-tertiary));
|
||||
}
|
||||
|
||||
.toggle-key.active {
|
||||
background-color: rgb(16, 185, 129);
|
||||
border-color: rgb(16, 185, 129);
|
||||
color: white;
|
||||
background-color: rgb(var(--color-primary));
|
||||
border-color: rgb(var(--color-primary));
|
||||
color: rgb(var(--color-text-bright));
|
||||
}
|
||||
|
||||
.toggle-key.active:hover {
|
||||
background-color: rgb(5, 150, 105);
|
||||
background-color: rgb(var(--color-primary-hover));
|
||||
}
|
||||
|
||||
/* Ctrl shortcut button styling */
|
||||
|
|
@ -427,7 +427,7 @@ export class TerminalQuickKeys extends LitElement {
|
|||
<button
|
||||
type="button"
|
||||
tabindex="-1"
|
||||
class="quick-key-btn flex-1 min-w-0 px-0.5 ${this.isLandscape ? 'py-1' : 'py-1.5'} bg-dark-bg-tertiary text-dark-text text-xs font-mono rounded border border-dark-border hover:bg-dark-surface hover:border-accent-green transition-all whitespace-nowrap ${modifier ? 'modifier-key' : ''} ${arrow ? 'arrow-key' : ''} ${toggle ? 'toggle-key' : ''} ${toggle && ((key === 'CtrlExpand' && this.showCtrlKeys) || (key === 'F' && this.showFunctionKeys)) ? 'active' : ''} ${modifier && key === 'Option' && this.activeModifiers.has('Option') ? 'active' : ''}"
|
||||
class="quick-key-btn flex-1 min-w-0 px-0.5 ${this.isLandscape ? 'py-1' : 'py-1.5'} bg-tertiary text-primary text-xs font-mono rounded border border-base hover:bg-surface hover:border-primary transition-all whitespace-nowrap ${modifier ? 'modifier-key' : ''} ${arrow ? 'arrow-key' : ''} ${toggle ? 'toggle-key' : ''} ${toggle && ((key === 'CtrlExpand' && this.showCtrlKeys) || (key === 'F' && this.showFunctionKeys)) ? 'active' : ''} ${modifier && key === 'Option' && this.activeModifiers.has('Option') ? 'active' : ''}"
|
||||
@mousedown=${(e: Event) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
|
@ -479,7 +479,7 @@ export class TerminalQuickKeys extends LitElement {
|
|||
<button
|
||||
type="button"
|
||||
tabindex="-1"
|
||||
class="ctrl-shortcut-btn flex-1 min-w-0 px-0.5 ${this.isLandscape ? 'py-1' : 'py-1.5'} bg-dark-bg-tertiary text-dark-text text-xs font-mono rounded border border-dark-border hover:bg-dark-surface hover:border-accent-green transition-all whitespace-nowrap ${combo ? 'combo-key' : ''} ${special ? 'special-key' : ''}"
|
||||
class="ctrl-shortcut-btn flex-1 min-w-0 px-0.5 ${this.isLandscape ? 'py-1' : 'py-1.5'} bg-tertiary text-primary text-xs font-mono rounded border border-base hover:bg-surface hover:border-primary transition-all whitespace-nowrap ${combo ? 'combo-key' : ''} ${special ? 'special-key' : ''}"
|
||||
@mousedown=${(e: Event) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
|
@ -514,7 +514,7 @@ export class TerminalQuickKeys extends LitElement {
|
|||
<button
|
||||
type="button"
|
||||
tabindex="-1"
|
||||
class="func-key-btn flex-1 min-w-0 px-0.5 ${this.isLandscape ? 'py-1' : 'py-1.5'} bg-dark-bg-tertiary text-dark-text text-xs font-mono rounded border border-dark-border hover:bg-dark-surface hover:border-accent-green transition-all whitespace-nowrap"
|
||||
class="func-key-btn flex-1 min-w-0 px-0.5 ${this.isLandscape ? 'py-1' : 'py-1.5'} bg-tertiary text-primary text-xs font-mono rounded border border-base hover:bg-surface hover:border-primary transition-all whitespace-nowrap"
|
||||
@mousedown=${(e: Event) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
|
@ -548,7 +548,7 @@ export class TerminalQuickKeys extends LitElement {
|
|||
<button
|
||||
type="button"
|
||||
tabindex="-1"
|
||||
class="quick-key-btn flex-1 min-w-0 px-0.5 ${this.isLandscape ? 'py-1' : 'py-1.5'} bg-dark-bg-tertiary text-dark-text text-xs font-mono rounded border border-dark-border hover:bg-dark-surface hover:border-accent-green transition-all whitespace-nowrap ${modifier ? 'modifier-key' : ''} ${combo ? 'combo-key' : ''} ${special ? 'special-key' : ''} ${toggle ? 'toggle-key' : ''} ${toggle && this.showFunctionKeys ? 'active' : ''}"
|
||||
class="quick-key-btn flex-1 min-w-0 px-0.5 ${this.isLandscape ? 'py-1' : 'py-1.5'} bg-tertiary text-primary text-xs font-mono rounded border border-base hover:bg-surface hover:border-primary transition-all whitespace-nowrap ${modifier ? 'modifier-key' : ''} ${combo ? 'combo-key' : ''} ${special ? 'special-key' : ''} ${toggle ? 'toggle-key' : ''} ${toggle && this.showFunctionKeys ? 'active' : ''}"
|
||||
@mousedown=${(e: Event) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
|
@ -583,7 +583,7 @@ export class TerminalQuickKeys extends LitElement {
|
|||
<button
|
||||
type="button"
|
||||
tabindex="-1"
|
||||
class="quick-key-btn flex-1 min-w-0 px-0.5 ${this.isLandscape ? 'py-0.5' : 'py-1'} bg-dark-bg-tertiary text-dark-text text-xs font-mono rounded border border-dark-border hover:bg-dark-surface hover:border-accent-green transition-all whitespace-nowrap ${modifier ? 'modifier-key' : ''} ${combo ? 'combo-key' : ''} ${special ? 'special-key' : ''} ${modifier && key === 'Option' && this.activeModifiers.has('Option') ? 'active' : ''}"
|
||||
class="quick-key-btn flex-1 min-w-0 px-0.5 ${this.isLandscape ? 'py-0.5' : 'py-1'} bg-tertiary text-primary text-xs font-mono rounded border border-base hover:bg-surface hover:border-primary transition-all whitespace-nowrap ${modifier ? 'modifier-key' : ''} ${combo ? 'combo-key' : ''} ${special ? 'special-key' : ''} ${modifier && key === 'Option' && this.activeModifiers.has('Option') ? 'active' : ''}"
|
||||
@mousedown=${(e: Event) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
|
|
|||
|
|
@ -135,12 +135,26 @@ export class Terminal extends LitElement {
|
|||
}
|
||||
}
|
||||
|
||||
private themeObserver?: MutationObserver;
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
// Check for debug mode
|
||||
this.debugMode = new URLSearchParams(window.location.search).has('debug');
|
||||
|
||||
// Watch for theme changes
|
||||
this.themeObserver = new MutationObserver(() => {
|
||||
if (this.terminal) {
|
||||
this.terminal.options.theme = this.getTerminalTheme();
|
||||
}
|
||||
});
|
||||
|
||||
this.themeObserver.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ['data-theme'],
|
||||
});
|
||||
|
||||
// Restore user override preference if we have a sessionId
|
||||
if (this.sessionId) {
|
||||
try {
|
||||
|
|
@ -207,6 +221,9 @@ export class Terminal extends LitElement {
|
|||
|
||||
disconnectedCallback() {
|
||||
this.cleanup();
|
||||
if (this.themeObserver) {
|
||||
this.themeObserver.disconnect();
|
||||
}
|
||||
super.disconnectedCallback();
|
||||
}
|
||||
|
||||
|
|
@ -335,6 +352,67 @@ export class Terminal extends LitElement {
|
|||
return changed;
|
||||
}
|
||||
|
||||
private getTerminalTheme() {
|
||||
const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
|
||||
|
||||
if (isDark) {
|
||||
// Dark theme (original colors)
|
||||
return {
|
||||
background: '#1e1e1e',
|
||||
foreground: '#d4d4d4',
|
||||
cursor: 'rgb(var(--color-primary))',
|
||||
cursorAccent: '#1e1e1e',
|
||||
// Standard 16 colors (0-15) - using proper xterm colors
|
||||
black: '#000000',
|
||||
red: '#cd0000',
|
||||
green: '#00cd00',
|
||||
yellow: '#cdcd00',
|
||||
blue: '#0000ee',
|
||||
magenta: '#cd00cd',
|
||||
cyan: '#00cdcd',
|
||||
white: '#e5e5e5',
|
||||
brightBlack: '#7f7f7f',
|
||||
brightRed: '#ff0000',
|
||||
brightGreen: '#00ff00',
|
||||
brightYellow: '#ffff00',
|
||||
brightBlue: '#5c5cff',
|
||||
brightMagenta: '#ff00ff',
|
||||
brightCyan: '#00ffff',
|
||||
brightWhite: '#ffffff',
|
||||
};
|
||||
} else {
|
||||
// Light theme - optimized for readability with softer contrast
|
||||
return {
|
||||
background: '#f8f9fa', // Slightly off-white for less eye strain
|
||||
foreground: '#1f2328', // Dark gray for better readability than pure black
|
||||
cursor: 'rgb(var(--color-primary))',
|
||||
cursorAccent: '#f8f9fa',
|
||||
// Standard 16 colors optimized for light backgrounds
|
||||
// Based on GitHub light theme for proven readability
|
||||
black: '#24292f',
|
||||
red: '#cf222e',
|
||||
green: '#1a7f37',
|
||||
yellow: '#9a6700',
|
||||
blue: '#0969da',
|
||||
magenta: '#8250df',
|
||||
cyan: '#1b7c83',
|
||||
white: '#6e7781',
|
||||
brightBlack: '#57606a',
|
||||
brightRed: '#da3633',
|
||||
brightGreen: '#2da44e',
|
||||
brightYellow: '#bf8700',
|
||||
brightBlue: '#218bff',
|
||||
brightMagenta: '#a475f9',
|
||||
brightCyan: '#3192aa',
|
||||
brightWhite: '#8c959f',
|
||||
// Selection colors for better visibility
|
||||
selectionBackground: '#0969da',
|
||||
selectionForeground: '#ffffff',
|
||||
selectionInactiveBackground: '#e1e4e8',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private async initializeTerminal() {
|
||||
try {
|
||||
this.requestUpdate();
|
||||
|
|
@ -399,29 +477,7 @@ export class Terminal extends LitElement {
|
|||
altClickMovesCursor: true,
|
||||
rightClickSelectsWord: false,
|
||||
wordSeparator: ' ()[]{}\'"`',
|
||||
theme: {
|
||||
background: '#1e1e1e',
|
||||
foreground: '#d4d4d4',
|
||||
cursor: '#10B981',
|
||||
cursorAccent: '#1e1e1e',
|
||||
// Standard 16 colors (0-15) - using proper xterm colors
|
||||
black: '#000000',
|
||||
red: '#cd0000',
|
||||
green: '#00cd00',
|
||||
yellow: '#cdcd00',
|
||||
blue: '#0000ee',
|
||||
magenta: '#cd00cd',
|
||||
cyan: '#00cdcd',
|
||||
white: '#e5e5e5',
|
||||
brightBlack: '#7f7f7f',
|
||||
brightRed: '#ff0000',
|
||||
brightGreen: '#00ff00',
|
||||
brightYellow: '#ffff00',
|
||||
brightBlue: '#5c5cff',
|
||||
brightMagenta: '#ff00ff',
|
||||
brightCyan: '#00ffff',
|
||||
brightWhite: '#ffffff',
|
||||
},
|
||||
theme: this.getTerminalTheme(),
|
||||
});
|
||||
|
||||
// Set terminal size - don't call .open() to keep it headless
|
||||
|
|
@ -1133,7 +1189,7 @@ export class Terminal extends LitElement {
|
|||
|
||||
// Apply cursor styling after inverse to ensure it takes precedence
|
||||
if (isCursor) {
|
||||
style += `background-color: #10B981;`;
|
||||
style += `background-color: rgb(var(--color-primary));`;
|
||||
}
|
||||
|
||||
// Handle invisible text
|
||||
|
|
|
|||
136
web/src/client/components/theme-toggle-icon.ts
Normal file
136
web/src/client/components/theme-toggle-icon.ts
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
import { html, LitElement } from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
|
||||
export type Theme = 'light' | 'dark' | 'system';
|
||||
|
||||
/**
|
||||
* Icon-only theme toggle button component
|
||||
* Cycles through light -> dark -> system themes
|
||||
*/
|
||||
@customElement('theme-toggle-icon')
|
||||
export class ThemeToggleIcon extends LitElement {
|
||||
@property({ type: String })
|
||||
theme: Theme = 'system';
|
||||
|
||||
private readonly STORAGE_KEY = 'vibetunnel-theme';
|
||||
private mediaQuery?: MediaQueryList;
|
||||
|
||||
// Disable shadow DOM to use Tailwind
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
// Load saved theme preference
|
||||
const saved = localStorage.getItem(this.STORAGE_KEY) as Theme | null;
|
||||
this.theme = saved || 'system';
|
||||
|
||||
// Set up system preference listener
|
||||
this.mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
this.mediaQuery.addEventListener('change', this.handleSystemThemeChange);
|
||||
|
||||
// Apply initial theme
|
||||
this.applyTheme();
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this.mediaQuery?.removeEventListener('change', this.handleSystemThemeChange);
|
||||
}
|
||||
|
||||
private handleSystemThemeChange = () => {
|
||||
if (this.theme === 'system') {
|
||||
this.applyTheme();
|
||||
}
|
||||
};
|
||||
|
||||
private applyTheme() {
|
||||
const root = document.documentElement;
|
||||
let effectiveTheme: 'light' | 'dark';
|
||||
|
||||
if (this.theme === 'system') {
|
||||
effectiveTheme = this.mediaQuery?.matches ? 'dark' : 'light';
|
||||
} else {
|
||||
effectiveTheme = this.theme;
|
||||
}
|
||||
|
||||
// Set data-theme attribute
|
||||
root.setAttribute('data-theme', effectiveTheme);
|
||||
|
||||
// Update meta theme-color for mobile browsers
|
||||
const metaTheme = document.querySelector('meta[name="theme-color"]');
|
||||
if (metaTheme) {
|
||||
metaTheme.setAttribute('content', effectiveTheme === 'dark' ? '#0a0a0a' : '#fafafa');
|
||||
}
|
||||
}
|
||||
|
||||
private cycleTheme() {
|
||||
// Cycle through: light -> dark -> system
|
||||
const themes: Theme[] = ['light', 'dark', 'system'];
|
||||
const currentIndex = themes.indexOf(this.theme);
|
||||
const nextIndex = (currentIndex + 1) % themes.length;
|
||||
this.theme = themes[nextIndex];
|
||||
|
||||
localStorage.setItem(this.STORAGE_KEY, this.theme);
|
||||
this.applyTheme();
|
||||
|
||||
// Dispatch event for other components that might need to react
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('theme-changed', {
|
||||
detail: { theme: this.theme },
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private getIcon() {
|
||||
switch (this.theme) {
|
||||
case 'light':
|
||||
return html`
|
||||
<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
`;
|
||||
case 'dark':
|
||||
return html`
|
||||
<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"/>
|
||||
</svg>
|
||||
`;
|
||||
case 'system':
|
||||
return html`
|
||||
<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M3 5a2 2 0 012-2h10a2 2 0 012 2v8a2 2 0 01-2 2h-2.22l.123.489.804.804A1 1 0 0113 18H7a1 1 0 01-.707-1.707l.804-.804L7.22 15H5a2 2 0 01-2-2V5zm5.771 7H5V5h10v7H8.771z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
private getTooltip() {
|
||||
const current = this.theme.charAt(0).toUpperCase() + this.theme.slice(1);
|
||||
const next = this.theme === 'light' ? 'Dark' : this.theme === 'dark' ? 'System' : 'Light';
|
||||
return `Theme: ${current} (click for ${next})`;
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<button
|
||||
@click=${this.cycleTheme}
|
||||
class="bg-elevated border border-base rounded-lg p-2 font-mono text-muted transition-all duration-200 hover:text-primary hover:bg-hover hover:border-primary hover:shadow-sm flex-shrink-0"
|
||||
title="${this.getTooltip()}"
|
||||
aria-label="Toggle theme"
|
||||
>
|
||||
${this.getIcon()}
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'theme-toggle-icon': ThemeToggleIcon;
|
||||
}
|
||||
}
|
||||
164
web/src/client/components/theme-toggle.ts
Normal file
164
web/src/client/components/theme-toggle.ts
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
import { css, html, LitElement } from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { Z_INDEX } from '../utils/constants';
|
||||
|
||||
export type Theme = 'light' | 'dark' | 'system';
|
||||
|
||||
@customElement('theme-toggle')
|
||||
export class ThemeToggle extends LitElement {
|
||||
@property({ type: String })
|
||||
theme: Theme = 'system';
|
||||
|
||||
@property({ type: Boolean })
|
||||
expanded = false;
|
||||
|
||||
private readonly STORAGE_KEY = 'vibetunnel-theme';
|
||||
private mediaQuery?: MediaQueryList;
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
// Load saved theme preference
|
||||
const saved = localStorage.getItem(this.STORAGE_KEY) as Theme | null;
|
||||
this.theme = saved || 'system';
|
||||
|
||||
// Set up system preference listener
|
||||
this.mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
this.mediaQuery.addEventListener('change', this.handleSystemThemeChange);
|
||||
|
||||
// Apply initial theme
|
||||
this.applyTheme();
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this.mediaQuery?.removeEventListener('change', this.handleSystemThemeChange);
|
||||
}
|
||||
|
||||
private handleSystemThemeChange = () => {
|
||||
if (this.theme === 'system') {
|
||||
this.applyTheme();
|
||||
}
|
||||
};
|
||||
|
||||
private applyTheme() {
|
||||
const root = document.documentElement;
|
||||
let effectiveTheme: 'light' | 'dark';
|
||||
|
||||
if (this.theme === 'system') {
|
||||
effectiveTheme = this.mediaQuery?.matches ? 'dark' : 'light';
|
||||
} else {
|
||||
effectiveTheme = this.theme;
|
||||
}
|
||||
|
||||
// Set data-theme attribute
|
||||
root.setAttribute('data-theme', effectiveTheme);
|
||||
|
||||
// Update meta theme-color for mobile browsers
|
||||
const metaTheme = document.querySelector('meta[name="theme-color"]');
|
||||
if (metaTheme) {
|
||||
metaTheme.setAttribute('content', effectiveTheme === 'dark' ? '#0a0a0a' : '#fafafa');
|
||||
}
|
||||
}
|
||||
|
||||
private selectTheme(theme: Theme) {
|
||||
this.theme = theme;
|
||||
localStorage.setItem(this.STORAGE_KEY, theme);
|
||||
this.applyTheme();
|
||||
this.expanded = false;
|
||||
|
||||
// Dispatch event for other components that might need to react
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('theme-changed', {
|
||||
detail: { theme },
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private toggleExpanded() {
|
||||
this.expanded = !this.expanded;
|
||||
}
|
||||
|
||||
private handleClickOutside = (e: Event) => {
|
||||
if (!this.contains(e.target as Node)) {
|
||||
this.expanded = false;
|
||||
}
|
||||
};
|
||||
|
||||
updated(changedProperties: Map<string, unknown>) {
|
||||
if (changedProperties.has('expanded')) {
|
||||
if (this.expanded) {
|
||||
document.addEventListener('click', this.handleClickOutside);
|
||||
} else {
|
||||
document.removeEventListener('click', this.handleClickOutside);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static styles = css``;
|
||||
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
|
||||
render() {
|
||||
const currentIcon = this.theme === 'light' ? 'sun' : this.theme === 'dark' ? 'moon' : 'laptop';
|
||||
|
||||
return html`
|
||||
<div class="relative">
|
||||
<button
|
||||
@click=${this.toggleExpanded}
|
||||
class="p-2 text-text border border-border hover:border-primary hover:text-primary rounded-lg transition-all duration-200"
|
||||
aria-label="Theme toggle"
|
||||
aria-expanded=${this.expanded}
|
||||
>
|
||||
<iconify-icon
|
||||
icon="ph:${currentIcon}"
|
||||
class="text-xl"
|
||||
></iconify-icon>
|
||||
</button>
|
||||
|
||||
${
|
||||
this.expanded
|
||||
? html`
|
||||
<div
|
||||
class="absolute right-0 mt-2 w-36 bg-elevated border border-border rounded-lg shadow-lg overflow-hidden"
|
||||
style="z-index: ${Z_INDEX.WIDTH_SELECTOR_DROPDOWN}"
|
||||
>
|
||||
<button
|
||||
@click=${() => this.selectTheme('light')}
|
||||
class="w-full px-4 py-2 text-left hover:bg-tertiary transition-colors flex items-center gap-3 ${this.theme === 'light' ? 'text-primary' : 'text-text'}"
|
||||
>
|
||||
<iconify-icon icon="ph:sun" class="text-lg"></iconify-icon>
|
||||
<span class="text-sm">Light</span>
|
||||
</button>
|
||||
<button
|
||||
@click=${() => this.selectTheme('dark')}
|
||||
class="w-full px-4 py-2 text-left hover:bg-tertiary transition-colors flex items-center gap-3 ${this.theme === 'dark' ? 'text-primary' : 'text-text'}"
|
||||
>
|
||||
<iconify-icon icon="ph:moon" class="text-lg"></iconify-icon>
|
||||
<span class="text-sm">Dark</span>
|
||||
</button>
|
||||
<button
|
||||
@click=${() => this.selectTheme('system')}
|
||||
class="w-full px-4 py-2 text-left hover:bg-tertiary transition-colors flex items-center gap-3 ${this.theme === 'system' ? 'text-primary' : 'text-text'}"
|
||||
>
|
||||
<iconify-icon icon="ph:laptop" class="text-lg"></iconify-icon>
|
||||
<span class="text-sm">System</span>
|
||||
</button>
|
||||
</div>
|
||||
`
|
||||
: ''
|
||||
}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'theme-toggle': ThemeToggle;
|
||||
}
|
||||
}
|
||||
|
|
@ -245,21 +245,21 @@ export class UnifiedSettings extends LitElement {
|
|||
return html`
|
||||
<div class="flex items-center space-x-2">
|
||||
<span class="text-status-success font-mono">✓</span>
|
||||
<span class="text-sm text-dark-text">Active</span>
|
||||
<span class="text-sm text-primary">Active</span>
|
||||
</div>
|
||||
`;
|
||||
} else if (this.permission === 'granted') {
|
||||
return html`
|
||||
<div class="flex items-center space-x-2">
|
||||
<span class="text-status-warning font-mono">!</span>
|
||||
<span class="text-sm text-dark-text">Not subscribed</span>
|
||||
<span class="text-sm text-primary">Not subscribed</span>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
return html`
|
||||
<div class="flex items-center space-x-2">
|
||||
<span class="text-status-error font-mono">✗</span>
|
||||
<span class="text-sm text-dark-text">Disabled</span>
|
||||
<span class="text-sm text-primary">Disabled</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
|
@ -289,10 +289,10 @@ export class UnifiedSettings extends LitElement {
|
|||
style="view-transition-name: settings-modal"
|
||||
>
|
||||
<!-- Header -->
|
||||
<div class="p-4 pb-4 border-b border-dark-border relative flex-shrink-0">
|
||||
<h2 class="text-accent-green text-lg font-bold">Settings</h2>
|
||||
<div class="p-4 pb-4 border-b border-base relative flex-shrink-0">
|
||||
<h2 class="text-primary text-lg font-bold">Settings</h2>
|
||||
<button
|
||||
class="absolute top-4 right-4 text-dark-text-muted hover:text-dark-text transition-colors p-1"
|
||||
class="absolute top-4 right-4 text-muted hover:text-primary transition-colors p-1"
|
||||
@click=${this.handleClose}
|
||||
title="Close"
|
||||
aria-label="Close settings"
|
||||
|
|
@ -321,7 +321,7 @@ export class UnifiedSettings extends LitElement {
|
|||
return html`
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<h3 class="text-md font-bold text-dark-text">Notifications</h3>
|
||||
<h3 class="text-md font-bold text-primary">Notifications</h3>
|
||||
${this.renderSubscriptionStatus()}
|
||||
</div>
|
||||
|
||||
|
|
@ -349,10 +349,10 @@ export class UnifiedSettings extends LitElement {
|
|||
`
|
||||
: html`
|
||||
<!-- Main toggle -->
|
||||
<div class="flex items-center justify-between p-4 bg-dark-bg-tertiary rounded-lg border border-dark-border">
|
||||
<div class="flex items-center justify-between p-4 bg-tertiary rounded-lg border border-base"
|
||||
<div class="flex-1">
|
||||
<label class="text-dark-text font-medium">Enable Notifications</label>
|
||||
<p class="text-dark-text-muted text-xs mt-1">
|
||||
<label class="text-primary font-medium">Enable Notifications</label>
|
||||
<p class="text-muted text-xs mt-1">
|
||||
Receive alerts for session events
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -361,12 +361,12 @@ export class UnifiedSettings extends LitElement {
|
|||
aria-checked="${this.isNotificationsEnabled}"
|
||||
@click=${this.handleToggleNotifications}
|
||||
?disabled=${this.isLoading}
|
||||
class="relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-accent-green focus:ring-offset-2 focus:ring-offset-dark-bg ${
|
||||
this.isNotificationsEnabled ? 'bg-accent-green' : 'bg-dark-border'
|
||||
class="relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 focus:ring-offset-base ${
|
||||
this.isNotificationsEnabled ? 'bg-primary' : 'bg-border'
|
||||
}"
|
||||
>
|
||||
<span
|
||||
class="inline-block h-5 w-5 transform rounded-full bg-white transition-transform ${
|
||||
class="inline-block h-5 w-5 transform rounded-full bg-bg-elevated transition-transform ${
|
||||
this.isNotificationsEnabled ? 'translate-x-5' : 'translate-x-0.5'
|
||||
}"
|
||||
></span>
|
||||
|
|
@ -379,8 +379,8 @@ export class UnifiedSettings extends LitElement {
|
|||
<!-- Notification types -->
|
||||
<div class="mt-4 space-y-4">
|
||||
<div>
|
||||
<h4 class="text-sm font-medium text-dark-text-muted mb-3">Notification Types</h4>
|
||||
<div class="space-y-2 bg-dark-bg rounded-lg p-3">
|
||||
<h4 class="text-sm font-medium text-muted mb-3">Notification Types</h4>
|
||||
<div class="space-y-2 bg-base rounded-lg p-3"
|
||||
${this.renderNotificationToggle('sessionExit', 'Session Exit', 'When a session terminates')}
|
||||
${this.renderNotificationToggle('sessionStart', 'Session Start', 'When a new session starts')}
|
||||
${this.renderNotificationToggle('sessionError', 'Session Errors', 'When errors occur in sessions')}
|
||||
|
|
@ -390,8 +390,8 @@ export class UnifiedSettings extends LitElement {
|
|||
|
||||
<!-- Sound and vibration -->
|
||||
<div>
|
||||
<h4 class="text-sm font-medium text-dark-text-muted mb-3">Notification Behavior</h4>
|
||||
<div class="space-y-2 bg-dark-bg rounded-lg p-3">
|
||||
<h4 class="text-sm font-medium text-muted mb-3">Notification Behavior</h4>
|
||||
<div class="space-y-2 bg-base rounded-lg p-3"
|
||||
${this.renderNotificationToggle('soundEnabled', 'Sound', 'Play sound with notifications')}
|
||||
${this.renderNotificationToggle('vibrationEnabled', 'Vibration', 'Vibrate device with notifications')}
|
||||
</div>
|
||||
|
|
@ -399,8 +399,8 @@ export class UnifiedSettings extends LitElement {
|
|||
</div>
|
||||
|
||||
<!-- Test button -->
|
||||
<div class="flex items-center justify-between pt-3 mt-3 border-t border-dark-border">
|
||||
<p class="text-xs text-dark-text-muted">Test your notification settings</p>
|
||||
<div class="flex items-center justify-between pt-3 mt-3 border-t border-base">
|
||||
<p class="text-xs text-muted">Test your notification settings</p>
|
||||
<button
|
||||
class="btn-secondary text-xs px-3 py-1.5"
|
||||
@click=${this.handleTestNotification}
|
||||
|
|
@ -427,19 +427,19 @@ export class UnifiedSettings extends LitElement {
|
|||
return html`
|
||||
<div class="flex items-center justify-between py-2">
|
||||
<div class="flex-1 pr-4">
|
||||
<label class="text-dark-text text-sm font-medium">${label}</label>
|
||||
<p class="text-dark-text-muted text-xs">${description}</p>
|
||||
<label class="text-primary text-sm font-medium">${label}</label>
|
||||
<p class="text-muted text-xs">${description}</p>
|
||||
</div>
|
||||
<button
|
||||
role="switch"
|
||||
aria-checked="${this.notificationPreferences[key]}"
|
||||
@click=${() => this.handleNotificationPreferenceChange(key, !this.notificationPreferences[key])}
|
||||
class="relative inline-flex h-5 w-9 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-accent-green focus:ring-offset-2 focus:ring-offset-dark-bg ${
|
||||
this.notificationPreferences[key] ? 'bg-accent-green' : 'bg-dark-border'
|
||||
class="relative inline-flex h-5 w-9 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 focus:ring-offset-base ${
|
||||
this.notificationPreferences[key] ? 'bg-primary' : 'bg-border'
|
||||
}"
|
||||
>
|
||||
<span
|
||||
class="inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
|
||||
class="inline-block h-4 w-4 transform rounded-full bg-bg-elevated transition-transform ${
|
||||
this.notificationPreferences[key] ? 'translate-x-4' : 'translate-x-0.5'
|
||||
}"
|
||||
></span>
|
||||
|
|
@ -451,18 +451,18 @@ export class UnifiedSettings extends LitElement {
|
|||
private renderAppSettings() {
|
||||
return html`
|
||||
<div class="space-y-4">
|
||||
<h3 class="text-md font-bold text-dark-text mb-3">Application</h3>
|
||||
<h3 class="text-md font-bold text-primary mb-3">Application</h3>
|
||||
|
||||
<!-- Direct keyboard input (Mobile only) -->
|
||||
${
|
||||
this.mediaState.isMobile
|
||||
? html`
|
||||
<div class="flex items-center justify-between p-4 bg-dark-bg-tertiary rounded-lg border border-dark-border">
|
||||
<div class="flex items-center justify-between p-4 bg-tertiary rounded-lg border border-base"
|
||||
<div class="flex-1">
|
||||
<label class="text-dark-text font-medium">
|
||||
<label class="text-primary font-medium">
|
||||
Use Direct Keyboard
|
||||
</label>
|
||||
<p class="text-dark-text-muted text-xs mt-1">
|
||||
<p class="text-muted text-xs mt-1">
|
||||
Capture keyboard input directly without showing a text field (desktop-like experience)
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -470,12 +470,12 @@ export class UnifiedSettings extends LitElement {
|
|||
role="switch"
|
||||
aria-checked="${this.appPreferences.useDirectKeyboard}"
|
||||
@click=${() => this.handleAppPreferenceChange('useDirectKeyboard', !this.appPreferences.useDirectKeyboard)}
|
||||
class="relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-accent-green focus:ring-offset-2 focus:ring-offset-dark-bg ${
|
||||
this.appPreferences.useDirectKeyboard ? 'bg-accent-green' : 'bg-dark-border'
|
||||
class="relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 focus:ring-offset-base ${
|
||||
this.appPreferences.useDirectKeyboard ? 'bg-primary' : 'bg-border'
|
||||
}"
|
||||
>
|
||||
<span
|
||||
class="inline-block h-5 w-5 transform rounded-full bg-white transition-transform ${
|
||||
class="inline-block h-5 w-5 transform rounded-full bg-bg-elevated transition-transform ${
|
||||
this.appPreferences.useDirectKeyboard ? 'translate-x-5' : 'translate-x-0.5'
|
||||
}"
|
||||
></span>
|
||||
|
|
@ -486,10 +486,10 @@ export class UnifiedSettings extends LitElement {
|
|||
}
|
||||
|
||||
<!-- Show log link -->
|
||||
<div class="flex items-center justify-between p-4 bg-dark-bg-tertiary rounded-lg border border-dark-border">
|
||||
<div class="flex items-center justify-between p-4 bg-tertiary rounded-lg border border-base">
|
||||
<div class="flex-1">
|
||||
<label class="text-dark-text font-medium">Show Log Link</label>
|
||||
<p class="text-dark-text-muted text-xs mt-1">
|
||||
<label class="text-primary font-medium">Show Log Link</label>
|
||||
<p class="text-muted text-xs mt-1">
|
||||
Display log link for debugging
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -497,12 +497,12 @@ export class UnifiedSettings extends LitElement {
|
|||
role="switch"
|
||||
aria-checked="${this.appPreferences.showLogLink}"
|
||||
@click=${() => this.handleAppPreferenceChange('showLogLink', !this.appPreferences.showLogLink)}
|
||||
class="relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-accent-green focus:ring-offset-2 focus:ring-offset-dark-bg ${
|
||||
this.appPreferences.showLogLink ? 'bg-accent-green' : 'bg-dark-border'
|
||||
class="relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 focus:ring-offset-base ${
|
||||
this.appPreferences.showLogLink ? 'bg-primary' : 'bg-border'
|
||||
}"
|
||||
>
|
||||
<span
|
||||
class="inline-block h-5 w-5 transform rounded-full bg-white transition-transform ${
|
||||
class="inline-block h-5 w-5 transform rounded-full bg-bg-elevated transition-transform ${
|
||||
this.appPreferences.showLogLink ? 'translate-x-5' : 'translate-x-0.5'
|
||||
}"
|
||||
></span>
|
||||
|
|
|
|||
|
|
@ -199,7 +199,7 @@ export class VibeTerminalBuffer extends LitElement {
|
|||
}
|
||||
</style>
|
||||
<div
|
||||
class="relative w-full h-full overflow-hidden bg-black"
|
||||
class="relative w-full h-full overflow-hidden bg-bg"
|
||||
style="view-transition-name: terminal-${this.sessionId}; min-height: 200px;"
|
||||
>
|
||||
${
|
||||
|
|
|
|||
|
|
@ -97,13 +97,28 @@ export class AuthClient {
|
|||
}
|
||||
|
||||
// Return generic avatar SVG for non-macOS or when no avatar found
|
||||
// Get computed theme colors from CSS variables
|
||||
const computedStyle = getComputedStyle(document.documentElement);
|
||||
const bgColor = computedStyle
|
||||
.getPropertyValue('--color-text-dim')
|
||||
.trim()
|
||||
.split(' ')
|
||||
.map((v) => Number.parseInt(v));
|
||||
const fgColor = computedStyle
|
||||
.getPropertyValue('--color-text-muted')
|
||||
.trim()
|
||||
.split(' ')
|
||||
.map((v) => Number.parseInt(v));
|
||||
const bgColorStr = `rgb(${bgColor.join(', ')})`;
|
||||
const fgColorStr = `rgb(${fgColor.join(', ')})`;
|
||||
|
||||
return (
|
||||
'data:image/svg+xml;base64,' +
|
||||
btoa(`
|
||||
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="24" cy="24" r="24" fill="#6B7280"/>
|
||||
<circle cx="24" cy="18" r="8" fill="#9CA3AF"/>
|
||||
<path d="M8 38c0-8.837 7.163-16 16-16s16 7.163 16 16" fill="#9CA3AF"/>
|
||||
<circle cx="24" cy="24" r="24" fill="${bgColorStr}"/>
|
||||
<circle cx="24" cy="18" r="8" fill="${fgColorStr}"/>
|
||||
<path d="M8 38c0-8.837 7.163-16 16-16s16 7.163 16 16" fill="${fgColorStr}"/>
|
||||
</svg>
|
||||
`)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,33 @@
|
|||
|
||||
/* CSS Custom Properties for VibeTunnel constants */
|
||||
:root {
|
||||
/* Theme Colors - Light Mode (Default) */
|
||||
--color-bg: 250 250 250; /* #fafafa - Off-white background */
|
||||
--color-bg-secondary: 245 245 245; /* #f5f5f5 - Subtle gray */
|
||||
--color-bg-tertiary: 237 237 237; /* #ededed - Card backgrounds */
|
||||
--color-bg-elevated: 255 255 255; /* #ffffff - Pure white for elevated surfaces */
|
||||
--color-surface: 245 245 245; /* #f5f5f5 */
|
||||
--color-surface-hover: 237 237 237; /* #ededed */
|
||||
--color-border: 226 226 226; /* #e2e2e2 - Subtle gray borders */
|
||||
--color-border-light: 237 237 237; /* #ededed */
|
||||
--color-border-focus: 200 200 200; /* #c8c8c8 */
|
||||
|
||||
/* Text colors - Light Mode */
|
||||
--color-text: 23 23 23; /* #171717 - Near black for high contrast */
|
||||
--color-text-bright: 0 0 0; /* #000000 - Pure black for emphasis */
|
||||
--color-text-muted: 75 75 75; /* #4b4b4b - Darker muted gray for AAA compliance */
|
||||
--color-text-dim: 115 115 115; /* #737373 - Darker dim gray for AA compliance */
|
||||
|
||||
/* Primary color (same across themes) */
|
||||
--color-primary: 16 185 129; /* #10B981 - VibeTunnel green */
|
||||
--color-primary-hover: 5 150 105; /* #059669 */
|
||||
--color-primary-dark: 4 120 87; /* #047857 */
|
||||
|
||||
/* Status colors */
|
||||
--color-status-error: 239 68 68; /* #EF4444 */
|
||||
--color-status-warning: 245 158 11; /* #F59E0B */
|
||||
--color-status-success: 16 185 129; /* #10B981 */
|
||||
--color-status-info: 59 130 246; /* #3B82F6 */
|
||||
/* Breakpoints */
|
||||
--vt-breakpoint-mobile: 768px;
|
||||
--vt-breakpoint-tablet: 1024px;
|
||||
|
|
@ -31,16 +58,77 @@
|
|||
--vt-terminal-resize-debounce: 100ms;
|
||||
}
|
||||
|
||||
/* Global dark theme styles */
|
||||
/* Dark mode theme colors */
|
||||
[data-theme="dark"] {
|
||||
--color-bg: 10 10 10; /* #0a0a0a */
|
||||
--color-bg-secondary: 20 20 20; /* #141414 */
|
||||
--color-bg-tertiary: 31 31 31; /* #1f1f1f */
|
||||
--color-bg-elevated: 38 38 38; /* #262626 */
|
||||
--color-surface: 26 26 26; /* #1a1a1a */
|
||||
--color-surface-hover: 42 42 42; /* #2a2a2a */
|
||||
--color-border: 42 42 42; /* #2a2a2a */
|
||||
--color-border-light: 58 58 58; /* #3a3a3a */
|
||||
--color-border-focus: 74 74 74; /* #4a4a4a */
|
||||
|
||||
/* Text colors - Dark Mode */
|
||||
--color-text: 228 228 228; /* #e4e4e4 */
|
||||
--color-text-bright: 255 255 255; /* #ffffff */
|
||||
--color-text-muted: 163 163 163; /* #a3a3a3 */
|
||||
--color-text-dim: 115 115 115; /* #737373 */
|
||||
|
||||
/* Primary color (same as light mode) */
|
||||
--color-primary: 16 185 129; /* #10B981 */
|
||||
--color-primary-hover: 5 150 105; /* #059669 */
|
||||
--color-primary-dark: 4 120 87; /* #047857 */
|
||||
|
||||
/* Status colors (same as light mode) */
|
||||
--color-status-error: 239 68 68; /* #EF4444 */
|
||||
--color-status-warning: 245 158 11; /* #F59E0B */
|
||||
--color-status-success: 16 185 129; /* #10B981 */
|
||||
--color-status-info: 59 130 246; /* #3B82F6 */
|
||||
}
|
||||
|
||||
/* System preference detection */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not([data-theme="light"]) {
|
||||
--color-bg: 10 10 10;
|
||||
--color-bg-secondary: 20 20 20;
|
||||
--color-bg-tertiary: 31 31 31;
|
||||
--color-bg-elevated: 38 38 38;
|
||||
--color-surface: 26 26 26;
|
||||
--color-surface-hover: 42 42 42;
|
||||
--color-border: 42 42 42;
|
||||
--color-border-light: 58 58 58;
|
||||
--color-border-focus: 74 74 74;
|
||||
--color-text: 228 228 228;
|
||||
--color-text-bright: 255 255 255;
|
||||
--color-text-muted: 163 163 163;
|
||||
--color-text-dim: 115 115 115;
|
||||
--color-primary: 16 185 129;
|
||||
--color-primary-hover: 5 150 105;
|
||||
--color-primary-dark: 4 120 87;
|
||||
--color-status-error: 239 68 68;
|
||||
--color-status-warning: 245 158 11;
|
||||
--color-status-success: 16 185 129;
|
||||
--color-status-info: 59 130 246;
|
||||
}
|
||||
}
|
||||
|
||||
/* Global theme-aware styles */
|
||||
@layer base {
|
||||
html {
|
||||
@apply bg-bg text-text;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-black text-dark-text;
|
||||
@apply bg-bg text-text;
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
}
|
||||
|
||||
/* Default focus styles */
|
||||
:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.3);
|
||||
box-shadow: 0 0 0 2px rgb(var(--color-primary) / 0.3);
|
||||
}
|
||||
|
||||
/* iOS Safe Area Support */
|
||||
|
|
@ -66,16 +154,16 @@
|
|||
/* Glowing terminal icon */
|
||||
.terminal-icon {
|
||||
@apply text-primary;
|
||||
filter: drop-shadow(0 0 10px rgba(16, 185, 129, 0.6));
|
||||
filter: drop-shadow(0 0 10px rgb(var(--color-primary) / 0.6));
|
||||
}
|
||||
|
||||
/* Input fields with unified style */
|
||||
.input-field {
|
||||
@apply bg-dark-bg-secondary border border-dark-border rounded-lg px-4 py-3 text-dark-text w-full;
|
||||
@apply bg-bg-secondary border border-border rounded-lg px-4 py-3 text-text w-full;
|
||||
@apply transition-all duration-200 ease-in-out;
|
||||
@apply hover:border-primary/50 focus:border-primary;
|
||||
@apply focus:shadow-glow-sm focus:outline-none;
|
||||
@apply text-left placeholder:text-dark-text-muted;
|
||||
@apply text-left placeholder:text-text-muted;
|
||||
}
|
||||
|
||||
/* Unified button styles */
|
||||
|
|
@ -98,7 +186,7 @@
|
|||
}
|
||||
|
||||
.btn-primary {
|
||||
@apply btn-md bg-primary text-black;
|
||||
@apply btn-md bg-primary text-bg;
|
||||
@apply hover:bg-primary-hover hover:shadow-glow;
|
||||
@apply active:scale-95;
|
||||
}
|
||||
|
|
@ -110,15 +198,15 @@
|
|||
}
|
||||
|
||||
.btn-ghost {
|
||||
@apply btn-md text-dark-text-muted;
|
||||
@apply hover:text-dark-text hover:bg-dark-bg-tertiary;
|
||||
@apply btn-md text-text-muted;
|
||||
@apply hover:text-text hover:bg-bg-tertiary;
|
||||
}
|
||||
|
||||
/* Enhanced card styles */
|
||||
.card {
|
||||
@apply bg-dark-bg-secondary border border-dark-border rounded-lg p-0;
|
||||
@apply bg-bg-secondary border border-border rounded-lg p-0;
|
||||
@apply transition-all duration-300 ease-out shadow-sm;
|
||||
@apply hover:border-dark-border-light hover:shadow-lg hover:-translate-y-1;
|
||||
@apply hover:border-border-light hover:shadow-lg hover:-translate-y-1;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
/* Ensure cards have proper z-index for hover */
|
||||
|
|
@ -138,7 +226,7 @@
|
|||
content: '';
|
||||
position: absolute;
|
||||
inset: -2px;
|
||||
background: radial-gradient(circle at 50% 50%, transparent 60%, rgba(16, 185, 129, 0.1) 100%);
|
||||
background: radial-gradient(circle at 50% 50%, transparent 60%, rgb(var(--color-primary) / 0.1) 100%);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease-out;
|
||||
pointer-events: none;
|
||||
|
|
@ -150,8 +238,8 @@
|
|||
}
|
||||
|
||||
.card-elevated {
|
||||
@apply bg-dark-bg-elevated rounded-xl p-6 shadow-card;
|
||||
@apply hover:shadow-elevated hover:bg-dark-surface-hover;
|
||||
@apply bg-bg-elevated rounded-xl p-6 shadow-card;
|
||||
@apply hover:shadow-elevated hover:bg-surface-hover;
|
||||
@apply transition-all duration-200;
|
||||
}
|
||||
|
||||
|
|
@ -178,26 +266,26 @@
|
|||
|
||||
/* Quick start buttons */
|
||||
.quick-start-btn {
|
||||
@apply bg-dark-bg-tertiary border border-dark-border rounded-lg px-4 py-3 h-12;
|
||||
@apply transition-all duration-200 ease-in-out text-dark-text-muted;
|
||||
@apply bg-bg-tertiary border border-border rounded-lg px-4 py-3 h-12;
|
||||
@apply transition-all duration-200 ease-in-out text-text-muted;
|
||||
@apply hover:border-primary hover:text-primary hover:shadow-glow-sm;
|
||||
@apply active:scale-95;
|
||||
}
|
||||
|
||||
.quick-start-btn.active {
|
||||
@apply bg-primary text-black border-primary shadow-glow-sm;
|
||||
@apply bg-primary text-bg border-primary shadow-glow-sm;
|
||||
}
|
||||
|
||||
/* Modal backdrop */
|
||||
.modal-backdrop {
|
||||
@apply fixed inset-0 bg-black bg-opacity-80;
|
||||
@apply fixed inset-0 bg-bg bg-opacity-80;
|
||||
z-index: 1000;
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
/* Modal content */
|
||||
.modal-content {
|
||||
@apply bg-dark-bg-secondary border border-dark-border rounded-xl;
|
||||
@apply bg-bg-secondary border border-border rounded-xl;
|
||||
@apply shadow-2xl shadow-black/50;
|
||||
position: relative;
|
||||
z-index: 1001;
|
||||
|
|
@ -213,7 +301,7 @@
|
|||
|
||||
/* Labels */
|
||||
.form-label {
|
||||
@apply text-dark-text-muted text-sm font-medium mb-2 flex items-center gap-2;
|
||||
@apply text-text-muted text-sm font-medium mb-2 flex items-center gap-2;
|
||||
}
|
||||
|
||||
/* Enhanced responsive session grid layout */
|
||||
|
|
@ -299,7 +387,7 @@
|
|||
|
||||
/* Enhanced authentication styles */
|
||||
.auth-container {
|
||||
@apply bg-gradient-to-br from-dark-bg to-dark-bg-secondary flex items-center justify-center p-4;
|
||||
@apply bg-gradient-to-br from-bg to-bg-secondary flex items-center justify-center p-4;
|
||||
min-height: 100vh;
|
||||
min-height: 100dvh; /* Dynamic viewport height for mobile */
|
||||
}
|
||||
|
|
@ -319,36 +407,36 @@
|
|||
}
|
||||
|
||||
.auth-title {
|
||||
@apply text-3xl font-mono font-bold text-dark-text-bright;
|
||||
@apply text-3xl font-mono font-bold text-text-bright;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.auth-subtitle {
|
||||
@apply text-dark-text-muted font-mono text-xs;
|
||||
@apply text-text-muted font-mono text-xs;
|
||||
}
|
||||
|
||||
.auth-form {
|
||||
@apply bg-dark-bg-elevated border border-dark-border rounded-xl shadow-card p-0 w-full;
|
||||
@apply bg-bg-elevated border border-border rounded-xl shadow-card p-0 w-full;
|
||||
}
|
||||
|
||||
.auth-divider {
|
||||
@apply relative text-center text-dark-text-muted font-mono text-sm;
|
||||
@apply relative text-center text-text-muted font-mono text-sm;
|
||||
}
|
||||
|
||||
.auth-divider::before {
|
||||
@apply absolute top-1/2 left-0 w-full h-px bg-dark-border;
|
||||
@apply absolute top-1/2 left-0 w-full h-px bg-border;
|
||||
content: '';
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.auth-divider span {
|
||||
@apply bg-dark-bg-secondary px-4;
|
||||
@apply bg-bg-secondary px-4;
|
||||
}
|
||||
|
||||
/* SSH Key Manager styles */
|
||||
.ssh-key-item {
|
||||
@apply bg-dark-bg border-0 rounded-lg p-6;
|
||||
@apply bg-bg border-0 rounded-lg p-6;
|
||||
@apply transition-all duration-200 ease-in-out;
|
||||
}
|
||||
|
||||
|
|
@ -357,11 +445,11 @@
|
|||
}
|
||||
|
||||
.badge-rsa {
|
||||
@apply bg-blue-500 text-white;
|
||||
@apply bg-status-info text-bg-elevated;
|
||||
}
|
||||
|
||||
.badge-ed25519 {
|
||||
@apply bg-purple-500 text-white;
|
||||
@apply bg-primary text-bg-elevated;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -898,9 +986,20 @@ body.initial-session-load .session-flex-responsive > session-card:nth-child(n +
|
|||
|
||||
/* Enhanced terminal container styling */
|
||||
.terminal-container {
|
||||
color: #e4e4e4;
|
||||
overflow: hidden;
|
||||
background-color: #0a0a0a;
|
||||
}
|
||||
|
||||
/* Terminal container theme styling */
|
||||
[data-theme="dark"] .terminal-container,
|
||||
:root:not([data-theme="light"]) .terminal-container {
|
||||
/* Terminal colors are now handled by xterm.js theme */
|
||||
}
|
||||
|
||||
/* Light theme terminal container styling */
|
||||
[data-theme="light"] .terminal-container {
|
||||
/* Terminal colors are now handled by xterm.js theme */
|
||||
border: 1px solid rgb(var(--color-border));
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
/* Terminal line styling */
|
||||
|
|
@ -955,20 +1054,20 @@ body.initial-session-load .session-flex-responsive > session-card:nth-child(n +
|
|||
/* Enhanced cursor styling */
|
||||
.terminal-char.cursor {
|
||||
animation: cursor-blink 1s infinite;
|
||||
background-color: #10B981;
|
||||
color: #0a0a0a;
|
||||
background-color: rgb(var(--color-primary));
|
||||
color: rgb(10 10 10); /* Always dark text on cursor */
|
||||
}
|
||||
|
||||
@keyframes cursor-blink {
|
||||
0%,
|
||||
50% {
|
||||
opacity: 1;
|
||||
background-color: #10B981;
|
||||
background-color: rgb(var(--color-primary));
|
||||
}
|
||||
51%,
|
||||
100% {
|
||||
opacity: 0.4;
|
||||
background-color: #10B981;
|
||||
background-color: rgb(var(--color-primary));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -976,6 +1075,16 @@ body.initial-session-load .session-flex-responsive > session-card:nth-child(n +
|
|||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
/* Light mode terminal styling */
|
||||
[data-theme="light"] .xterm {
|
||||
/* Terminal background is now handled by xterm.js theme */
|
||||
}
|
||||
|
||||
[data-theme="light"] .session-card .terminal-container {
|
||||
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||
border: 1px solid rgb(var(--color-border));
|
||||
}
|
||||
|
||||
/* Ensure terminal container has proper size */
|
||||
#terminal-player,
|
||||
#interactive-terminal {
|
||||
|
|
@ -986,8 +1095,8 @@ body.initial-session-load .session-flex-responsive > session-card:nth-child(n +
|
|||
|
||||
/* Enhanced terminal focus indicator */
|
||||
.terminal-focused {
|
||||
box-shadow: 0 0 0 2px #10B981, 0 0 20px rgba(16, 185, 129, 0.3);
|
||||
border-color: #10B981 !important;
|
||||
box-shadow: 0 0 0 2px rgb(var(--color-primary)), 0 0 20px rgb(var(--color-primary) / 0.3);
|
||||
border-color: rgb(var(--color-primary)) !important;
|
||||
transition: all 0.2s ease-out;
|
||||
}
|
||||
|
||||
|
|
@ -996,17 +1105,17 @@ body.initial-session-load .session-flex-responsive > session-card:nth-child(n +
|
|||
position: fixed;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
background: rgba(16, 185, 129, 0.1);
|
||||
background: rgb(var(--color-primary) / 0.1);
|
||||
backdrop-filter: blur(8px);
|
||||
border: 1px solid #10B981;
|
||||
color: #10B981;
|
||||
border: 1px solid rgb(var(--color-primary));
|
||||
color: rgb(var(--color-primary));
|
||||
padding: 6px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
z-index: 1000;
|
||||
animation: slideInFromTop 0.3s ease-out;
|
||||
box-shadow: 0 2px 8px rgba(16, 185, 129, 0.2);
|
||||
box-shadow: 0 2px 8px rgb(var(--color-primary) / 0.2);
|
||||
}
|
||||
|
||||
@keyframes slideInFromTop {
|
||||
|
|
@ -1330,8 +1439,8 @@ body.initial-session-load .session-flex-responsive > session-card:nth-child(n +
|
|||
.keyboard-button {
|
||||
@apply absolute flex items-center justify-center;
|
||||
@apply w-12 h-12 rounded-md;
|
||||
@apply bg-dark-bg/20 border border-dark-border/50;
|
||||
@apply text-dark-text text-2xl;
|
||||
@apply bg-bg/20 border border-border/50;
|
||||
@apply text-text text-2xl;
|
||||
@apply cursor-pointer select-none;
|
||||
@apply transition-all duration-200;
|
||||
backdrop-filter: blur(12px) saturate(1.5);
|
||||
|
|
@ -1358,7 +1467,7 @@ body.initial-session-load .session-flex-responsive > session-card:nth-child(n +
|
|||
/* Hover state for both buttons */
|
||||
.scroll-to-bottom:hover,
|
||||
.keyboard-button:hover {
|
||||
@apply bg-dark-bg/40 border-primary text-primary;
|
||||
@apply bg-bg/40 border-primary text-primary;
|
||||
@apply -translate-y-0.5 shadow-glow-sm;
|
||||
backdrop-filter: blur(16px) saturate(1.8);
|
||||
-webkit-backdrop-filter: blur(16px) saturate(1.8);
|
||||
|
|
@ -1381,7 +1490,7 @@ body.initial-session-load .session-flex-responsive > session-card:nth-child(n +
|
|||
padding: 8px 12px;
|
||||
font-family: 'Fira Code', monospace;
|
||||
font-size: 11px;
|
||||
color: #e4e4e4;
|
||||
color: rgb(228 228 228); /* Always light for terminal */
|
||||
user-select: none;
|
||||
z-index: 10;
|
||||
line-height: 1.4;
|
||||
|
|
@ -1416,7 +1525,7 @@ body.initial-session-load .session-flex-responsive > session-card:nth-child(n +
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
color: #e4e4e4;
|
||||
color: rgb(var(--color-text));
|
||||
font-size: 20px;
|
||||
transition: all 0.2s ease;
|
||||
user-select: none;
|
||||
|
|
@ -1426,9 +1535,9 @@ body.initial-session-load .session-flex-responsive > session-card:nth-child(n +
|
|||
.fit-toggle:hover {
|
||||
background: rgba(26, 26, 26, 1);
|
||||
border-color: #10B981;
|
||||
color: #10B981;
|
||||
color: rgb(var(--color-primary));
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 0 10px rgba(16, 185, 129, 0.3);
|
||||
box-shadow: 0 0 10px rgb(var(--color-primary) / 0.3);
|
||||
}
|
||||
|
||||
.fit-toggle:active {
|
||||
|
|
@ -1437,8 +1546,8 @@ body.initial-session-load .session-flex-responsive > session-card:nth-child(n +
|
|||
|
||||
.fit-toggle.active {
|
||||
border-color: #10B981;
|
||||
color: #10B981;
|
||||
box-shadow: 0 0 10px rgba(16, 185, 129, 0.3);
|
||||
color: rgb(var(--color-primary));
|
||||
box-shadow: 0 0 10px rgb(var(--color-primary) / 0.3);
|
||||
}
|
||||
|
||||
/* View Transitions */
|
||||
|
|
@ -1500,83 +1609,12 @@ body.initial-session-load .session-flex-responsive > session-card:nth-child(n +
|
|||
z-index: 1000;
|
||||
}
|
||||
|
||||
/* Custom morph animation from button to modal with spring effect */
|
||||
@keyframes expand-from-button {
|
||||
0% {
|
||||
transform: scale(0)
|
||||
translate(calc(var(--vt-button-x) - 50vw), calc(var(--vt-button-y) - 50vh));
|
||||
opacity: 0;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
85% {
|
||||
transform: scale(1.01) translate(0, 0);
|
||||
opacity: 1;
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
100% {
|
||||
transform: scale(1) translate(0, 0);
|
||||
opacity: 1;
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shrink-to-button {
|
||||
0% {
|
||||
transform: scale(1) translate(0, 0);
|
||||
opacity: 1;
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
100% {
|
||||
transform: scale(0)
|
||||
translate(calc(var(--vt-button-x) - 50vw), calc(var(--vt-button-y) - 50vh));
|
||||
opacity: 0;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Apply the custom animations */
|
||||
::view-transition-new(create-session-modal) {
|
||||
animation: expand-from-button 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||
}
|
||||
|
||||
::view-transition-old(create-session-modal) {
|
||||
animation: shrink-to-button 0.25s cubic-bezier(0.4, 0, 0.6, 1);
|
||||
}
|
||||
|
||||
/* Hide button during forward transition */
|
||||
::view-transition-old(create-session-button) {
|
||||
animation: fade-out 0.15s ease-out;
|
||||
}
|
||||
|
||||
/* Show button during reverse transition */
|
||||
::view-transition-new(create-session-button) {
|
||||
animation: fade-in 0.15s ease-out;
|
||||
}
|
||||
|
||||
/* Make button invisible during transition */
|
||||
.vt-create-button {
|
||||
contain: layout;
|
||||
}
|
||||
|
||||
/* Ensure smooth backdrop fade */
|
||||
.modal-backdrop {
|
||||
animation: fade-in 0.3s ease-out;
|
||||
}
|
||||
|
||||
/* During close transition, fade backdrop out faster */
|
||||
body:has(::view-transition-old(create-session-modal)) .modal-backdrop {
|
||||
animation: fade-out 0.25s ease-out;
|
||||
}
|
||||
|
||||
/* Prevent flicker during modal close on mobile Safari */
|
||||
body.modal-closing .modal-content {
|
||||
opacity: 0 !important;
|
||||
pointer-events: none !important;
|
||||
}
|
||||
|
||||
body.modal-closing .modal-backdrop {
|
||||
opacity: 0 !important;
|
||||
}
|
||||
|
||||
|
||||
/* Black hole collapse animation for session removal */
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export type GitStatus = 'modified' | 'added' | 'deleted' | 'untracked' | 'unchan
|
|||
export function getFileIcon(fileName: string, type: FileType): TemplateResult {
|
||||
if (type === 'directory') {
|
||||
return html`
|
||||
<svg class="w-5 h-5 text-blue-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
<svg class="w-5 h-5 text-status-info" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M2 6a2 2 0 012-2h5l2 2h5a2 2 0 012 2v6a2 2 0 01-2 2H4a2 2 0 01-2-2V6z" />
|
||||
</svg>
|
||||
`;
|
||||
|
|
@ -26,216 +26,216 @@ export function getFileIcon(fileName: string, type: FileType): TemplateResult {
|
|||
// Icon map for different file extensions
|
||||
const iconMap: Record<string, TemplateResult> = {
|
||||
// JavaScript/TypeScript
|
||||
js: html`<svg class="w-5 h-5 text-yellow-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
js: html`<svg class="w-5 h-5 text-status-warning" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M4 4a2 2 0 00-2 2v8a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2H4zm6 3a1 1 0 011 1v2a1 1 0 11-2 0V9h-.5a.5.5 0 000 1H10a1 1 0 110 2H8.5A2.5 2.5 0 016 9.5V8a1 1 0 011-1h3z"
|
||||
/>
|
||||
</svg>`,
|
||||
mjs: html`<svg class="w-5 h-5 text-yellow-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
mjs: html`<svg class="w-5 h-5 text-status-warning" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M4 4a2 2 0 00-2 2v8a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2H4zm6 3a1 1 0 011 1v2a1 1 0 11-2 0V9h-.5a.5.5 0 000 1H10a1 1 0 110 2H8.5A2.5 2.5 0 016 9.5V8a1 1 0 011-1h3z"
|
||||
/>
|
||||
</svg>`,
|
||||
cjs: html`<svg class="w-5 h-5 text-yellow-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
cjs: html`<svg class="w-5 h-5 text-status-warning" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M4 4a2 2 0 00-2 2v8a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2H4zm6 3a1 1 0 011 1v2a1 1 0 11-2 0V9h-.5a.5.5 0 000 1H10a1 1 0 110 2H8.5A2.5 2.5 0 016 9.5V8a1 1 0 011-1h3z"
|
||||
/>
|
||||
</svg>`,
|
||||
ts: html`<svg class="w-5 h-5 text-blue-500" fill="currentColor" viewBox="0 0 20 20">
|
||||
ts: html`<svg class="w-5 h-5 text-status-info" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M4 4a2 2 0 00-2 2v8a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2H4zm6 3h4v1h-1v4a1 1 0 11-2 0V8h-1a1 1 0 110-2zM6 7h2v6H6V7z"
|
||||
/>
|
||||
</svg>`,
|
||||
tsx: html`<svg class="w-5 h-5 text-blue-500" fill="currentColor" viewBox="0 0 20 20">
|
||||
tsx: html`<svg class="w-5 h-5 text-status-info" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M4 4a2 2 0 00-2 2v8a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2H4zm6 3h4v1h-1v4a1 1 0 11-2 0V8h-1a1 1 0 110-2zM6 7h2v6H6V7z"
|
||||
/>
|
||||
</svg>`,
|
||||
jsx: html`<svg class="w-5 h-5 text-cyan-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
jsx: html`<svg class="w-5 h-5 text-status-info" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M4 4a2 2 0 00-2 2v8a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2H4zm2 6a2 2 0 114 0 2 2 0 01-4 0zm6-2a2 2 0 104 0 2 2 0 00-4 0z"
|
||||
/>
|
||||
</svg>`,
|
||||
|
||||
// Web files
|
||||
html: html`<svg class="w-5 h-5 text-orange-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
html: html`<svg class="w-5 h-5 text-status-warning" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm1 2h10v2H5V5zm0 4h10v2H5V9zm0 4h6v2H5v-2z"
|
||||
/>
|
||||
</svg>`,
|
||||
htm: html`<svg class="w-5 h-5 text-orange-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
htm: html`<svg class="w-5 h-5 text-status-warning" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm1 2h10v2H5V5zm0 4h10v2H5V9zm0 4h6v2H5v-2z"
|
||||
/>
|
||||
</svg>`,
|
||||
css: html`<svg class="w-5 h-5 text-pink-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
css: html`<svg class="w-5 h-5 text-primary" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm4 6a2 2 0 100 4 2 2 0 000-4zm4-2a2 2 0 100 4 2 2 0 000-4z"
|
||||
/>
|
||||
</svg>`,
|
||||
scss: html`<svg class="w-5 h-5 text-pink-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
scss: html`<svg class="w-5 h-5 text-primary" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm4 6a2 2 0 100 4 2 2 0 000-4zm4-2a2 2 0 100 4 2 2 0 000-4z"
|
||||
/>
|
||||
</svg>`,
|
||||
sass: html`<svg class="w-5 h-5 text-pink-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
sass: html`<svg class="w-5 h-5 text-primary" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm4 6a2 2 0 100 4 2 2 0 000-4zm4-2a2 2 0 100 4 2 2 0 000-4z"
|
||||
/>
|
||||
</svg>`,
|
||||
less: html`<svg class="w-5 h-5 text-pink-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
less: html`<svg class="w-5 h-5 text-primary" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm4 6a2 2 0 100 4 2 2 0 000-4zm4-2a2 2 0 100 4 2 2 0 000-4z"
|
||||
/>
|
||||
</svg>`,
|
||||
|
||||
// Config and data files
|
||||
json: html`<svg class="w-5 h-5 text-green-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
json: html`<svg class="w-5 h-5 text-status-success" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M3 4a1 1 0 011-1h12a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1V4zM3 10a1 1 0 011-1h6a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1v-2zM14 9a1 1 0 00-1 1v6a1 1 0 001 1h2a1 1 0 001-1v-6a1 1 0 00-1-1h-2z"
|
||||
/>
|
||||
</svg>`,
|
||||
jsonc: html`<svg class="w-5 h-5 text-green-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
jsonc: html`<svg class="w-5 h-5 text-status-success" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M3 4a1 1 0 011-1h12a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1V4zM3 10a1 1 0 011-1h6a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1v-2zM14 9a1 1 0 00-1 1v6a1 1 0 001 1h2a1 1 0 001-1v-6a1 1 0 00-1-1h-2z"
|
||||
/>
|
||||
</svg>`,
|
||||
xml: html`<svg class="w-5 h-5 text-purple-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
xml: html`<svg class="w-5 h-5 text-primary" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M3 4a1 1 0 011-1h12a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1V4zM3 10a1 1 0 011-1h6a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1v-2zM14 9a1 1 0 00-1 1v6a1 1 0 001 1h2a1 1 0 001-1v-6a1 1 0 00-1-1h-2z"
|
||||
/>
|
||||
</svg>`,
|
||||
yaml: html`<svg class="w-5 h-5 text-purple-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
yaml: html`<svg class="w-5 h-5 text-primary" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M3 4a1 1 0 011-1h12a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1V4zM3 10a1 1 0 011-1h6a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1v-2zM14 9a1 1 0 00-1 1v6a1 1 0 001 1h2a1 1 0 001-1v-6a1 1 0 00-1-1h-2z"
|
||||
/>
|
||||
</svg>`,
|
||||
yml: html`<svg class="w-5 h-5 text-purple-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
yml: html`<svg class="w-5 h-5 text-primary" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M3 4a1 1 0 011-1h12a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1V4zM3 10a1 1 0 011-1h6a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1v-2zM14 9a1 1 0 00-1 1v6a1 1 0 001 1h2a1 1 0 001-1v-6a1 1 0 00-1-1h-2z"
|
||||
/>
|
||||
</svg>`,
|
||||
|
||||
// Documentation
|
||||
md: html`<svg class="w-5 h-5 text-gray-300" fill="currentColor" viewBox="0 0 20 20">
|
||||
md: html`<svg class="w-5 h-5 text-text-muted" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M2 6a2 2 0 012-2h12a2 2 0 012 2v8a2 2 0 01-2 2H4a2 2 0 01-2-2V6zm2 0v8h12V6H4zm2 2h8v1H6V8zm0 2h8v1H6v-1zm0 2h6v1H6v-1z"
|
||||
/>
|
||||
</svg>`,
|
||||
markdown: html`<svg class="w-5 h-5 text-gray-300" fill="currentColor" viewBox="0 0 20 20">
|
||||
markdown: html`<svg class="w-5 h-5 text-text-muted" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M2 6a2 2 0 012-2h12a2 2 0 012 2v8a2 2 0 01-2 2H4a2 2 0 01-2-2V6zm2 0v8h12V6H4zm2 2h8v1H6V8zm0 2h8v1H6v-1zm0 2h6v1H6v-1z"
|
||||
/>
|
||||
</svg>`,
|
||||
txt: html`<svg class="w-5 h-5 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
txt: html`<svg class="w-5 h-5 text-text-dim" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm0 2h12v10H4V5zm2 2v6h8V7H6zm2 1h4v1H8V8zm0 2h4v1H8v-1z"
|
||||
/>
|
||||
</svg>`,
|
||||
text: html`<svg class="w-5 h-5 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
text: html`<svg class="w-5 h-5 text-text-dim" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm0 2h12v10H4V5zm2 2v6h8V7H6zm2 1h4v1H8V8zm0 2h4v1H8v-1z"
|
||||
/>
|
||||
</svg>`,
|
||||
|
||||
// Images
|
||||
png: html`<svg class="w-5 h-5 text-green-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
png: html`<svg class="w-5 h-5 text-status-success" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm12 12H4l4-8 3 6 2-4 3 6z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>`,
|
||||
jpg: html`<svg class="w-5 h-5 text-green-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
jpg: html`<svg class="w-5 h-5 text-status-success" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm12 12H4l4-8 3 6 2-4 3 6z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>`,
|
||||
jpeg: html`<svg class="w-5 h-5 text-green-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
jpeg: html`<svg class="w-5 h-5 text-status-success" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm12 12H4l4-8 3 6 2-4 3 6z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>`,
|
||||
gif: html`<svg class="w-5 h-5 text-green-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
gif: html`<svg class="w-5 h-5 text-status-success" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm12 12H4l4-8 3 6 2-4 3 6z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>`,
|
||||
webp: html`<svg class="w-5 h-5 text-green-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
webp: html`<svg class="w-5 h-5 text-status-success" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm12 12H4l4-8 3 6 2-4 3 6z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>`,
|
||||
bmp: html`<svg class="w-5 h-5 text-green-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
bmp: html`<svg class="w-5 h-5 text-status-success" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm12 12H4l4-8 3 6 2-4 3 6z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>`,
|
||||
svg: html`<svg class="w-5 h-5 text-indigo-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
svg: html`<svg class="w-5 h-5 text-primary" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm6 6L8 7l2 2 2-2-2 2 2 2-2-2-2 2 2-2z"
|
||||
/>
|
||||
</svg>`,
|
||||
|
||||
// Archives
|
||||
zip: html`<svg class="w-5 h-5 text-amber-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
zip: html`<svg class="w-5 h-5 text-status-warning" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M3 4a1 1 0 011-1h12a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1V4zM3 10a1 1 0 011-1h6a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1v-2zM14 9a1 1 0 00-1 1v6a1 1 0 001 1h2a1 1 0 001-1v-6a1 1 0 00-1-1h-2z"
|
||||
/>
|
||||
</svg>`,
|
||||
tar: html`<svg class="w-5 h-5 text-amber-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
tar: html`<svg class="w-5 h-5 text-status-warning" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M3 4a1 1 0 011-1h12a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1V4zM3 10a1 1 0 011-1h6a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1v-2zM14 9a1 1 0 00-1 1v6a1 1 0 001 1h2a1 1 0 001-1v-6a1 1 0 00-1-1h-2z"
|
||||
/>
|
||||
</svg>`,
|
||||
gz: html`<svg class="w-5 h-5 text-amber-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
gz: html`<svg class="w-5 h-5 text-status-warning" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M3 4a1 1 0 011-1h12a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1V4zM3 10a1 1 0 011-1h6a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1v-2zM14 9a1 1 0 00-1 1v6a1 1 0 001 1h2a1 1 0 001-1v-6a1 1 0 00-1-1h-2z"
|
||||
/>
|
||||
</svg>`,
|
||||
rar: html`<svg class="w-5 h-5 text-amber-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
rar: html`<svg class="w-5 h-5 text-status-warning" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M3 4a1 1 0 011-1h12a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1V4zM3 10a1 1 0 011-1h6a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1v-2zM14 9a1 1 0 00-1 1v6a1 1 0 001 1h2a1 1 0 001-1v-6a1 1 0 00-1-1h-2z"
|
||||
/>
|
||||
</svg>`,
|
||||
'7z': html`<svg class="w-5 h-5 text-amber-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
'7z': html`<svg class="w-5 h-5 text-status-warning" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M3 4a1 1 0 011-1h12a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1V4zM3 10a1 1 0 011-1h6a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1v-2zM14 9a1 1 0 00-1 1v6a1 1 0 001 1h2a1 1 0 001-1v-6a1 1 0 00-1-1h-2z"
|
||||
/>
|
||||
</svg>`,
|
||||
|
||||
// Documents
|
||||
pdf: html`<svg class="w-5 h-5 text-red-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
pdf: html`<svg class="w-5 h-5 text-status-error" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M4 18h12V6h-4V2H4v16zm8-14v4h4l-4-4zM6 10h8v1H6v-1zm0 2h8v1H6v-1zm0 2h6v1H6v-1z" />
|
||||
</svg>`,
|
||||
|
||||
// Scripts
|
||||
sh: html`<svg class="w-5 h-5 text-green-500" fill="currentColor" viewBox="0 0 20 20">
|
||||
sh: html`<svg class="w-5 h-5 text-status-success" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M3 4a1 1 0 000 2h11.586l-2.293 2.293a1 1 0 101.414 1.414L17.414 6H19a1 1 0 100-2H3zM3 11a1 1 0 100 2h3.586l-2.293 2.293a1 1 0 101.414 1.414L9.414 13H11a1 1 0 100-2H3z"
|
||||
/>
|
||||
</svg>`,
|
||||
bash: html`<svg class="w-5 h-5 text-green-500" fill="currentColor" viewBox="0 0 20 20">
|
||||
bash: html`<svg class="w-5 h-5 text-status-success" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M3 4a1 1 0 000 2h11.586l-2.293 2.293a1 1 0 101.414 1.414L17.414 6H19a1 1 0 100-2H3zM3 11a1 1 0 100 2h3.586l-2.293 2.293a1 1 0 101.414 1.414L9.414 13H11a1 1 0 100-2H3z"
|
||||
/>
|
||||
</svg>`,
|
||||
zsh: html`<svg class="w-5 h-5 text-green-500" fill="currentColor" viewBox="0 0 20 20">
|
||||
zsh: html`<svg class="w-5 h-5 text-status-success" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M3 4a1 1 0 000 2h11.586l-2.293 2.293a1 1 0 101.414 1.414L17.414 6H19a1 1 0 100-2H3zM3 11a1 1 0 100 2h3.586l-2.293 2.293a1 1 0 101.414 1.414L9.414 13H11a1 1 0 100-2H3z"
|
||||
/>
|
||||
</svg>`,
|
||||
fish: html`<svg class="w-5 h-5 text-green-500" fill="currentColor" viewBox="0 0 20 20">
|
||||
fish: html`<svg class="w-5 h-5 text-status-success" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M3 4a1 1 0 000 2h11.586l-2.293 2.293a1 1 0 101.414 1.414L17.414 6H19a1 1 0 100-2H3zM3 11a1 1 0 100 2h3.586l-2.293 2.293a1 1 0 101.414 1.414L9.414 13H11a1 1 0 100-2H3z"
|
||||
/>
|
||||
|
|
@ -251,7 +251,7 @@ export function getFileIcon(fileName: string, type: FileType): TemplateResult {
|
|||
*/
|
||||
export function getDefaultFileIcon(): TemplateResult {
|
||||
return html`
|
||||
<svg class="w-5 h-5 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
<svg class="w-5 h-5 text-text-dim" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4zm2 6a1 1 0 011-1h6a1 1 0 110 2H7a1 1 0 01-1-1zm1 3a1 1 0 100 2h6a1 1 0 100-2H7z"
|
||||
|
|
@ -266,7 +266,7 @@ export function getDefaultFileIcon(): TemplateResult {
|
|||
*/
|
||||
export function getParentDirectoryIcon(): TemplateResult {
|
||||
return html`
|
||||
<svg class="w-5 h-5 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
<svg class="w-5 h-5 text-text-dim" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M9.707 16.707a1 1 0 01-1.414 0l-6-6a1 1 0 010-1.414l6-6a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l4.293 4.293a1 1 0 010 1.414z"
|
||||
|
|
@ -291,10 +291,10 @@ export function renderGitStatusBadge(status?: GitStatus): TemplateResult | strin
|
|||
};
|
||||
|
||||
const colorClasses: Record<GitStatus, string> = {
|
||||
modified: 'bg-yellow-900/50 text-yellow-400',
|
||||
added: 'bg-green-900/50 text-green-400',
|
||||
deleted: 'bg-red-900/50 text-red-400',
|
||||
untracked: 'bg-gray-700 text-gray-400',
|
||||
modified: 'bg-status-warning/20 text-status-warning',
|
||||
added: 'bg-status-success/20 text-status-success',
|
||||
deleted: 'bg-status-error/20 text-status-error',
|
||||
untracked: 'bg-text-dim/20 text-text-dim',
|
||||
unchanged: '',
|
||||
};
|
||||
|
||||
|
|
@ -321,7 +321,7 @@ export const UIIcons = {
|
|||
`,
|
||||
|
||||
folder: html`
|
||||
<svg class="w-6 h-6 text-blue-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
<svg class="w-6 h-6 text-status-info" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M2 6a2 2 0 012-2h5l2 2h5a2 2 0 012 2v6a2 2 0 01-2 2H4a2 2 0 01-2-2V6z" />
|
||||
</svg>
|
||||
`,
|
||||
|
|
@ -336,7 +336,7 @@ export const UIIcons = {
|
|||
`,
|
||||
|
||||
preview: html`
|
||||
<svg class="w-16 h-16 mb-4 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
<svg class="w-16 h-16 mb-4 text-text-dim" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4zm2 6a1 1 0 011-1h6a1 1 0 110 2H7a1 1 0 01-1-1zm1 3a1 1 0 100 2h6a1 1 0 100-2H7z"
|
||||
|
|
@ -346,7 +346,7 @@ export const UIIcons = {
|
|||
`,
|
||||
|
||||
binary: html`
|
||||
<svg class="w-16 h-16 mb-4 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
<svg class="w-16 h-16 mb-4 text-text-dim" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M3 4a1 1 0 011-1h12a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1V4zM3 10a1 1 0 011-1h6a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1v-2zM14 9a1 1 0 00-1 1v6a1 1 0 001 1h2a1 1 0 001-1v-6a1 1 0 00-1-1h-2z"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -107,7 +107,20 @@ export async function initializeMonaco(): Promise<void> {
|
|||
},
|
||||
});
|
||||
|
||||
monaco.editor.setTheme('vs-dark');
|
||||
// Set initial theme based on current theme
|
||||
const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
|
||||
monaco.editor.setTheme(isDark ? 'vs-dark' : 'vs');
|
||||
|
||||
// Watch for theme changes
|
||||
const themeObserver = new MutationObserver(() => {
|
||||
const currentTheme = document.documentElement.getAttribute('data-theme') === 'dark';
|
||||
monaco.editor.setTheme(currentTheme ? 'vs-dark' : 'vs');
|
||||
});
|
||||
|
||||
themeObserver.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ['data-theme'],
|
||||
});
|
||||
|
||||
// Add custom themes if needed
|
||||
/*monaco.editor.defineTheme('vibetunnel-dark', {
|
||||
|
|
|
|||
|
|
@ -4,24 +4,24 @@ module.exports = {
|
|||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
// Unified Dark theme colors with consistent depth
|
||||
"dark-bg": "#0a0a0a",
|
||||
"dark-bg-secondary": "#141414",
|
||||
"dark-bg-tertiary": "#1f1f1f",
|
||||
"dark-bg-elevated": "#262626",
|
||||
"dark-surface": "#1a1a1a",
|
||||
"dark-surface-hover": "#2a2a2a",
|
||||
"dark-border": "#2a2a2a",
|
||||
"dark-border-light": "#3a3a3a",
|
||||
"dark-border-focus": "#4a4a4a",
|
||||
// Theme-aware colors using CSS variables
|
||||
"bg": "rgb(var(--color-bg) / <alpha-value>)",
|
||||
"bg-secondary": "rgb(var(--color-bg-secondary) / <alpha-value>)",
|
||||
"bg-tertiary": "rgb(var(--color-bg-tertiary) / <alpha-value>)",
|
||||
"bg-elevated": "rgb(var(--color-bg-elevated) / <alpha-value>)",
|
||||
"surface": "rgb(var(--color-surface) / <alpha-value>)",
|
||||
"surface-hover": "rgb(var(--color-surface-hover) / <alpha-value>)",
|
||||
"border": "rgb(var(--color-border) / <alpha-value>)",
|
||||
"border-light": "rgb(var(--color-border-light) / <alpha-value>)",
|
||||
"border-focus": "rgb(var(--color-border-focus) / <alpha-value>)",
|
||||
|
||||
// Text colors
|
||||
"dark-text": "#e4e4e4",
|
||||
"dark-text-bright": "#ffffff",
|
||||
"dark-text-muted": "#a3a3a3",
|
||||
"dark-text-dim": "#737373",
|
||||
"text": "rgb(var(--color-text) / <alpha-value>)",
|
||||
"text-bright": "rgb(var(--color-text-bright) / <alpha-value>)",
|
||||
"text-muted": "rgb(var(--color-text-muted) / <alpha-value>)",
|
||||
"text-dim": "rgb(var(--color-text-dim) / <alpha-value>)",
|
||||
|
||||
// Unified accent color - Vibrant teal-green
|
||||
// Unified accent color - Vibrant teal-green (stays the same across themes)
|
||||
"primary": "#10B981",
|
||||
"primary-hover": "#059669",
|
||||
"primary-dark": "#047857",
|
||||
|
|
@ -34,18 +34,6 @@ module.exports = {
|
|||
"status-warning": "#F59E0B",
|
||||
"status-success": "#10B981",
|
||||
"status-info": "#3B82F6",
|
||||
|
||||
// Legacy mappings for gradual migration
|
||||
"accent-primary": "#10B981",
|
||||
"accent-primary-dark": "#059669",
|
||||
"accent-primary-darker": "#047857",
|
||||
"accent-primary-light": "#34D399",
|
||||
"accent-primary-glow": "#10B98166",
|
||||
"accent-green": "#10B981",
|
||||
"accent-green-dark": "#059669",
|
||||
"accent-green-darker": "#047857",
|
||||
"accent-green-light": "#34D399",
|
||||
"accent-green-glow": "#10B98166",
|
||||
},
|
||||
boxShadow: {
|
||||
// Unified glow effects with primary color
|
||||
|
|
@ -53,20 +41,19 @@ module.exports = {
|
|||
'glow-sm': '0 0 10px rgba(16, 185, 129, 0.3)',
|
||||
'glow-lg': '0 0 30px rgba(16, 185, 129, 0.5)',
|
||||
'glow-intense': '0 0 40px rgba(16, 185, 129, 0.6)',
|
||||
// Legacy mappings
|
||||
'glow-primary': '0 0 20px rgba(16, 185, 129, 0.4)',
|
||||
'glow-primary-sm': '0 0 10px rgba(16, 185, 129, 0.3)',
|
||||
'glow-primary-lg': '0 0 30px rgba(16, 185, 129, 0.5)',
|
||||
'glow-green': '0 0 20px rgba(16, 185, 129, 0.4)',
|
||||
'glow-green-sm': '0 0 10px rgba(16, 185, 129, 0.3)',
|
||||
'glow-green-lg': '0 0 30px rgba(16, 185, 129, 0.5)',
|
||||
// Status-specific glow effects
|
||||
'glow-error': '0 0 20px rgba(239, 68, 68, 0.4)',
|
||||
'glow-error-sm': '0 0 10px rgba(239, 68, 68, 0.3)',
|
||||
'glow-error-lg': '0 0 30px rgba(239, 68, 68, 0.5)',
|
||||
'glow-warning': '0 0 20px rgba(245, 158, 11, 0.4)',
|
||||
'glow-warning-sm': '0 0 10px rgba(245, 158, 11, 0.3)',
|
||||
'glow-warning-lg': '0 0 30px rgba(245, 158, 11, 0.5)',
|
||||
// Subtle shadows for depth
|
||||
'card': '0 1px 3px rgba(0, 0, 0, 0.3), 0 1px 2px rgba(0, 0, 0, 0.4)',
|
||||
'card-hover': '0 4px 6px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.4)',
|
||||
'elevated': '0 10px 15px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.2)',
|
||||
},
|
||||
animation: {
|
||||
'pulse-green': 'pulseGreen 2s cubic-bezier(0.4, 0, 0.6, 1) infinite',
|
||||
'pulse-primary': 'pulsePrimary 2s cubic-bezier(0.4, 0, 0.6, 1) infinite',
|
||||
'slide-in-right': 'slideInRight 0.3s ease-out',
|
||||
'slide-in-bottom': 'slideInBottom 0.3s ease-out',
|
||||
|
|
@ -74,14 +61,6 @@ module.exports = {
|
|||
'scale-in': 'scaleIn 0.2s ease-out',
|
||||
},
|
||||
keyframes: {
|
||||
pulseGreen: {
|
||||
'0%, 100%': {
|
||||
opacity: '1',
|
||||
},
|
||||
'50%': {
|
||||
opacity: '.8',
|
||||
},
|
||||
},
|
||||
pulsePrimary: {
|
||||
'0%, 100%': {
|
||||
opacity: '1',
|
||||
|
|
|
|||
Loading…
Reference in a new issue