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:
2026-04-15 00:00:00 +01:00
parent 35bc9c75b1
commit 61e99e9fca
10 changed files with 3466 additions and 0 deletions
+187
View File
@@ -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;
}
+864
View File
@@ -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); }
}
+439
View File
@@ -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;
}
+137
View File
@@ -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);
}
+490
View File
@@ -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;
}
+311
View File
@@ -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;
}
+307
View File
@@ -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);
}
+392
View File
@@ -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);
}
+140
View File
@@ -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;
}