/* App Sandbox - Dark Theme */ :root { --bg: #1e1e1e; --ctrl-bg: #2d2d2d; --ctrl-border: #3e3e3e; --text: #e6e6e6; --text-dim: #888; --accent: #4ea6f5; --accent-hover: #6db8ff; --danger: #dc3232; --danger-dim: #8c2828; --success: #00c800; --warning: #dcc800; --section-gap: 12px; --radius: 4px; } * { box-sizing: border-box; margin: 0; padding: 0; } body { background: var(--bg); color: var(--text); font-family: 'Segoe UI', sans-serif; font-size: 13px; padding: 8px 12px; overflow: hidden; user-select: none; height: 100vh; display: flex; flex-direction: column; } /* Section headings */ h2 { font-size: 12px; font-weight: 600; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 8px; padding-bottom: 4px; border-bottom: 1px solid var(--ctrl-border); } /* Section header row: title on left, action button on right */ .section-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; padding-bottom: 4px; border-bottom: 1px solid var(--ctrl-border); } .section-header h2 { border-bottom: none; margin-bottom: 0; padding-bottom: 0; } #btn-new-sandbox { height: 26px; padding: 2px 14px; } /* Collapsible sections */ h2.collapsible { cursor: pointer; } h2.collapsible:hover { color: var(--text); } .collapse-arrow { display: inline-block; font-size: 10px; transition: transform 0.15s; } .collapsed .collapse-arrow { transform: rotate(-90deg); } .collapsed .section-body, .collapsed #log-panel { display: none; } /* Form layout */ .form-grid { display: grid; grid-template-columns: 90px 1fr; gap: 4px 8px; align-items: center; } .form-grid label { font-size: 12px; color: var(--text-dim); text-align: right; padding-right: 4px; } .form-row { display: flex; align-items: center; gap: 6px; min-height: 26px; } .form-row .host-info { font-size: 11px; color: var(--text-dim); margin-left: 8px; } /* Inputs */ input[type="text"], input[type="number"], input[type="password"], select { background: var(--ctrl-bg); color: var(--text); border: 1px solid var(--ctrl-border); border-radius: var(--radius); padding: 3px 6px; font-size: 12px; font-family: inherit; outline: none; height: 24px; } input:focus, select:focus { border-color: var(--accent); } input[type="text"] { width: 100%; } input[type="number"] { width: 80px; } input[type="password"] { width: 100%; } select { min-width: 80px; } #gpu-mode, #edit-gpu-mode { max-width: 100%; text-overflow: ellipsis; } .input-sm { width: 80px !important; } .input-md { width: 140px !important; } /* Buttons */ button { background: var(--ctrl-bg); color: var(--text); border: 1px solid var(--ctrl-border); border-radius: var(--radius); padding: 4px 12px; font-size: 12px; font-family: inherit; cursor: pointer; height: 26px; white-space: nowrap; } button:hover { background: #3a3a3a; border-color: #555; } button:active { background: #444; } button:disabled { opacity: 0.4; cursor: default; } button:disabled:hover { background: var(--ctrl-bg); border-color: var(--ctrl-border); } /* Disabled checkboxes (e.g. "Deploy SSH key" until "SSH Server" is ticked). Chromium/WebView2 dims a disabled native checkbox automatically, but WebKit (the macOS WKWebView host) does not -- so dim it, and the whole label, here to make the unavailable affordance obvious on both engines. */ input[type="checkbox"]:disabled { opacity: 0.4; cursor: default; } label:has(input[type="checkbox"]:disabled) { opacity: 0.5; cursor: default; } button.primary { background: #2a5a8a; border-color: #3a7aba; } button.primary:hover { background: #3a6a9a; } button.primary:disabled { background: #2a3a4a; border-color: #3a4a5a; } /* Image row with OR separator */ .image-row { display: flex; align-items: center; gap: 6px; } .image-row input[type="text"] { flex: 1; min-width: 120px; } .or-separator { color: var(--text-dim); font-size: 11px; font-weight: 600; padding: 0 2px; } .image-row select { min-width: 140px; } /* Template custom dropdown */ .template-dropdown { position: relative; min-width: 180px; } .template-dropdown-selected { background: var(--ctrl-bg); color: var(--text); border: 1px solid var(--ctrl-border); border-radius: var(--radius); padding: 3px 6px; font-size: 12px; height: 24px; cursor: pointer; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .template-dropdown.open .template-dropdown-selected { border-color: var(--accent); } .template-dropdown-list { display: none; position: absolute; top: 100%; left: 0; right: 0; background: var(--ctrl-bg); border: 1px solid var(--accent); border-radius: var(--radius); margin-top: 2px; z-index: 100; max-height: 200px; overflow-y: auto; } .template-dropdown.open .template-dropdown-list { display: block; } .template-dropdown-item { display: flex; align-items: center; padding: 4px 6px; font-size: 12px; cursor: pointer; color: var(--text); } .template-dropdown-item:hover { background: #3a3a3a; } .template-dropdown-item .tpl-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .template-dropdown-item .tpl-delete { flex-shrink: 0; margin-left: 6px; cursor: pointer; opacity: 0.5; font-size: 14px; } .template-dropdown-item .tpl-delete:hover { opacity: 1; } /* Inline field validation warning */ .field-warn { font-size: 11px; color: var(--danger); white-space: normal; overflow-wrap: anywhere; flex: 1 1 0; min-width: 0; } /* Password match validation */ .pass-mismatch:focus { border-color: var(--danger-dim) !important; } .pass-match:focus { border-color: var(--success) !important; } /* Password reveal toggle */ .toggle-pass { cursor: pointer; font-size: 14px; opacity: 0.5; user-select: none; } .toggle-pass:hover { opacity: 1; } /* Credentials row */ .cred-row { display: flex; align-items: center; gap: 6px; } .cred-row label { font-size: 12px; color: var(--text-dim); white-space: nowrap; } .cred-row input { flex: 1; } /* Action buttons row */ .action-buttons { display: flex; gap: 6px; margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--ctrl-border); } /* Section spacing */ section { margin-bottom: var(--section-gap); flex-shrink: 0; } section:last-of-type { flex: 1; min-height: 0; display: flex; flex-direction: column; margin-bottom: 0; } section:last-of-type.collapsed { flex: 0; } /* VM Table */ .vm-table-wrap { overflow-x: auto; border: 1px solid var(--ctrl-border); border-radius: var(--radius); } table { width: 100%; border-collapse: collapse; font-size: 12px; } th { background: #252525; color: var(--text-dim); font-weight: 600; font-size: 11px; text-align: left; padding: 4px 6px; border-bottom: 1px solid var(--ctrl-border); white-space: nowrap; position: sticky; top: 0; } td { padding: 3px 6px; border-bottom: 1px solid #2a2a2a; white-space: nowrap; } td.empty-state { text-align: center; color: var(--text-dim); padding: 20px 6px; } .empty-state-btn { height: 32px; padding: 4px 18px; font-size: 13px; } tr:hover td { background: #252525; } tr.selected td { background: #1a3a5a; } /* Icon columns — fixed width, centered in both header and body */ th.icon-col, td.icon-col { width: 28px; text-align: center; padding-left: 2px; padding-right: 2px; } /* Icon button cells */ .icon-btn { background: none; border: none; font-size: 14px; padding: 0; height: auto; width: 100%; text-align: center; cursor: pointer; line-height: 1; } .icon-btn:hover { opacity: 0.8; } .icon-btn:disabled { opacity: 0.3; cursor: default; } /* Icon colors by state */ .icon-btn.start { color: var(--success); } .icon-btn.start.inactive { color: #505050; } .icon-btn.shutdown { color: var(--warning); } .icon-btn.shutdown.inactive { color: #505050; } .icon-btn.stop { color: #dc3232; } .icon-btn.stop.inactive { color: #505050; } .icon-btn.delete { color: #c83c3c; } .icon-btn.delete.running { color: #642828; } .icon-btn.ssh { color: var(--accent); font-family: monospace; font-weight: bold; } .icon-btn.ssh.inactive { color: #505050; } .icon-btn.ssh.hidden { display: none; } .icon-btn.edit { color: var(--text-dim); } .icon-btn.edit:hover { color: var(--text); } /* Agent dot */ .agent-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: #c80000; vertical-align: middle; } .agent-dot.online { background: var(--success); } .agent-dot.disabled { background: var(--text-dim); } /* Status text */ .status-running { color: var(--success); font-weight: 600; } .status-shutting-down { color: var(--warning); font-weight: 600; } .status-building { color: var(--accent); font-weight: 600; } .spinner { display: inline-block; width: 10px; height: 10px; border: 2px solid var(--accent); border-top-color: transparent; border-radius: 50%; animation: spin 0.8s linear infinite; margin-left: 5px; vertical-align: middle; } @keyframes spin { to { transform: rotate(360deg); } } .status-stopped { color: var(--text-dim); } .snap-cell { white-space: nowrap; } .snap-open { display: block; max-width: 220px; overflow: hidden; text-overflow: ellipsis; font-size: 11px; } /* Log panel */ #log-panel { background: var(--ctrl-bg); border: 1px solid var(--ctrl-border); border-radius: var(--radius); padding: 6px; flex: 1; min-height: 60px; overflow-y: auto; font-family: 'Consolas', 'Courier New', monospace; font-size: 11px; line-height: 1.5; color: #ccc; user-select: text; cursor: text; } #log-panel .log-line { white-space: pre-wrap; word-break: break-all; } /* Confirmation modal */ .modal-overlay { display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.6); z-index: 1000; align-items: center; justify-content: center; } .modal-overlay.active { display: flex; } .modal { background: #2a2a2a; border: 1px solid #555; border-radius: 6px; padding: 20px; max-width: 420px; width: 90%; } .modal.wide { max-width: 640px; width: 92%; display: flex; flex-direction: column; max-height: calc(100vh - 32px); } .modal.wide h3, .modal.wide .modal-buttons { flex-shrink: 0; } #edit-vm-overlay .modal { max-width: 480px; } .modal-form-body { min-height: 0; overflow-y: auto; color-scheme: dark; margin-bottom: 16px; } .modal-form-body .form-grid { grid-template-columns: 90px minmax(0, 1fr); } .modal-form-body .form-row, .modal-form-body .cred-row, .modal-form-body .image-row { min-width: 0; flex-wrap: wrap; } .modal-form-body .cred-row input { min-width: 0; } #edit-vm-warn, #snapshot-warn { display: block; margin-top: 12px; } #edit-vm-warn:empty, #snapshot-warn:empty { display: none; } #snapshot-current { overflow-wrap: anywhere; } #snapshot-tree { border: 1px solid var(--ctrl-border); border-radius: var(--radius); background: var(--ctrl-bg); padding: 6px; } #snapshot-tree ul { list-style: none; margin: 0; padding: 0; } #snapshot-tree ul ul { margin-left: 13px; padding-left: 12px; border-left: 1px solid var(--ctrl-border); } .snapshot-choice { display: flex; align-items: center; gap: 8px; padding: 6px; width: 100%; height: auto; font-size: inherit; text-align: left; white-space: normal; } .snapshot-choice, .snapshot-choice:hover, .snapshot-choice:active, .snapshot-choice:disabled:hover { background: none; border: none; } .snapshot-choice[aria-pressed="true"] .snapshot-name { font-weight: 600; } .snapshot-choice:disabled { opacity: 1; } .snapshot-choice:focus-visible { outline: 1px solid var(--accent); outline-offset: -1px; } .snapshot-name { min-width: 0; overflow-wrap: anywhere; } .snapshot-meta { min-width: 0; margin-left: auto; color: var(--text-dim); font-size: 11px; text-align: right; overflow-wrap: anywhere; } #snapshot-help { margin: 12px 0 0; } #btn-snapshot-create { margin-right: auto; } .disk-location-btn { flex-shrink: 0; } #disk-location-overlay .modal { max-width: 560px; max-height: calc(100vh - 32px); overflow-y: auto; color-scheme: dark; } #disk-location-overlay .disk-directory-field { margin-bottom: 16px; } .disk-directory-label { display: block; color: var(--text-dim); font-size: 12px; margin-bottom: 6px; } .disk-directory-field { min-width: 0; } .disk-directory-row { display: flex; align-items: center; gap: 6px; } .disk-directory-row input[type="text"] { flex: 1; min-width: 0; } .disk-directory-row button { flex-shrink: 0; } .disk-directory-field .field-warn { display: block; margin: 4px 0 0; } .disk-directory-field .field-warn:empty { display: none; } #disk-location-space { display: block; color: var(--text-dim); font-size: 11px; margin-top: 6px; } @media (max-width: 560px) { .modal-form-body .form-grid { grid-template-columns: minmax(0, 1fr); } .modal-form-body .form-grid > label { text-align: left; margin-top: 5px; } .modal.wide .modal-buttons { flex-wrap: wrap; } .modal.wide { padding: 14px; } .snapshot-choice { flex-wrap: wrap; } .snapshot-meta { flex-basis: 100%; text-align: left; } } .modal h3 { font-size: 14px; margin-bottom: 12px; overflow-wrap: anywhere; } .modal p { font-size: 12px; color: #bbb; margin-bottom: 16px; line-height: 1.5; } .modal-buttons { display: flex; justify-content: flex-end; gap: 8px; } .modal-buttons .danger { background: #5a1a1a; border-color: #8a2a2a; color: #ff8888; } .modal-buttons .danger:hover { background: #6a2a2a; } .modal-buttons .primary { background: #1a3a5a; border-color: #2a5a8a; color: #88bbff; } .modal-buttons .primary:hover { background: #2a4a6a; } .prereq-progress { margin-top: 12px; height: 6px; background: #1a1a1a; border-radius: 3px; overflow: hidden; } .prereq-progress-bar { height: 100%; width: 0%; background: #4a8adf; border-radius: 3px; transition: width 0.3s ease; } .prereq-pct { margin-top: 6px; font-size: 11px; color: #888; text-align: center; }