feat(frontend): add complete CSS design system and HTML shell
Create the Nexus Terminal design system with dark charcoal palette, teal accent, and premium desktop application styling: - tokens.css: Design tokens (colours, spacing, typography, layout) - base.css: Modern reset, scrollbar, focus, and utility styles - components.css: Buttons, forms, badges, dropdowns, notifications, disclosure, search, empty state, tooltip, spinner - layout.css: App grid shell (sidebar, tab bar, workspace, status bar) - sidebar.css: Connection manager with sections, profile items, search - tabs.css: Compact session tabs with status dots, protocol badges - terminal.css: xterm.js container, RAW mode views, send bar, search - forms.css: Profile editor sections, tag input, jump hosts, validation - modals.css: Modal, drawer, confirmation, host key, settings dialogs - index.html: Complete HTML shell with sidebar, tabs, workspace, status
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Nexus Terminal</title>
|
||||
|
||||
<!-- Design system -->
|
||||
<link rel="stylesheet" href="/src/css/tokens.css">
|
||||
<link rel="stylesheet" href="/src/css/base.css">
|
||||
<link rel="stylesheet" href="/src/css/components.css">
|
||||
<link rel="stylesheet" href="/src/css/layout.css">
|
||||
<link rel="stylesheet" href="/src/css/sidebar.css">
|
||||
<link rel="stylesheet" href="/src/css/tabs.css">
|
||||
<link rel="stylesheet" href="/src/css/terminal.css">
|
||||
<link rel="stylesheet" href="/src/css/forms.css">
|
||||
<link rel="stylesheet" href="/src/css/modals.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" class="app">
|
||||
|
||||
<!-- ============================================================
|
||||
Sidebar — connection manager
|
||||
============================================================ -->
|
||||
<aside class="sidebar" id="sidebar">
|
||||
<div class="sidebar-header wails-drag">
|
||||
<div class="sidebar-logo">
|
||||
<!-- Terminal prompt icon -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="4 17 10 11 4 5"></polyline>
|
||||
<line x1="12" y1="19" x2="20" y2="19"></line>
|
||||
</svg>
|
||||
<span class="sidebar-title">Nexus Terminal</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-search">
|
||||
<div class="search-wrapper">
|
||||
<input type="text"
|
||||
class="search-input"
|
||||
id="profile-search"
|
||||
placeholder="Search connections…"
|
||||
autocomplete="off"
|
||||
spellcheck="false">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="sidebar-nav" id="sidebar-nav">
|
||||
<!-- Favourites -->
|
||||
<div class="sidebar-section" id="favourites-section">
|
||||
<button class="sidebar-section-header" data-section="favourites">
|
||||
<span class="section-icon">★</span>
|
||||
<span>Favourites</span>
|
||||
<span class="section-count" id="favourites-count">0</span>
|
||||
</button>
|
||||
<div class="sidebar-list" id="favourites-list">
|
||||
<div class="sidebar-list-empty">No favourites yet</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent -->
|
||||
<div class="sidebar-section" id="recent-section">
|
||||
<button class="sidebar-section-header" data-section="recent">
|
||||
<span class="section-icon">⟳</span>
|
||||
<span>Recent</span>
|
||||
</button>
|
||||
<div class="sidebar-list" id="recent-list">
|
||||
<div class="sidebar-list-empty">No recent sessions</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- All Connections -->
|
||||
<div class="sidebar-section" id="all-section">
|
||||
<button class="sidebar-section-header" data-section="all">
|
||||
<span class="section-icon">⊞</span>
|
||||
<span>All Connections</span>
|
||||
<span class="section-count" id="all-count">0</span>
|
||||
</button>
|
||||
<div class="sidebar-list" id="all-list">
|
||||
<div class="sidebar-list-empty">No saved connections</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="sidebar-footer">
|
||||
<button class="btn btn-ghost btn-sm" id="btn-new-connection" title="New Connection">
|
||||
<span>+ New</span>
|
||||
</button>
|
||||
<button class="btn btn-ghost btn-icon btn-sm" id="btn-import" title="Import">
|
||||
<!-- Upload icon -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
||||
<polyline points="17 8 12 3 7 8"></polyline>
|
||||
<line x1="12" y1="3" x2="12" y2="15"></line>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="btn btn-ghost btn-icon btn-sm" id="btn-settings" title="Settings">
|
||||
<!-- Cog icon -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="3"></circle>
|
||||
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0
|
||||
0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65
|
||||
0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0
|
||||
0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9
|
||||
19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0
|
||||
1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0
|
||||
0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0
|
||||
1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6
|
||||
9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0
|
||||
1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65
|
||||
0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2
|
||||
2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0
|
||||
0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2
|
||||
2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65
|
||||
1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51
|
||||
1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65
|
||||
1.65 0 0 0-1.51 1z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- ============================================================
|
||||
Tab bar — session tabs
|
||||
============================================================ -->
|
||||
<div class="tab-bar" id="tab-bar">
|
||||
<div class="tab-list" id="tab-list">
|
||||
<!-- Tabs are injected dynamically by JS -->
|
||||
</div>
|
||||
<button class="tab-new" id="btn-new-tab" title="New Tab">+</button>
|
||||
</div>
|
||||
|
||||
<!-- ============================================================
|
||||
Workspace — terminal area
|
||||
============================================================ -->
|
||||
<main class="workspace" id="workspace">
|
||||
<!-- Empty state — shown when no sessions are active -->
|
||||
<div class="empty-state" id="empty-state">
|
||||
<div class="empty-state-icon">
|
||||
<!-- Terminal icon -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor" stroke-width="1.5"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="4 17 10 11 4 5"></polyline>
|
||||
<line x1="12" y1="19" x2="20" y2="19"></line>
|
||||
</svg>
|
||||
</div>
|
||||
<h2>No active sessions</h2>
|
||||
<p>Select a connection from the sidebar or create a new one to get started.</p>
|
||||
<div class="empty-state-actions">
|
||||
<button class="btn btn-primary" id="btn-empty-connect">New Connection</button>
|
||||
<button class="btn btn-secondary" id="btn-empty-quick">Quick Connect</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Session containers are injected here dynamically -->
|
||||
</main>
|
||||
|
||||
<!-- ============================================================
|
||||
Status bar
|
||||
============================================================ -->
|
||||
<footer class="status-bar" id="status-bar">
|
||||
<span id="status-connection">
|
||||
<span class="status-dot is-disconnected"></span>
|
||||
Disconnected
|
||||
</span>
|
||||
<span id="status-protocol"></span>
|
||||
<span id="status-session-count">0 sessions</span>
|
||||
<span class="status-spacer"></span>
|
||||
<span id="status-vault">🔒 Locked</span>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<!-- Modal container (dynamically populated) -->
|
||||
<div id="modals"></div>
|
||||
|
||||
<!-- Notification toasts -->
|
||||
<div id="notifications" class="notifications-container"></div>
|
||||
|
||||
<!-- Wails runtime (injected by the Wails framework) -->
|
||||
<script src="/wails/ipc.js"></script>
|
||||
<script src="/wails/runtime.js"></script>
|
||||
|
||||
<!-- Vendor libraries -->
|
||||
<script src="/src/js/lib/xterm.min.js"></script>
|
||||
<script src="/src/js/lib/xterm-addon-fit.min.js"></script>
|
||||
<script src="/src/js/lib/xterm-addon-search.min.js"></script>
|
||||
<script src="/src/js/lib/xterm-addon-web-links.min.js"></script>
|
||||
|
||||
<!-- Application entry point -->
|
||||
<script type="module" src="/src/js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,187 @@
|
||||
/* ==========================================================================
|
||||
Nexus Terminal — Base / Reset
|
||||
Modern CSS reset and foundational element styles.
|
||||
========================================================================== */
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Box-model reset
|
||||
----------------------------------------------------------------------- */
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Root element
|
||||
----------------------------------------------------------------------- */
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
font-family: var(--font-sans);
|
||||
font-size: var(--text-base);
|
||||
line-height: var(--leading-normal);
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-base);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Scrollbar styling (Webkit / Blink)
|
||||
----------------------------------------------------------------------- */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--border-strong);
|
||||
border-radius: var(--radius-full);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--text-tertiary);
|
||||
}
|
||||
|
||||
/* Firefox scrollbar */
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--border-strong) transparent;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Text selection
|
||||
----------------------------------------------------------------------- */
|
||||
::selection {
|
||||
background: var(--accent-strong);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Focus management
|
||||
----------------------------------------------------------------------- */
|
||||
:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
:focus:not(:focus-visible) {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Links
|
||||
----------------------------------------------------------------------- */
|
||||
a {
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
transition: color var(--transition-fast);
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--accent-hover);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Images
|
||||
----------------------------------------------------------------------- */
|
||||
img,
|
||||
svg {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Lists
|
||||
----------------------------------------------------------------------- */
|
||||
ul,
|
||||
ol {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Tables
|
||||
----------------------------------------------------------------------- */
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Buttons & inputs — inherit font
|
||||
----------------------------------------------------------------------- */
|
||||
button,
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
background: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Monospace elements
|
||||
----------------------------------------------------------------------- */
|
||||
code,
|
||||
pre,
|
||||
kbd,
|
||||
.mono {
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Hidden utility
|
||||
----------------------------------------------------------------------- */
|
||||
[hidden],
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Text utilities
|
||||
----------------------------------------------------------------------- */
|
||||
.truncate {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.text-secondary { color: var(--text-secondary); }
|
||||
.text-tertiary { color: var(--text-tertiary); }
|
||||
.text-accent { color: var(--accent); }
|
||||
.text-success { color: var(--success); }
|
||||
.text-warning { color: var(--warning); }
|
||||
.text-error { color: var(--error); }
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Wails window drag region
|
||||
----------------------------------------------------------------------- */
|
||||
.wails-drag {
|
||||
--wails-draggable: drag;
|
||||
}
|
||||
@@ -0,0 +1,864 @@
|
||||
/* ==========================================================================
|
||||
Nexus Terminal — Reusable Components
|
||||
Buttons, form controls, badges, dropdowns, modals, notifications, etc.
|
||||
========================================================================== */
|
||||
|
||||
/* =======================================================================
|
||||
BUTTONS
|
||||
======================================================================= */
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--space-2);
|
||||
height: 30px;
|
||||
padding: 0 var(--space-3);
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--weight-medium);
|
||||
line-height: 1;
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid transparent;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition:
|
||||
background var(--transition-fast),
|
||||
border-color var(--transition-fast),
|
||||
color var(--transition-fast),
|
||||
box-shadow var(--transition-fast);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.45;
|
||||
cursor: not-allowed;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Primary — accent filled */
|
||||
.btn-primary {
|
||||
background: var(--accent);
|
||||
color: var(--text-inverse);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
.btn-primary:hover {
|
||||
background: var(--accent-hover);
|
||||
border-color: var(--accent-hover);
|
||||
}
|
||||
.btn-primary:active {
|
||||
background: #3da69f;
|
||||
border-color: #3da69f;
|
||||
}
|
||||
|
||||
/* Secondary — subtle border */
|
||||
.btn-secondary {
|
||||
background: transparent;
|
||||
color: var(--text-secondary);
|
||||
border-color: var(--border-default);
|
||||
}
|
||||
.btn-secondary:hover {
|
||||
background: var(--bg-hover);
|
||||
color: var(--text-primary);
|
||||
border-color: var(--border-strong);
|
||||
}
|
||||
.btn-secondary:active {
|
||||
background: var(--bg-active);
|
||||
}
|
||||
|
||||
/* Ghost — transparent, hover only */
|
||||
.btn-ghost {
|
||||
background: transparent;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.btn-ghost:hover {
|
||||
background: var(--bg-hover);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.btn-ghost:active {
|
||||
background: var(--bg-active);
|
||||
}
|
||||
|
||||
/* Danger */
|
||||
.btn-danger {
|
||||
background: transparent;
|
||||
color: var(--error);
|
||||
border-color: var(--error);
|
||||
}
|
||||
.btn-danger:hover {
|
||||
background: rgba(239, 83, 80, 0.12);
|
||||
}
|
||||
.btn-danger:active {
|
||||
background: rgba(239, 83, 80, 0.20);
|
||||
}
|
||||
|
||||
.btn-danger-filled {
|
||||
background: var(--error);
|
||||
color: #fff;
|
||||
border-color: var(--error);
|
||||
}
|
||||
.btn-danger-filled:hover {
|
||||
background: #e53935;
|
||||
border-color: #e53935;
|
||||
}
|
||||
|
||||
/* Size variants */
|
||||
.btn-sm {
|
||||
height: 26px;
|
||||
padding: 0 var(--space-2);
|
||||
font-size: var(--text-xs);
|
||||
}
|
||||
|
||||
.btn-xs {
|
||||
height: 22px;
|
||||
padding: 0 var(--space-2);
|
||||
font-size: var(--text-xs);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
/* Icon-only button (square) */
|
||||
.btn-icon {
|
||||
width: 30px;
|
||||
padding: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.btn-icon.btn-sm {
|
||||
width: 26px;
|
||||
}
|
||||
.btn-icon.btn-xs {
|
||||
width: 22px;
|
||||
}
|
||||
|
||||
/* Button group */
|
||||
.btn-group {
|
||||
display: inline-flex;
|
||||
}
|
||||
.btn-group .btn {
|
||||
border-radius: 0;
|
||||
}
|
||||
.btn-group .btn:first-child {
|
||||
border-radius: var(--radius-md) 0 0 var(--radius-md);
|
||||
}
|
||||
.btn-group .btn:last-child {
|
||||
border-radius: 0 var(--radius-md) var(--radius-md) 0;
|
||||
}
|
||||
.btn-group .btn + .btn {
|
||||
margin-left: -1px;
|
||||
}
|
||||
|
||||
|
||||
/* =======================================================================
|
||||
FORM CONTROLS
|
||||
======================================================================= */
|
||||
|
||||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-1);
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--weight-medium);
|
||||
color: var(--text-secondary);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.form-label-required::after {
|
||||
content: ' *';
|
||||
color: var(--error);
|
||||
}
|
||||
|
||||
/* Text input, select, textarea share a common treatment */
|
||||
.form-input,
|
||||
.form-select,
|
||||
.form-textarea {
|
||||
height: 30px;
|
||||
padding: 0 var(--space-3);
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-input);
|
||||
border: 1px solid var(--border-default);
|
||||
border-radius: var(--radius-md);
|
||||
transition:
|
||||
border-color var(--transition-fast),
|
||||
box-shadow var(--transition-fast);
|
||||
}
|
||||
|
||||
.form-input::placeholder,
|
||||
.form-textarea::placeholder {
|
||||
color: var(--text-disabled);
|
||||
}
|
||||
|
||||
.form-input:hover,
|
||||
.form-select:hover,
|
||||
.form-textarea:hover {
|
||||
border-color: var(--border-strong);
|
||||
}
|
||||
|
||||
.form-input:focus,
|
||||
.form-select:focus,
|
||||
.form-textarea:focus {
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 2px var(--accent-muted);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.form-input:disabled,
|
||||
.form-select:disabled,
|
||||
.form-textarea:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Select arrow */
|
||||
.form-select {
|
||||
appearance: none;
|
||||
padding-right: var(--space-8);
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%236e6e88'/%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 10px center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Textarea */
|
||||
.form-textarea {
|
||||
height: auto;
|
||||
min-height: 64px;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
resize: vertical;
|
||||
line-height: var(--leading-normal);
|
||||
}
|
||||
|
||||
/* Help text */
|
||||
.form-help {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-tertiary);
|
||||
line-height: var(--leading-normal);
|
||||
}
|
||||
|
||||
/* Error message */
|
||||
.form-error {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--error);
|
||||
line-height: var(--leading-normal);
|
||||
}
|
||||
|
||||
/* Error state on inputs */
|
||||
.form-input.is-error,
|
||||
.form-select.is-error,
|
||||
.form-textarea.is-error {
|
||||
border-color: var(--error);
|
||||
}
|
||||
.form-input.is-error:focus,
|
||||
.form-select.is-error:focus,
|
||||
.form-textarea.is-error:focus {
|
||||
box-shadow: 0 0 0 2px rgba(239, 83, 80, 0.20);
|
||||
}
|
||||
|
||||
/* Inline layout (label beside control) */
|
||||
.form-inline {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
.form-inline .form-label {
|
||||
min-width: 100px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Checkbox */
|
||||
.form-checkbox {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
cursor: pointer;
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-secondary);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.form-checkbox input[type="checkbox"] {
|
||||
appearance: none;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 1px solid var(--border-strong);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--bg-input);
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background var(--transition-fast),
|
||||
border-color var(--transition-fast);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.form-checkbox input[type="checkbox"]:checked {
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.form-checkbox input[type="checkbox"]:checked::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 5px;
|
||||
width: 4px;
|
||||
height: 8px;
|
||||
border: solid var(--text-inverse);
|
||||
border-width: 0 2px 2px 0;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.form-checkbox input[type="checkbox"]:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* Toggle switch */
|
||||
.form-toggle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
cursor: pointer;
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-secondary);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.form-toggle input[type="checkbox"] {
|
||||
appearance: none;
|
||||
width: 32px;
|
||||
height: 18px;
|
||||
border-radius: var(--radius-full);
|
||||
background: var(--border-strong);
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
transition: background var(--transition-normal);
|
||||
}
|
||||
|
||||
.form-toggle input[type="checkbox"]::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 50%;
|
||||
background: var(--text-secondary);
|
||||
transition: transform var(--transition-normal), background var(--transition-normal);
|
||||
}
|
||||
|
||||
.form-toggle input[type="checkbox"]:checked {
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
.form-toggle input[type="checkbox"]:checked::after {
|
||||
transform: translateX(14px);
|
||||
background: var(--text-inverse);
|
||||
}
|
||||
|
||||
|
||||
/* =======================================================================
|
||||
BADGES
|
||||
======================================================================= */
|
||||
|
||||
.badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 18px;
|
||||
padding: 0 var(--space-2);
|
||||
font-size: 10px;
|
||||
font-weight: var(--weight-semibold);
|
||||
line-height: 1;
|
||||
border-radius: var(--radius-sm);
|
||||
white-space: nowrap;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
background: var(--bg-overlay);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* Protocol badges */
|
||||
.badge-ssh {
|
||||
background: rgba(78, 205, 196, 0.15);
|
||||
color: var(--protocol-ssh);
|
||||
}
|
||||
.badge-telnet {
|
||||
background: rgba(255, 167, 38, 0.15);
|
||||
color: var(--protocol-telnet);
|
||||
}
|
||||
.badge-raw {
|
||||
background: rgba(171, 71, 188, 0.15);
|
||||
color: var(--protocol-raw);
|
||||
}
|
||||
|
||||
/* Status badges */
|
||||
.badge-status {
|
||||
gap: var(--space-1);
|
||||
}
|
||||
.badge-status::before {
|
||||
content: '';
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: var(--text-tertiary);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.badge-status.is-connected::before { background: var(--success); }
|
||||
.badge-status.is-connecting::before { background: var(--warning); animation: pulse-dot 1.2s ease infinite; }
|
||||
.badge-status.is-error::before { background: var(--error); }
|
||||
.badge-status.is-disconnected::before { background: var(--text-tertiary); }
|
||||
|
||||
@keyframes pulse-dot {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.4; }
|
||||
}
|
||||
|
||||
|
||||
/* =======================================================================
|
||||
CARDS & SURFACES
|
||||
======================================================================= */
|
||||
|
||||
.surface {
|
||||
background: var(--bg-elevated);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.card {
|
||||
background: var(--bg-elevated);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--space-4);
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background var(--transition-fast),
|
||||
border-color var(--transition-fast),
|
||||
box-shadow var(--transition-fast);
|
||||
}
|
||||
.card:hover {
|
||||
background: var(--bg-hover);
|
||||
border-color: var(--border-default);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
.card:active {
|
||||
background: var(--bg-active);
|
||||
}
|
||||
|
||||
|
||||
/* =======================================================================
|
||||
DROPDOWN / CONTEXT MENU
|
||||
======================================================================= */
|
||||
|
||||
.dropdown {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
z-index: var(--z-dropdown);
|
||||
min-width: 160px;
|
||||
padding: var(--space-1) 0;
|
||||
background: var(--bg-overlay);
|
||||
border: 1px solid var(--border-default);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-lg);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transform: translateY(-4px);
|
||||
transition:
|
||||
opacity var(--transition-fast),
|
||||
visibility var(--transition-fast),
|
||||
transform var(--transition-fast);
|
||||
}
|
||||
|
||||
.dropdown.is-open .dropdown-menu,
|
||||
.dropdown-menu.is-visible {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: translateY(2px);
|
||||
}
|
||||
|
||||
/* Right-aligned variant */
|
||||
.dropdown-menu.is-right {
|
||||
left: auto;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
width: 100%;
|
||||
padding: var(--space-1) var(--space-3);
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
transition: background var(--transition-fast), color var(--transition-fast);
|
||||
border: none;
|
||||
background: none;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.dropdown-item:hover {
|
||||
background: var(--bg-hover);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.dropdown-item.is-danger {
|
||||
color: var(--error);
|
||||
}
|
||||
.dropdown-item.is-danger:hover {
|
||||
background: rgba(239, 83, 80, 0.10);
|
||||
}
|
||||
|
||||
.dropdown-item.is-disabled {
|
||||
opacity: 0.4;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.dropdown-item-icon {
|
||||
width: 16px;
|
||||
text-align: center;
|
||||
flex-shrink: 0;
|
||||
font-size: var(--text-md);
|
||||
}
|
||||
|
||||
.dropdown-item-shortcut {
|
||||
margin-left: auto;
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.dropdown-separator {
|
||||
height: 1px;
|
||||
margin: var(--space-1) 0;
|
||||
background: var(--border-subtle);
|
||||
}
|
||||
|
||||
.dropdown-header {
|
||||
padding: var(--space-1) var(--space-3);
|
||||
font-size: var(--text-xs);
|
||||
font-weight: var(--weight-semibold);
|
||||
color: var(--text-tertiary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
|
||||
/* =======================================================================
|
||||
NOTIFICATIONS (toast)
|
||||
======================================================================= */
|
||||
|
||||
.notifications-container {
|
||||
position: fixed;
|
||||
top: var(--space-4);
|
||||
right: var(--space-4);
|
||||
z-index: var(--z-notification);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.notification {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: var(--space-3);
|
||||
min-width: 280px;
|
||||
max-width: 400px;
|
||||
padding: var(--space-3) var(--space-4);
|
||||
background: var(--bg-overlay);
|
||||
border: 1px solid var(--border-default);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-xl);
|
||||
pointer-events: auto;
|
||||
animation: notification-in var(--transition-slow) ease forwards;
|
||||
}
|
||||
|
||||
.notification.is-leaving {
|
||||
animation: notification-out var(--transition-normal) ease forwards;
|
||||
}
|
||||
|
||||
@keyframes notification-in {
|
||||
from { opacity: 0; transform: translateX(20px); }
|
||||
to { opacity: 1; transform: translateX(0); }
|
||||
}
|
||||
@keyframes notification-out {
|
||||
from { opacity: 1; transform: translateX(0); }
|
||||
to { opacity: 0; transform: translateX(20px); }
|
||||
}
|
||||
|
||||
.notification-icon {
|
||||
flex-shrink: 0;
|
||||
font-size: var(--text-lg);
|
||||
line-height: 1;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.notification-body {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.notification-title {
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--weight-semibold);
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.notification-message {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-secondary);
|
||||
line-height: var(--leading-normal);
|
||||
}
|
||||
|
||||
.notification-close {
|
||||
flex-shrink: 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--text-tertiary);
|
||||
cursor: pointer;
|
||||
transition: background var(--transition-fast), color var(--transition-fast);
|
||||
}
|
||||
.notification-close:hover {
|
||||
background: var(--bg-hover);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* Colour variants (left border accent) */
|
||||
.notification-success { border-left: 3px solid var(--success); }
|
||||
.notification-success .notification-icon { color: var(--success); }
|
||||
|
||||
.notification-error { border-left: 3px solid var(--error); }
|
||||
.notification-error .notification-icon { color: var(--error); }
|
||||
|
||||
.notification-warning { border-left: 3px solid var(--warning); }
|
||||
.notification-warning .notification-icon { color: var(--warning); }
|
||||
|
||||
.notification-info { border-left: 3px solid var(--info); }
|
||||
.notification-info .notification-icon { color: var(--info); }
|
||||
|
||||
|
||||
/* =======================================================================
|
||||
DISCLOSURE / ACCORDION
|
||||
======================================================================= */
|
||||
|
||||
.disclosure {
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
}
|
||||
|
||||
.disclosure-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
width: 100%;
|
||||
padding: var(--space-3) var(--space-4);
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--weight-medium);
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
transition: background var(--transition-fast), color var(--transition-fast);
|
||||
user-select: none;
|
||||
text-align: left;
|
||||
background: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.disclosure-header:hover {
|
||||
background: var(--bg-hover);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.disclosure-chevron {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
flex-shrink: 0;
|
||||
transition: transform var(--transition-normal);
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.disclosure.is-open .disclosure-chevron {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.disclosure-content {
|
||||
overflow: hidden;
|
||||
max-height: 0;
|
||||
transition: max-height var(--transition-slow);
|
||||
}
|
||||
|
||||
.disclosure.is-open .disclosure-content {
|
||||
max-height: 2000px;
|
||||
}
|
||||
|
||||
.disclosure-content-inner {
|
||||
padding: 0 var(--space-4) var(--space-4);
|
||||
}
|
||||
|
||||
|
||||
/* =======================================================================
|
||||
SEARCH INPUT
|
||||
======================================================================= */
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
padding: 0 var(--space-3) 0 var(--space-8);
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-input);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: var(--radius-md);
|
||||
transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
|
||||
}
|
||||
|
||||
.search-input::placeholder {
|
||||
color: var(--text-disabled);
|
||||
}
|
||||
|
||||
.search-input:hover {
|
||||
border-color: var(--border-default);
|
||||
}
|
||||
|
||||
.search-input:focus {
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 2px var(--accent-muted);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Wrapper with icon */
|
||||
.search-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.search-wrapper::before {
|
||||
content: '⌕';
|
||||
position: absolute;
|
||||
left: var(--space-3);
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-tertiary);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
||||
/* =======================================================================
|
||||
EMPTY STATE
|
||||
======================================================================= */
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
padding: var(--space-8);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-state-icon {
|
||||
font-size: 48px;
|
||||
color: var(--text-tertiary);
|
||||
margin-bottom: var(--space-4);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.empty-state h2 {
|
||||
font-size: var(--text-lg);
|
||||
font-weight: var(--weight-semibold);
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.empty-state p {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-tertiary);
|
||||
max-width: 320px;
|
||||
line-height: var(--leading-relaxed);
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
|
||||
.empty-state-actions {
|
||||
display: flex;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
|
||||
/* =======================================================================
|
||||
TOOLTIP
|
||||
======================================================================= */
|
||||
|
||||
.tooltip {
|
||||
position: absolute;
|
||||
z-index: var(--z-tooltip);
|
||||
padding: var(--space-1) var(--space-2);
|
||||
font-size: var(--text-xs);
|
||||
font-weight: var(--weight-medium);
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-overlay);
|
||||
border: 1px solid var(--border-default);
|
||||
border-radius: var(--radius-sm);
|
||||
box-shadow: var(--shadow-md);
|
||||
white-space: nowrap;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
transition: opacity var(--transition-fast);
|
||||
}
|
||||
|
||||
.tooltip.is-visible {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
|
||||
/* =======================================================================
|
||||
DIVIDER
|
||||
======================================================================= */
|
||||
|
||||
.divider {
|
||||
height: 1px;
|
||||
background: var(--border-subtle);
|
||||
margin: var(--space-2) 0;
|
||||
}
|
||||
|
||||
.divider-vertical {
|
||||
width: 1px;
|
||||
align-self: stretch;
|
||||
background: var(--border-subtle);
|
||||
margin: 0 var(--space-2);
|
||||
}
|
||||
|
||||
|
||||
/* =======================================================================
|
||||
LOADING / SPINNER
|
||||
======================================================================= */
|
||||
|
||||
.spinner {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 2px solid var(--border-strong);
|
||||
border-top-color: var(--accent);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.6s linear infinite;
|
||||
}
|
||||
|
||||
.spinner-sm {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-width: 1.5px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
@@ -0,0 +1,439 @@
|
||||
/* ==========================================================================
|
||||
Nexus Terminal — Profile Editor & Form Polish
|
||||
Section headers, grouped fields, protocol-specific visibility,
|
||||
inline validation, tag input, jump host list, key file selector.
|
||||
========================================================================== */
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Form panel (profile editor container)
|
||||
----------------------------------------------------------------------- */
|
||||
.form-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.form-panel-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.form-panel-title {
|
||||
font-size: var(--text-md);
|
||||
font-weight: var(--weight-semibold);
|
||||
color: var(--text-primary);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.form-panel-body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: var(--space-4);
|
||||
}
|
||||
|
||||
.form-panel-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border-top: 1px solid var(--border-subtle);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Form sections (disclosure-based grouping)
|
||||
----------------------------------------------------------------------- */
|
||||
.form-section {
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: var(--radius-lg);
|
||||
margin-bottom: var(--space-4);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.form-section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
width: 100%;
|
||||
padding: var(--space-3) var(--space-4);
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--weight-semibold);
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-elevated);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
text-align: left;
|
||||
transition: background var(--transition-fast);
|
||||
}
|
||||
|
||||
.form-section-header:hover {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.form-section-chevron {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
color: var(--text-tertiary);
|
||||
transition: transform var(--transition-normal);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.form-section.is-open .form-section-chevron {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.form-section-icon {
|
||||
width: 16px;
|
||||
text-align: center;
|
||||
flex-shrink: 0;
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.form-section-body {
|
||||
padding: var(--space-4);
|
||||
display: none;
|
||||
}
|
||||
|
||||
.form-section.is-open .form-section-body {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Field grid (two-column layout for compact forms)
|
||||
----------------------------------------------------------------------- */
|
||||
.form-fields {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
/* Single-column override */
|
||||
.form-row.full-width {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
/* Three-column for port + protocol combos */
|
||||
.form-row.triple {
|
||||
grid-template-columns: 1fr 100px 120px;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Protocol-specific field visibility
|
||||
Fields are hidden unless their protocol class is active.
|
||||
----------------------------------------------------------------------- */
|
||||
.proto-only-ssh,
|
||||
.proto-only-telnet,
|
||||
.proto-only-raw {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.protocol-ssh .proto-only-ssh,
|
||||
.protocol-telnet .proto-only-telnet,
|
||||
.protocol-raw .proto-only-raw {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* For block-level protocol fields */
|
||||
.protocol-ssh .proto-only-ssh.form-section,
|
||||
.protocol-telnet .proto-only-telnet.form-section,
|
||||
.protocol-raw .proto-only-raw.form-section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Tag input
|
||||
----------------------------------------------------------------------- */
|
||||
.tag-input-wrapper {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
min-height: 30px;
|
||||
padding: var(--space-1) var(--space-2);
|
||||
background: var(--bg-input);
|
||||
border: 1px solid var(--border-default);
|
||||
border-radius: var(--radius-md);
|
||||
cursor: text;
|
||||
transition: border-color var(--transition-fast);
|
||||
}
|
||||
|
||||
.tag-input-wrapper:hover {
|
||||
border-color: var(--border-strong);
|
||||
}
|
||||
|
||||
.tag-input-wrapper:focus-within {
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 2px var(--accent-muted);
|
||||
}
|
||||
|
||||
.tag-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
height: 20px;
|
||||
padding: 0 var(--space-2);
|
||||
font-size: var(--text-xs);
|
||||
color: var(--accent);
|
||||
background: var(--accent-muted);
|
||||
border-radius: var(--radius-sm);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tag-item-remove {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
font-size: 10px;
|
||||
cursor: pointer;
|
||||
border-radius: 50%;
|
||||
color: var(--accent);
|
||||
transition: background var(--transition-fast);
|
||||
}
|
||||
|
||||
.tag-item-remove:hover {
|
||||
background: var(--accent-strong);
|
||||
}
|
||||
|
||||
.tag-input {
|
||||
flex: 1;
|
||||
min-width: 60px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-primary);
|
||||
outline: none;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
.tag-input::placeholder {
|
||||
color: var(--text-disabled);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Jump host / proxy list
|
||||
----------------------------------------------------------------------- */
|
||||
.jump-host-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.jump-host-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
background: var(--bg-input);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: var(--radius-md);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
.jump-host-item .jump-host-order {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-disabled);
|
||||
min-width: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.jump-host-item .jump-host-value {
|
||||
flex: 1;
|
||||
font-family: var(--font-mono);
|
||||
color: var(--text-secondary);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.jump-host-item .jump-host-actions {
|
||||
display: flex;
|
||||
gap: var(--space-1);
|
||||
opacity: 0;
|
||||
transition: opacity var(--transition-fast);
|
||||
}
|
||||
|
||||
.jump-host-item:hover .jump-host-actions {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.jump-host-add {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-2);
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-tertiary);
|
||||
border: 1px dashed var(--border-default);
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
transition: border-color var(--transition-fast), color var(--transition-fast);
|
||||
}
|
||||
|
||||
.jump-host-add:hover {
|
||||
border-color: var(--accent);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Key file selector row
|
||||
----------------------------------------------------------------------- */
|
||||
.file-selector {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.file-selector .form-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.file-selector .btn {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* File path display */
|
||||
.file-path {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-tertiary);
|
||||
background: var(--bg-input);
|
||||
padding: var(--space-1) var(--space-2);
|
||||
border-radius: var(--radius-sm);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Inline validation
|
||||
----------------------------------------------------------------------- */
|
||||
.form-group.is-valid .form-input {
|
||||
border-color: var(--success);
|
||||
}
|
||||
|
||||
.form-group.is-valid .form-input:focus {
|
||||
box-shadow: 0 0 0 2px rgba(102, 187, 106, 0.20);
|
||||
}
|
||||
|
||||
.form-group.is-invalid .form-input {
|
||||
border-color: var(--error);
|
||||
}
|
||||
|
||||
.form-group.is-invalid .form-input:focus {
|
||||
box-shadow: 0 0 0 2px rgba(239, 83, 80, 0.20);
|
||||
}
|
||||
|
||||
.form-validation-icon {
|
||||
position: absolute;
|
||||
right: var(--space-3);
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
.form-group.is-valid .form-validation-icon { color: var(--success); }
|
||||
.form-group.is-invalid .form-validation-icon { color: var(--error); }
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Help text under sections
|
||||
----------------------------------------------------------------------- */
|
||||
.form-section-help {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-tertiary);
|
||||
padding: 0 var(--space-4) var(--space-3);
|
||||
line-height: var(--leading-relaxed);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Colour preview (for terminal colour settings)
|
||||
----------------------------------------------------------------------- */
|
||||
.colour-preview {
|
||||
display: inline-block;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: var(--radius-sm);
|
||||
border: 1px solid var(--border-default);
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Number input with stepper
|
||||
----------------------------------------------------------------------- */
|
||||
.form-number {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.form-number .form-input {
|
||||
border-radius: var(--radius-md) 0 0 var(--radius-md);
|
||||
text-align: center;
|
||||
width: 60px;
|
||||
/* Hide browser spinners */
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
.form-number .form-input::-webkit-inner-spin-button,
|
||||
.form-number .form-input::-webkit-outer-spin-button {
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
.form-number-btn {
|
||||
width: 24px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-secondary);
|
||||
background: var(--bg-input);
|
||||
border: 1px solid var(--border-default);
|
||||
border-left: none;
|
||||
cursor: pointer;
|
||||
transition: background var(--transition-fast), color var(--transition-fast);
|
||||
}
|
||||
|
||||
.form-number-btn:last-child {
|
||||
border-radius: 0 var(--radius-md) var(--radius-md) 0;
|
||||
}
|
||||
|
||||
.form-number-btn:hover {
|
||||
background: var(--bg-hover);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Form action row (test connection, etc.)
|
||||
----------------------------------------------------------------------- */
|
||||
.form-actions-inline {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
padding-top: var(--space-3);
|
||||
border-top: 1px solid var(--border-subtle);
|
||||
margin-top: var(--space-3);
|
||||
}
|
||||
|
||||
.form-actions-inline .btn {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.form-actions-inline .form-actions-status {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-tertiary);
|
||||
flex: 1;
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/* ==========================================================================
|
||||
Nexus Terminal — Application Layout
|
||||
Grid shell, sidebar, tab bar, workspace, and status bar structure.
|
||||
========================================================================== */
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Main application grid
|
||||
----------------------------------------------------------------------- */
|
||||
.app {
|
||||
display: grid;
|
||||
grid-template-columns: var(--sidebar-width) 1fr;
|
||||
grid-template-rows: var(--tab-bar-height) 1fr var(--status-bar-height);
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
background: var(--bg-base);
|
||||
}
|
||||
|
||||
/* Collapsed sidebar variant */
|
||||
.app.sidebar-collapsed {
|
||||
grid-template-columns: var(--sidebar-width-collapsed) 1fr;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Sidebar
|
||||
----------------------------------------------------------------------- */
|
||||
.sidebar {
|
||||
grid-row: 1 / -1;
|
||||
grid-column: 1;
|
||||
background: var(--bg-surface);
|
||||
border-right: 1px solid var(--border-subtle);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
z-index: var(--z-sticky);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Tab bar
|
||||
----------------------------------------------------------------------- */
|
||||
.tab-bar {
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
background: var(--bg-surface);
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: var(--tab-bar-height);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tab-list {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
.tab-list::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Workspace
|
||||
----------------------------------------------------------------------- */
|
||||
.workspace {
|
||||
grid-column: 2;
|
||||
grid-row: 2;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: var(--bg-base);
|
||||
}
|
||||
|
||||
/* Terminal container — fills workspace */
|
||||
.terminal-container {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
padding: var(--space-1);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Status bar
|
||||
----------------------------------------------------------------------- */
|
||||
.status-bar {
|
||||
grid-column: 2;
|
||||
grid-row: 3;
|
||||
background: var(--bg-surface);
|
||||
border-top: 1px solid var(--border-subtle);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 var(--space-3);
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-tertiary);
|
||||
gap: var(--space-4);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.status-bar span {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
}
|
||||
|
||||
.status-spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Status bar indicator dots */
|
||||
.status-bar .status-dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
.status-dot.is-connected { background: var(--success); }
|
||||
.status-dot.is-connecting { background: var(--warning); }
|
||||
.status-dot.is-error { background: var(--error); }
|
||||
.status-dot.is-disconnected { background: var(--text-tertiary); }
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Resize handle (between sidebar and workspace)
|
||||
----------------------------------------------------------------------- */
|
||||
.resize-handle {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: -3px;
|
||||
width: 6px;
|
||||
height: 100%;
|
||||
cursor: col-resize;
|
||||
z-index: var(--z-sticky);
|
||||
}
|
||||
.resize-handle:hover,
|
||||
.resize-handle.is-dragging {
|
||||
background: var(--accent-muted);
|
||||
}
|
||||
@@ -0,0 +1,490 @@
|
||||
/* ==========================================================================
|
||||
Nexus Terminal — Modals & Drawers
|
||||
Profile editor drawer, settings modal, known hosts viewer,
|
||||
confirmation dialogs, host key verification dialog.
|
||||
========================================================================== */
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Modal backdrop
|
||||
----------------------------------------------------------------------- */
|
||||
.modal-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: var(--z-modal-backdrop);
|
||||
background: rgba(10, 10, 20, 0.60);
|
||||
backdrop-filter: blur(2px);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition:
|
||||
opacity var(--transition-normal),
|
||||
visibility var(--transition-normal);
|
||||
}
|
||||
|
||||
.modal-backdrop.is-visible {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Modal (centred panel)
|
||||
----------------------------------------------------------------------- */
|
||||
.modal {
|
||||
position: fixed;
|
||||
z-index: var(--z-modal);
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%) scale(0.96);
|
||||
width: 480px;
|
||||
max-width: calc(100vw - var(--space-8));
|
||||
max-height: calc(100vh - var(--space-8));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--bg-elevated);
|
||||
border: 1px solid var(--border-default);
|
||||
border-radius: var(--radius-xl);
|
||||
box-shadow: var(--shadow-xl);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition:
|
||||
opacity var(--transition-normal),
|
||||
visibility var(--transition-normal),
|
||||
transform var(--transition-normal);
|
||||
}
|
||||
|
||||
.modal.is-visible {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: translate(-50%, -50%) scale(1);
|
||||
}
|
||||
|
||||
/* Size variants */
|
||||
.modal-sm { width: 360px; }
|
||||
.modal-lg { width: 640px; }
|
||||
.modal-xl { width: 800px; }
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-4) var(--space-5);
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.modal-header-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: var(--text-lg);
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
flex: 1;
|
||||
font-size: var(--text-md);
|
||||
font-weight: var(--weight-semibold);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--radius-md);
|
||||
font-size: var(--text-md);
|
||||
color: var(--text-tertiary);
|
||||
cursor: pointer;
|
||||
transition: background var(--transition-fast), color var(--transition-fast);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.modal-close:hover {
|
||||
background: var(--bg-hover);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: var(--space-5);
|
||||
}
|
||||
|
||||
.modal-body p {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-secondary);
|
||||
line-height: var(--leading-relaxed);
|
||||
}
|
||||
|
||||
.modal-body p + p {
|
||||
margin-top: var(--space-3);
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-3) var(--space-5);
|
||||
border-top: 1px solid var(--border-subtle);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.modal-footer-left {
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Drawer (slides in from the right)
|
||||
Used for the profile editor.
|
||||
----------------------------------------------------------------------- */
|
||||
.drawer-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: var(--z-modal-backdrop);
|
||||
background: rgba(10, 10, 20, 0.50);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition:
|
||||
opacity var(--transition-normal),
|
||||
visibility var(--transition-normal);
|
||||
}
|
||||
|
||||
.drawer-backdrop.is-visible {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.drawer {
|
||||
position: fixed;
|
||||
z-index: var(--z-modal);
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 480px;
|
||||
max-width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--bg-elevated);
|
||||
border-left: 1px solid var(--border-default);
|
||||
box-shadow: var(--shadow-xl);
|
||||
transform: translateX(100%);
|
||||
transition: transform var(--transition-slow);
|
||||
}
|
||||
|
||||
.drawer.is-visible {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
/* Drawer width variants */
|
||||
.drawer-wide { width: 560px; }
|
||||
.drawer-narrow { width: 380px; }
|
||||
|
||||
.drawer-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-4) var(--space-5);
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.drawer-title {
|
||||
flex: 1;
|
||||
font-size: var(--text-md);
|
||||
font-weight: var(--weight-semibold);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.drawer-close {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--radius-md);
|
||||
font-size: var(--text-md);
|
||||
color: var(--text-tertiary);
|
||||
cursor: pointer;
|
||||
transition: background var(--transition-fast), color var(--transition-fast);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.drawer-close:hover {
|
||||
background: var(--bg-hover);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.drawer-body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: var(--space-5);
|
||||
}
|
||||
|
||||
.drawer-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-3) var(--space-5);
|
||||
border-top: 1px solid var(--border-subtle);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Confirmation dialog
|
||||
----------------------------------------------------------------------- */
|
||||
.confirm-dialog {
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.confirm-dialog .modal-body {
|
||||
text-align: center;
|
||||
padding: var(--space-6) var(--space-5);
|
||||
}
|
||||
|
||||
.confirm-dialog-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
margin: 0 auto var(--space-4);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
font-size: var(--text-2xl);
|
||||
}
|
||||
|
||||
.confirm-dialog-icon.is-warning {
|
||||
background: rgba(255, 167, 38, 0.12);
|
||||
color: var(--warning);
|
||||
}
|
||||
|
||||
.confirm-dialog-icon.is-danger {
|
||||
background: rgba(239, 83, 80, 0.12);
|
||||
color: var(--error);
|
||||
}
|
||||
|
||||
.confirm-dialog-icon.is-info {
|
||||
background: rgba(66, 165, 245, 0.12);
|
||||
color: var(--info);
|
||||
}
|
||||
|
||||
.confirm-dialog-message {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-secondary);
|
||||
line-height: var(--leading-relaxed);
|
||||
max-width: 300px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Host key verification dialog
|
||||
----------------------------------------------------------------------- */
|
||||
.hostkey-dialog {
|
||||
width: 520px;
|
||||
}
|
||||
|
||||
.hostkey-fingerprint {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-input);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--border-subtle);
|
||||
word-break: break-all;
|
||||
line-height: var(--leading-relaxed);
|
||||
margin: var(--space-3) 0;
|
||||
}
|
||||
|
||||
.hostkey-fingerprint-label {
|
||||
font-size: var(--text-xs);
|
||||
font-weight: var(--weight-semibold);
|
||||
color: var(--text-tertiary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
margin-bottom: var(--space-1);
|
||||
}
|
||||
|
||||
.hostkey-mismatch-warning {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
background: rgba(239, 83, 80, 0.08);
|
||||
border: 1px solid rgba(239, 83, 80, 0.20);
|
||||
border-radius: var(--radius-md);
|
||||
margin: var(--space-3) 0;
|
||||
}
|
||||
|
||||
.hostkey-mismatch-warning .warning-icon {
|
||||
font-size: var(--text-lg);
|
||||
color: var(--error);
|
||||
flex-shrink: 0;
|
||||
line-height: 1;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.hostkey-mismatch-warning p {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-secondary);
|
||||
line-height: var(--leading-relaxed);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Settings modal
|
||||
----------------------------------------------------------------------- */
|
||||
.settings-modal {
|
||||
width: 640px;
|
||||
max-height: 80vh;
|
||||
}
|
||||
|
||||
.settings-nav {
|
||||
display: flex;
|
||||
gap: 1px;
|
||||
background: var(--border-subtle);
|
||||
border-radius: var(--radius-md);
|
||||
overflow: hidden;
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.settings-nav-item {
|
||||
flex: 1;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--weight-medium);
|
||||
color: var(--text-tertiary);
|
||||
text-align: center;
|
||||
background: var(--bg-surface);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: background var(--transition-fast), color var(--transition-fast);
|
||||
}
|
||||
|
||||
.settings-nav-item:hover {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.settings-nav-item.active {
|
||||
background: var(--bg-active);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.settings-section {
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
|
||||
.settings-section-title {
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--weight-semibold);
|
||||
color: var(--text-primary);
|
||||
margin-bottom: var(--space-3);
|
||||
padding-bottom: var(--space-2);
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Known hosts viewer
|
||||
----------------------------------------------------------------------- */
|
||||
.known-hosts-modal {
|
||||
width: 700px;
|
||||
}
|
||||
|
||||
.known-hosts-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1px;
|
||||
}
|
||||
|
||||
.known-hosts-item {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 140px 60px;
|
||||
gap: var(--space-3);
|
||||
align-items: center;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
font-size: var(--text-sm);
|
||||
background: var(--bg-surface);
|
||||
border-radius: var(--radius-sm);
|
||||
transition: background var(--transition-fast);
|
||||
}
|
||||
|
||||
.known-hosts-item:hover {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.known-hosts-hostname {
|
||||
font-family: var(--font-mono);
|
||||
font-weight: var(--weight-medium);
|
||||
color: var(--text-primary);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.known-hosts-type {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.known-hosts-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.known-hosts-empty {
|
||||
padding: var(--space-8);
|
||||
text-align: center;
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Quick connect popover
|
||||
----------------------------------------------------------------------- */
|
||||
.quick-connect {
|
||||
width: 360px;
|
||||
}
|
||||
|
||||
.quick-connect .modal-body {
|
||||
padding: var(--space-4);
|
||||
}
|
||||
|
||||
.quick-connect-input {
|
||||
width: 100%;
|
||||
height: 36px;
|
||||
padding: 0 var(--space-3);
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-input);
|
||||
border: 1px solid var(--border-default);
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.quick-connect-input::placeholder {
|
||||
color: var(--text-disabled);
|
||||
font-family: var(--font-sans);
|
||||
}
|
||||
|
||||
.quick-connect-input:focus {
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 2px var(--accent-muted);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.quick-connect-help {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-tertiary);
|
||||
margin-top: var(--space-2);
|
||||
line-height: var(--leading-relaxed);
|
||||
}
|
||||
|
||||
.quick-connect-help code {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-secondary);
|
||||
background: var(--bg-overlay);
|
||||
padding: 1px 4px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
/* ==========================================================================
|
||||
Nexus Terminal — Sidebar
|
||||
Connection manager: logo, search, sections, profile list, footer.
|
||||
========================================================================== */
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Sidebar header (logo / title / drag region)
|
||||
----------------------------------------------------------------------- */
|
||||
.sidebar-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: var(--space-3) var(--space-4);
|
||||
min-height: var(--tab-bar-height);
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sidebar-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.sidebar-logo svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: var(--accent);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sidebar-title {
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--weight-semibold);
|
||||
color: var(--text-primary);
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Sidebar search
|
||||
----------------------------------------------------------------------- */
|
||||
.sidebar-search {
|
||||
padding: var(--space-2) var(--space-3);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Sidebar navigation (scrollable area)
|
||||
----------------------------------------------------------------------- */
|
||||
.sidebar-nav {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Sections (Favourites, Recent, All Connections)
|
||||
----------------------------------------------------------------------- */
|
||||
.sidebar-section {
|
||||
/* Each section can be collapsed */
|
||||
}
|
||||
|
||||
.sidebar-section + .sidebar-section {
|
||||
border-top: 1px solid var(--border-subtle);
|
||||
}
|
||||
|
||||
.sidebar-section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
width: 100%;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
font-size: var(--text-xs);
|
||||
font-weight: var(--weight-semibold);
|
||||
color: var(--text-tertiary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
cursor: pointer;
|
||||
transition: color var(--transition-fast), background var(--transition-fast);
|
||||
user-select: none;
|
||||
text-align: left;
|
||||
background: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.sidebar-section-header:hover {
|
||||
color: var(--text-secondary);
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.sidebar-section-header .section-icon {
|
||||
width: 14px;
|
||||
text-align: center;
|
||||
flex-shrink: 0;
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
.sidebar-section-header .section-chevron {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
margin-right: var(--space-1);
|
||||
flex-shrink: 0;
|
||||
transition: transform var(--transition-normal);
|
||||
color: var(--text-disabled);
|
||||
}
|
||||
|
||||
.sidebar-section.is-collapsed .section-chevron {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.section-count {
|
||||
margin-left: auto;
|
||||
font-size: 10px;
|
||||
font-weight: var(--weight-normal);
|
||||
color: var(--text-disabled);
|
||||
background: var(--bg-overlay);
|
||||
padding: 0 var(--space-1);
|
||||
border-radius: var(--radius-sm);
|
||||
min-width: 18px;
|
||||
text-align: center;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Sidebar list (container for items)
|
||||
----------------------------------------------------------------------- */
|
||||
.sidebar-list {
|
||||
padding: 0 var(--space-2);
|
||||
}
|
||||
|
||||
.sidebar-section.is-collapsed .sidebar-list {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Sidebar item (individual connection profile)
|
||||
----------------------------------------------------------------------- */
|
||||
.sidebar-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-2) var(--space-2);
|
||||
margin-bottom: 1px;
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background var(--transition-fast),
|
||||
box-shadow var(--transition-fast);
|
||||
user-select: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sidebar-item:hover {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.sidebar-item:active {
|
||||
background: var(--bg-active);
|
||||
}
|
||||
|
||||
/* Active / selected state */
|
||||
.sidebar-item.active {
|
||||
background: var(--accent-muted);
|
||||
}
|
||||
.sidebar-item.active::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 4px;
|
||||
bottom: 4px;
|
||||
width: 2px;
|
||||
border-radius: var(--radius-full);
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
/* Protocol colour indicator dot */
|
||||
.sidebar-item .protocol-indicator {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
background: var(--text-tertiary);
|
||||
}
|
||||
.sidebar-item .protocol-indicator.proto-ssh { background: var(--protocol-ssh); }
|
||||
.sidebar-item .protocol-indicator.proto-telnet { background: var(--protocol-telnet); }
|
||||
.sidebar-item .protocol-indicator.proto-raw { background: var(--protocol-raw); }
|
||||
|
||||
/* Text block (name + host) */
|
||||
.sidebar-item-text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.sidebar-item-name {
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--weight-medium);
|
||||
color: var(--text-primary);
|
||||
line-height: var(--leading-tight);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sidebar-item-host {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-tertiary);
|
||||
font-family: var(--font-mono);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
line-height: var(--leading-tight);
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
/* Favourite star */
|
||||
.sidebar-item-star {
|
||||
flex-shrink: 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 11px;
|
||||
color: var(--text-disabled);
|
||||
border-radius: var(--radius-sm);
|
||||
opacity: 0;
|
||||
transition: opacity var(--transition-fast), color var(--transition-fast);
|
||||
}
|
||||
|
||||
.sidebar-item:hover .sidebar-item-star {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.sidebar-item-star.is-favourite {
|
||||
opacity: 1;
|
||||
color: var(--warning);
|
||||
}
|
||||
|
||||
.sidebar-item-star:hover {
|
||||
color: var(--warning);
|
||||
}
|
||||
|
||||
/* Inline status dot (right side, tiny) */
|
||||
.sidebar-item-status {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
margin-right: var(--space-1);
|
||||
}
|
||||
.sidebar-item-status.is-connected { background: var(--success); }
|
||||
.sidebar-item-status.is-connecting { background: var(--warning); animation: pulse-dot 1.2s ease infinite; }
|
||||
.sidebar-item-status.is-error { background: var(--error); }
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Sidebar empty list placeholder
|
||||
----------------------------------------------------------------------- */
|
||||
.sidebar-list-empty {
|
||||
padding: var(--space-3) var(--space-2);
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-disabled);
|
||||
text-align: center;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Sidebar footer (actions)
|
||||
----------------------------------------------------------------------- */
|
||||
.sidebar-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border-top: 1px solid var(--border-subtle);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sidebar-footer .btn:first-child {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Sidebar collapsed state
|
||||
----------------------------------------------------------------------- */
|
||||
.sidebar-collapsed .sidebar-header {
|
||||
justify-content: center;
|
||||
padding: var(--space-3) var(--space-2);
|
||||
}
|
||||
.sidebar-collapsed .sidebar-title,
|
||||
.sidebar-collapsed .sidebar-search,
|
||||
.sidebar-collapsed .sidebar-section-header span:not(.section-icon),
|
||||
.sidebar-collapsed .sidebar-item-text,
|
||||
.sidebar-collapsed .sidebar-item-star,
|
||||
.sidebar-collapsed .section-count,
|
||||
.sidebar-collapsed .sidebar-footer .btn:first-child span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidebar-collapsed .sidebar-item {
|
||||
justify-content: center;
|
||||
padding: var(--space-2);
|
||||
}
|
||||
|
||||
.sidebar-collapsed .sidebar-section-header {
|
||||
justify-content: center;
|
||||
padding: var(--space-2);
|
||||
}
|
||||
|
||||
.sidebar-collapsed .sidebar-footer {
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
/* ==========================================================================
|
||||
Nexus Terminal — Tab Bar & Tabs
|
||||
Compact session tabs with status indicators, protocol badges, and
|
||||
close / pin controls.
|
||||
========================================================================== */
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Individual tab
|
||||
----------------------------------------------------------------------- */
|
||||
.tab {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
height: var(--tab-bar-height);
|
||||
padding: 0 var(--space-3);
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
border-right: 1px solid var(--border-subtle);
|
||||
transition:
|
||||
background var(--transition-fast),
|
||||
color var(--transition-fast);
|
||||
user-select: none;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.tab:hover {
|
||||
background: var(--bg-hover);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* Active tab */
|
||||
.tab.active {
|
||||
background: var(--bg-base);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* Active indicator — accent bar at the bottom */
|
||||
.tab.active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
background: var(--accent);
|
||||
border-radius: var(--radius-full) var(--radius-full) 0 0;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Tab icon (small terminal/protocol icon)
|
||||
----------------------------------------------------------------------- */
|
||||
.tab-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 12px;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.tab.active .tab-icon {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Tab label
|
||||
----------------------------------------------------------------------- */
|
||||
.tab-label {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Protocol mini-badge inside tab
|
||||
----------------------------------------------------------------------- */
|
||||
.tab-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 14px;
|
||||
padding: 0 3px;
|
||||
font-size: 9px;
|
||||
font-weight: var(--weight-semibold);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
border-radius: 2px;
|
||||
flex-shrink: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.tab-badge.badge-ssh {
|
||||
background: rgba(78, 205, 196, 0.15);
|
||||
color: var(--protocol-ssh);
|
||||
}
|
||||
.tab-badge.badge-telnet {
|
||||
background: rgba(255, 167, 38, 0.15);
|
||||
color: var(--protocol-telnet);
|
||||
}
|
||||
.tab-badge.badge-raw {
|
||||
background: rgba(171, 71, 188, 0.15);
|
||||
color: var(--protocol-raw);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Status dot (connection state)
|
||||
----------------------------------------------------------------------- */
|
||||
.tab-status {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
background: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.tab-status.is-connected { background: var(--success); }
|
||||
.tab-status.is-connecting { background: var(--warning); animation: pulse-dot 1.2s ease infinite; }
|
||||
.tab-status.is-error { background: var(--error); }
|
||||
.tab-status.is-disconnected { background: var(--text-tertiary); }
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Close button — revealed on hover
|
||||
----------------------------------------------------------------------- */
|
||||
.tab-close {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 11px;
|
||||
color: var(--text-tertiary);
|
||||
flex-shrink: 0;
|
||||
opacity: 0;
|
||||
transition:
|
||||
opacity var(--transition-fast),
|
||||
background var(--transition-fast),
|
||||
color var(--transition-fast);
|
||||
cursor: pointer;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.tab:hover .tab-close,
|
||||
.tab.active .tab-close {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tab-close:hover {
|
||||
background: var(--bg-active);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Pin indicator
|
||||
----------------------------------------------------------------------- */
|
||||
.tab-pin {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 9px;
|
||||
color: var(--text-disabled);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tab.is-pinned .tab-pin {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
/* Pinned tabs are always narrow */
|
||||
.tab.is-pinned {
|
||||
max-width: 100px;
|
||||
padding: 0 var(--space-2);
|
||||
}
|
||||
|
||||
.tab.is-pinned .tab-label {
|
||||
/* Hide label on pinned tabs to save space */
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab.is-pinned .tab-close {
|
||||
/* Pinned tabs don't show close by default */
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Drag handle area (for tab reordering)
|
||||
----------------------------------------------------------------------- */
|
||||
.tab-drag-handle {
|
||||
cursor: grab;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tab-drag-handle:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
/* Drag ghost styling */
|
||||
.tab.is-dragging {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.tab.is-drag-over-left::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: -1px;
|
||||
top: 4px;
|
||||
bottom: 4px;
|
||||
width: 2px;
|
||||
background: var(--accent);
|
||||
border-radius: var(--radius-full);
|
||||
}
|
||||
|
||||
.tab.is-drag-over-right::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: -1px;
|
||||
top: 4px;
|
||||
bottom: 4px;
|
||||
width: 2px;
|
||||
background: var(--accent);
|
||||
border-radius: var(--radius-full);
|
||||
/* Note: this re-uses ::after — active tab indicator is overridden while dragging */
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
New-tab button (+)
|
||||
----------------------------------------------------------------------- */
|
||||
.tab-new {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: var(--tab-bar-height);
|
||||
height: var(--tab-bar-height);
|
||||
font-size: var(--text-lg);
|
||||
color: var(--text-tertiary);
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
transition: background var(--transition-fast), color var(--transition-fast);
|
||||
border: none;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.tab-new:hover {
|
||||
background: var(--bg-hover);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.tab-new:active {
|
||||
background: var(--bg-active);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Tab overflow indicators (chevrons at edges)
|
||||
----------------------------------------------------------------------- */
|
||||
.tab-bar-overflow-left,
|
||||
.tab-bar-overflow-right {
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 20px;
|
||||
flex-shrink: 0;
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-tertiary);
|
||||
cursor: pointer;
|
||||
background: linear-gradient(90deg, var(--bg-surface), transparent);
|
||||
}
|
||||
|
||||
.tab-bar-overflow-right {
|
||||
background: linear-gradient(-90deg, var(--bg-surface), transparent);
|
||||
}
|
||||
|
||||
.tab-bar.has-overflow-left .tab-bar-overflow-left,
|
||||
.tab-bar.has-overflow-right .tab-bar-overflow-right {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Tab context menu trigger (on right-click)
|
||||
----------------------------------------------------------------------- */
|
||||
.tab-context-area {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 1;
|
||||
/* Transparent hit area for right-click context menu */
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Modified indicator (unsaved changes)
|
||||
----------------------------------------------------------------------- */
|
||||
.tab.is-modified .tab-close::before {
|
||||
content: '●';
|
||||
font-size: 8px;
|
||||
color: var(--accent);
|
||||
}
|
||||
@@ -0,0 +1,392 @@
|
||||
/* ==========================================================================
|
||||
Nexus Terminal — Terminal Integration
|
||||
xterm.js container, header info strip, RAW mode views, and send bar.
|
||||
========================================================================== */
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Terminal wrapper — fills the workspace panel
|
||||
----------------------------------------------------------------------- */
|
||||
.terminal-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Terminal header / info strip (optional)
|
||||
Shows connection info above the terminal.
|
||||
----------------------------------------------------------------------- */
|
||||
.terminal-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-1) var(--space-3);
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-tertiary);
|
||||
background: var(--bg-surface);
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
flex-shrink: 0;
|
||||
min-height: 24px;
|
||||
}
|
||||
|
||||
.terminal-header-label {
|
||||
font-weight: var(--weight-medium);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.terminal-header-value {
|
||||
font-family: var(--font-mono);
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.terminal-header-separator {
|
||||
width: 1px;
|
||||
height: 10px;
|
||||
background: var(--border-subtle);
|
||||
}
|
||||
|
||||
.terminal-header-spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.terminal-header-actions {
|
||||
display: flex;
|
||||
gap: var(--space-1);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
xterm.js container
|
||||
----------------------------------------------------------------------- */
|
||||
.terminal-container {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
/* Slight padding so terminal text doesn't hug edges */
|
||||
padding: var(--space-1);
|
||||
}
|
||||
|
||||
/* xterm.js overrides for consistent look */
|
||||
.terminal-container .xterm {
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.terminal-container .xterm-viewport {
|
||||
/* Allow xterm's own scrollbar to work */
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
|
||||
.terminal-container .xterm-viewport::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
.terminal-container .xterm-viewport::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
.terminal-container .xterm-viewport::-webkit-scrollbar-thumb {
|
||||
background: var(--border-strong);
|
||||
border-radius: var(--radius-full);
|
||||
}
|
||||
.terminal-container .xterm-viewport::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--text-tertiary);
|
||||
}
|
||||
|
||||
/* Ensure cursor blink is visible */
|
||||
.terminal-container .xterm-cursor-layer {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* Selection styling within terminal */
|
||||
.terminal-container .xterm .xterm-selection div {
|
||||
background: var(--accent-strong) !important;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Terminal search overlay
|
||||
----------------------------------------------------------------------- */
|
||||
.terminal-search {
|
||||
position: absolute;
|
||||
top: var(--space-2);
|
||||
right: var(--space-4);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
padding: var(--space-1);
|
||||
background: var(--bg-overlay);
|
||||
border: 1px solid var(--border-default);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-md);
|
||||
z-index: 10;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transform: translateY(-8px);
|
||||
transition:
|
||||
opacity var(--transition-fast),
|
||||
visibility var(--transition-fast),
|
||||
transform var(--transition-fast);
|
||||
}
|
||||
|
||||
.terminal-search.is-visible {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.terminal-search input {
|
||||
width: 200px;
|
||||
height: 24px;
|
||||
padding: 0 var(--space-2);
|
||||
font-size: var(--text-sm);
|
||||
font-family: var(--font-mono);
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-input);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.terminal-search input:focus {
|
||||
border-color: var(--accent);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.terminal-search-count {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-tertiary);
|
||||
padding: 0 var(--space-1);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
RAW mode containers
|
||||
----------------------------------------------------------------------- */
|
||||
|
||||
/* RAW text view — scrollable, monospace output */
|
||||
.raw-text-view {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: var(--space-3);
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-sm);
|
||||
line-height: var(--leading-relaxed);
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-base);
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* RAW hex view */
|
||||
.raw-hex-view {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: var(--space-3);
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-xs);
|
||||
line-height: var(--leading-relaxed);
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-base);
|
||||
}
|
||||
|
||||
.raw-hex-row {
|
||||
display: flex;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.raw-hex-offset {
|
||||
color: var(--text-tertiary);
|
||||
min-width: 60px;
|
||||
}
|
||||
|
||||
.raw-hex-bytes {
|
||||
min-width: 350px;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
|
||||
.raw-hex-bytes .byte-highlight {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.raw-hex-ascii {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.raw-hex-ascii .non-printable {
|
||||
color: var(--text-disabled);
|
||||
}
|
||||
|
||||
/* RAW split view (text on left, hex on right) */
|
||||
.raw-split-view {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.raw-split-view .raw-text-view,
|
||||
.raw-split-view .raw-hex-view {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.raw-split-divider {
|
||||
width: 1px;
|
||||
background: var(--border-subtle);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
RAW mode view toggle buttons
|
||||
----------------------------------------------------------------------- */
|
||||
.raw-view-toggle {
|
||||
display: flex;
|
||||
gap: 1px;
|
||||
background: var(--border-subtle);
|
||||
border-radius: var(--radius-md);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.raw-view-toggle-btn {
|
||||
padding: 0 var(--space-2);
|
||||
height: 22px;
|
||||
font-size: var(--text-xs);
|
||||
font-weight: var(--weight-medium);
|
||||
color: var(--text-tertiary);
|
||||
background: var(--bg-surface);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: background var(--transition-fast), color var(--transition-fast);
|
||||
}
|
||||
|
||||
.raw-view-toggle-btn:hover {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.raw-view-toggle-btn.active {
|
||||
background: var(--bg-active);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Send bar (RAW TCP mode — manual data entry)
|
||||
----------------------------------------------------------------------- */
|
||||
.send-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
background: var(--bg-surface);
|
||||
border-top: 1px solid var(--border-subtle);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.send-bar-input {
|
||||
flex: 1;
|
||||
height: 28px;
|
||||
padding: 0 var(--space-3);
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-input);
|
||||
border: 1px solid var(--border-default);
|
||||
border-radius: var(--radius-md);
|
||||
transition: border-color var(--transition-fast);
|
||||
}
|
||||
|
||||
.send-bar-input:focus {
|
||||
border-color: var(--accent);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.send-bar-input::placeholder {
|
||||
color: var(--text-disabled);
|
||||
}
|
||||
|
||||
/* Line ending selector */
|
||||
.send-bar-eol {
|
||||
height: 28px;
|
||||
padding: 0 var(--space-2);
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-secondary);
|
||||
background: var(--bg-input);
|
||||
border: 1px solid var(--border-default);
|
||||
border-radius: var(--radius-md);
|
||||
appearance: none;
|
||||
cursor: pointer;
|
||||
min-width: 60px;
|
||||
}
|
||||
|
||||
.send-bar-eol:focus {
|
||||
border-color: var(--accent);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Encoding selector */
|
||||
.send-bar-encoding {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-tertiary);
|
||||
padding: 0 var(--space-1);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Session overlay (connecting / error states)
|
||||
----------------------------------------------------------------------- */
|
||||
.session-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(26, 26, 46, 0.85);
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.session-overlay-connecting {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.session-overlay-connecting .spinner {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin: 0 auto var(--space-4);
|
||||
}
|
||||
|
||||
.session-overlay-connecting p {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.session-overlay-error {
|
||||
text-align: center;
|
||||
max-width: 360px;
|
||||
}
|
||||
|
||||
.session-overlay-error h3 {
|
||||
font-size: var(--text-md);
|
||||
font-weight: var(--weight-semibold);
|
||||
color: var(--error);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.session-overlay-error p {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-tertiary);
|
||||
line-height: var(--leading-relaxed);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.session-overlay-error .session-error-detail {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-disabled);
|
||||
background: var(--bg-input);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border-radius: var(--radius-md);
|
||||
margin-bottom: var(--space-4);
|
||||
text-align: left;
|
||||
max-height: 120px;
|
||||
overflow-y: auto;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.session-overlay-actions {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/* ==========================================================================
|
||||
Nexus Terminal — Design Tokens
|
||||
Central palette, spacing, typography, and layout constants.
|
||||
All values consumed via var(--token-name) throughout the application.
|
||||
========================================================================== */
|
||||
|
||||
:root {
|
||||
/* -----------------------------------------------------------------------
|
||||
Colour palette — dark charcoal theme
|
||||
----------------------------------------------------------------------- */
|
||||
--bg-base: #1a1a2e; /* deepest background */
|
||||
--bg-surface: #1f1f35; /* primary surface (sidebar, bars) */
|
||||
--bg-elevated: #252540; /* elevated panels, cards */
|
||||
--bg-overlay: #2a2a4a; /* overlays, dropdowns */
|
||||
--bg-hover: #30304f; /* hover state backgrounds */
|
||||
--bg-active: #353560; /* active / pressed state */
|
||||
--bg-input: #1c1c32; /* input field backgrounds */
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Border colours
|
||||
----------------------------------------------------------------------- */
|
||||
--border-subtle: rgba(255, 255, 255, 0.06);
|
||||
--border-default: rgba(255, 255, 255, 0.10);
|
||||
--border-strong: rgba(255, 255, 255, 0.16);
|
||||
--border-focus: var(--accent);
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Text colours
|
||||
----------------------------------------------------------------------- */
|
||||
--text-primary: #e8e8f0;
|
||||
--text-secondary: #a0a0b8;
|
||||
--text-tertiary: #6e6e88;
|
||||
--text-disabled: #4a4a60;
|
||||
--text-inverse: #1a1a2e;
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Accent colours — teal / cyan
|
||||
----------------------------------------------------------------------- */
|
||||
--accent: #4ecdc4;
|
||||
--accent-hover: #45b7af;
|
||||
--accent-muted: rgba(78, 205, 196, 0.15);
|
||||
--accent-strong: rgba(78, 205, 196, 0.25);
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Status colours
|
||||
----------------------------------------------------------------------- */
|
||||
--success: #66bb6a;
|
||||
--warning: #ffa726;
|
||||
--error: #ef5350;
|
||||
--info: #42a5f5;
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Protocol badge colours
|
||||
----------------------------------------------------------------------- */
|
||||
--protocol-ssh: #4ecdc4;
|
||||
--protocol-telnet: #ffa726;
|
||||
--protocol-raw: #ab47bc;
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Spacing scale (4 px base unit)
|
||||
----------------------------------------------------------------------- */
|
||||
--space-1: 4px;
|
||||
--space-2: 8px;
|
||||
--space-3: 12px;
|
||||
--space-4: 16px;
|
||||
--space-5: 20px;
|
||||
--space-6: 24px;
|
||||
--space-8: 32px;
|
||||
--space-10: 40px;
|
||||
--space-12: 48px;
|
||||
--space-16: 64px;
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Typography
|
||||
----------------------------------------------------------------------- */
|
||||
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI',
|
||||
system-ui, sans-serif;
|
||||
--font-mono: 'JetBrains Mono', 'Fira Code', 'SF Mono', Monaco,
|
||||
'Cascadia Code', monospace;
|
||||
|
||||
--text-xs: 11px;
|
||||
--text-sm: 12px;
|
||||
--text-base: 13px;
|
||||
--text-md: 14px;
|
||||
--text-lg: 16px;
|
||||
--text-xl: 18px;
|
||||
--text-2xl: 22px;
|
||||
|
||||
--leading-tight: 1.25;
|
||||
--leading-normal: 1.5;
|
||||
--leading-relaxed: 1.625;
|
||||
|
||||
--weight-normal: 400;
|
||||
--weight-medium: 500;
|
||||
--weight-semibold: 600;
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Border radius
|
||||
----------------------------------------------------------------------- */
|
||||
--radius-sm: 3px;
|
||||
--radius-md: 5px;
|
||||
--radius-lg: 8px;
|
||||
--radius-xl: 12px;
|
||||
--radius-full: 9999px;
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Shadows
|
||||
----------------------------------------------------------------------- */
|
||||
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.30);
|
||||
--shadow-md: 0 2px 8px rgba(0, 0, 0, 0.40);
|
||||
--shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.50);
|
||||
--shadow-xl: 0 8px 32px rgba(0, 0, 0, 0.60);
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Transitions
|
||||
----------------------------------------------------------------------- */
|
||||
--transition-fast: 100ms ease;
|
||||
--transition-normal: 200ms ease;
|
||||
--transition-slow: 300ms ease;
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Z-index layers
|
||||
----------------------------------------------------------------------- */
|
||||
--z-base: 0;
|
||||
--z-sticky: 100;
|
||||
--z-dropdown: 200;
|
||||
--z-modal-backdrop: 300;
|
||||
--z-modal: 400;
|
||||
--z-notification: 500;
|
||||
--z-tooltip: 600;
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Layout
|
||||
----------------------------------------------------------------------- */
|
||||
--sidebar-width: 260px;
|
||||
--sidebar-width-collapsed: 48px;
|
||||
--tab-bar-height: 36px;
|
||||
--toolbar-height: 40px;
|
||||
--status-bar-height: 24px;
|
||||
}
|
||||
Reference in New Issue
Block a user