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; +}