diff --git a/frontend/index.html b/frontend/index.html
new file mode 100644
index 0000000..266b0bc
--- /dev/null
+++ b/frontend/index.html
@@ -0,0 +1,199 @@
+
+
+
+
+
+ Nexus Terminal
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
No active sessions
+
Select a connection from the sidebar or create a new one to get started.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/css/base.css b/frontend/src/css/base.css
new file mode 100644
index 0000000..eef84b3
--- /dev/null
+++ b/frontend/src/css/base.css
@@ -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;
+}
diff --git a/frontend/src/css/components.css b/frontend/src/css/components.css
new file mode 100644
index 0000000..05d96ca
--- /dev/null
+++ b/frontend/src/css/components.css
@@ -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); }
+}
diff --git a/frontend/src/css/forms.css b/frontend/src/css/forms.css
new file mode 100644
index 0000000..ea4795a
--- /dev/null
+++ b/frontend/src/css/forms.css
@@ -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;
+}
diff --git a/frontend/src/css/layout.css b/frontend/src/css/layout.css
new file mode 100644
index 0000000..12135a3
--- /dev/null
+++ b/frontend/src/css/layout.css
@@ -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);
+}
diff --git a/frontend/src/css/modals.css b/frontend/src/css/modals.css
new file mode 100644
index 0000000..cd64732
--- /dev/null
+++ b/frontend/src/css/modals.css
@@ -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;
+}
diff --git a/frontend/src/css/sidebar.css b/frontend/src/css/sidebar.css
new file mode 100644
index 0000000..37ebc60
--- /dev/null
+++ b/frontend/src/css/sidebar.css
@@ -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;
+}
diff --git a/frontend/src/css/tabs.css b/frontend/src/css/tabs.css
new file mode 100644
index 0000000..a9cbba1
--- /dev/null
+++ b/frontend/src/css/tabs.css
@@ -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);
+}
diff --git a/frontend/src/css/terminal.css b/frontend/src/css/terminal.css
new file mode 100644
index 0000000..f7d1f89
--- /dev/null
+++ b/frontend/src/css/terminal.css
@@ -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);
+}
diff --git a/frontend/src/css/tokens.css b/frontend/src/css/tokens.css
new file mode 100644
index 0000000..a66383d
--- /dev/null
+++ b/frontend/src/css/tokens.css
@@ -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;
+}