/* Screen-level layout: welcome screen and the main browsing shell. */ #screen-root { flex: 1; min-height: 0; position: relative; } .screen { position: absolute; inset: 0; opacity: 0; animation: screen-in 0.2s var(--ease-out) forwards; } @keyframes screen-in { from { opacity: 0; } to { opacity: 1; } } /* ================= Welcome screen ================= */ .welcome { height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: flex-start; padding-top: 11vh; text-align: center; overflow-y: auto; /* soft ember glow behind the hero so the screen isn't a flat void */ background: radial-gradient(560px 300px at 50% -60px, var(--accent-soft), transparent 70%), var(--bg-base); } .welcome-hero { display: flex; flex-direction: column; align-items: center; } .welcome-logo { width: 76px; height: 76px; border-radius: 20px; background: linear-gradient(145deg, var(--accent-hover), var(--accent-dim)); display: flex; align-items: center; justify-content: center; color: var(--on-accent); box-shadow: 0 12px 36px color-mix(in srgb, var(--accent) 28%, transparent), inset 0 1px 0 rgba(255, 255, 255, 0.25); } .welcome-title { margin-top: 16px; font-size: 22px; font-weight: 700; letter-spacing: -0.01em; color: var(--text-primary); } .welcome-subtitle { margin-top: 4px; font-size: 12.5px; color: var(--text-hint); } .welcome-actions { margin-top: 24px; display: flex; align-items: center; gap: 10px; } .welcome-cta { height: 32px; padding: 0 14px; } .welcome-actions .kbd { margin-left: 4px; } .welcome-status { margin-top: 14px; min-height: 20px; display: flex; align-items: center; gap: 8px; } .welcome-columns { margin-top: 24px; width: min(720px, 90vw); display: flex; align-items: flex-start; justify-content: center; gap: 14px; flex-wrap: wrap; padding-bottom: 48px; } .welcome-card { flex: 1 1 300px; min-width: 280px; max-width: 352px; background: color-mix(in srgb, var(--bg-panel) 72%, transparent); border: 1px solid var(--border); border-radius: var(--radius-card); padding: 12px 8px 8px; text-align: left; } .welcome-card-title { display: flex; align-items: center; gap: 6px; padding: 0 10px 8px; font-size: 10.5px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; color: var(--text-hint); } .recent-row { display: flex; align-items: center; gap: 8px; height: 34px; padding: 0 10px; border-radius: var(--radius-sm); cursor: pointer; transition: background-color 0.1s var(--ease-out); } .recent-row:hover { background: var(--bg-row-hover); } /* Shared by both the welcome screen's recent-connections list (.recent-row) and the toolbar's history popover (.popover-row) -- unscoped so it applies in either parent. */ .recent-label { flex: 1; min-width: 0; font-size: 12.5px; color: var(--text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .recent-time { flex-shrink: 0; margin-left: auto; font-size: 10.5px; color: var(--text-hint); white-space: nowrap; } /* ================= Main shell ================= */ .app-shell { height: 100%; display: flex; flex-direction: column; } /* Toolbar — now floats above the body with uniform rounding */ .toolbar { height: var(--toolbar-h); flex-shrink: 0; display: flex; align-items: center; gap: 8px; padding: 0 16px; margin: 8px 14px 0; background: var(--bg-toolbar); border-radius: var(--radius-card) var(--radius-card) 0 0; position: relative; z-index: 3; } .toolbar-spacer { flex: 1; } /* Body row: sidebar + main content, floating in a padded, gapless frame so the two panels read as one rounded pill (sidebar rounds its left corners, the content card rounds its right corners — see mockup "new shell concept"). */ .app-body { flex: 1; min-height: 0; display: flex; padding: 0 14px 14px; gap: 0; background: var(--bg-base); } .sidebar { width: var(--sidebar-w); min-width: 140px; max-width: 260px; flex-shrink: 0; display: flex; flex-direction: column; background: var(--bg-panel); border: 1px solid rgba(255, 255, 255, 0.04); border-right: none; border-radius: 0 0 0 var(--radius-card); overflow-y: auto; padding: 16px 0; margin-top: -8px; position: relative; z-index: 1; } .sidebar-logo { display: flex; align-items: center; gap: 8px; padding: 0 16px; margin-bottom: 18px; flex-shrink: 0; } .sidebar-logo-badge { width: 26px; height: 26px; border-radius: var(--radius-sm); flex-shrink: 0; background: linear-gradient(145deg, var(--accent), var(--accent-dim)); display: flex; align-items: center; justify-content: center; color: var(--on-accent); font-weight: 800; font-size: 13px; } .sidebar-logo-text { font-weight: 700; font-size: 14px; color: var(--text-primary); } .sidebar-section-title { padding: 0 16px; font-size: 10.5px; font-weight: 700; color: var(--text-hint); letter-spacing: 0.02em; flex-shrink: 0; } .sidebar-divider { height: 1px; background: var(--separator); margin: 10px 8px; flex-shrink: 0; } .sidebar-spacer { flex: 1; } .sidebar-footer { padding: 0 16px; font-size: 10px; color: var(--text-hint); flex-shrink: 0; } .main-content { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; background: var(--bg-content); border-radius: 0 0 var(--radius-card) 0; box-shadow: 0 12px 32px rgba(0, 0, 0, 0.45), inset 0 1px 0 rgba(255, 255, 255, 0.06); overflow: hidden; position: relative; z-index: 1; } /* Tab bar (Local + one per connection) */ .tabbar { height: var(--tabbar-h); flex-shrink: 0; display: flex; align-items: stretch; background: var(--bg-panel); border-bottom: 1px solid var(--border); overflow-x: auto; } .tab { display: flex; align-items: center; gap: 6px; padding: 0 12px; height: 100%; border: none; border-right: 1px solid var(--border); background: var(--bg-tab-idle); color: var(--text-dim); font-size: 12px; cursor: pointer; white-space: nowrap; flex-shrink: 0; transition: background-color 0.1s var(--ease-out), color 0.1s var(--ease-out); } .tab:hover { background: var(--bg-row-hover); } .tab.active { background: var(--bg-tab-active); color: var(--text-primary); } .tab-close { width: 22px; height: 22px; border-radius: 4px; border: none; background: transparent; color: var(--text-hint); display: flex; align-items: center; justify-content: center; font-size: 13px; line-height: 1; cursor: pointer; opacity: 0.7; } .tab-close:hover { background: rgba(255, 255, 255, 0.12); color: var(--text-primary); opacity: 1; } /* localPane.js/remotePane.js render straight into this element (there's no separate .pane wrapper -- renderActivePane() swaps the one visible pane's content in place rather than stacking multiple absolutely-positioned panes), so it has to be the flex column container itself. */ .tab-panes { flex: 1; min-height: 0; position: relative; display: flex; flex-direction: column; } /* Path bar (top of each pane) */ .path-bar { height: 32px; flex-shrink: 0; display: flex; align-items: center; gap: 8px; padding: 0 16px; background: var(--bg-base); border-bottom: 1px solid var(--border); } .path-bar-label { font-size: 10.5px; font-weight: 700; color: var(--text-dim); flex-shrink: 0; } .breadcrumbs { display: flex; align-items: center; gap: 2px; overflow: hidden; flex: 1; min-width: 0; } .breadcrumb-seg { border: none; background: transparent; color: var(--text-dim); font-size: 12px; padding: 3px 5px; border-radius: var(--radius-sm); cursor: pointer; flex-shrink: 0; } .breadcrumb-seg:hover { background: rgba(255, 255, 255, 0.06); color: var(--text-primary); } .breadcrumb-seg.current { color: var(--text-primary); font-weight: 600; cursor: default; } .breadcrumb-seg.current:hover { background: none; } .breadcrumb-sep { color: var(--text-hint); font-size: 12px; } .path-bar-right { margin-left: auto; display: flex; align-items: center; gap: 6px; color: var(--text-hint); font-size: 10.5px; flex-shrink: 0; } /* File table */ .file-table-wrap { flex: 1; min-height: 0; display: flex; flex-direction: column; } .file-table-header { display: grid; grid-template-columns: 1fr 140px 60px 80px; align-items: center; height: 26px; padding: 0 16px; background: var(--bg-header); border-bottom: 1px solid var(--border); flex-shrink: 0; } .sort-header { appearance: none; border: none; background: transparent; color: var(--text-dim); font-size: 10.5px; font-weight: 700; text-align: left; padding: 0; cursor: pointer; height: 20px; justify-self: start; } .file-table-header .sort-header:nth-child(2), .file-table-header .sort-header:nth-child(3), .file-table-header .sort-header:nth-child(4) { justify-self: end; text-align: right; } .sort-header:hover { color: var(--text-primary); } .sort-header.active { color: var(--accent); } .file-table-body { flex: 1; min-height: 0; overflow-y: auto; padding: 2px 0; } .file-row { display: grid; grid-template-columns: 1fr 140px 60px 80px; align-items: center; height: var(--row-h); padding: 0 16px; margin: 0 8px; border-radius: var(--radius-sm); cursor: default; transition: background-color 0.08s var(--ease-out); border: 1px solid transparent; } .file-row:hover { background: var(--bg-row-hover); } .file-row.selected { background: var(--bg-row-sel); } .file-row.dragging { opacity: 0.35; } /* A dragged file/folder hovering a folder row (or "..") -- it'll spring open into that folder after HOVER_OPEN_DELAY, see makeDropTarget in dnd.js. */ .file-row.drag-hover { background: var(--accent-soft); outline: 1px dashed var(--accent-ring); outline-offset: -1px; } .file-row-name { display: flex; align-items: center; gap: 8px; min-width: 0; } .file-row-name span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12.5px; } .file-row-modified { font-size: 10.5px; color: var(--text-hint); text-align: right; padding-right: 12px; } .file-row-type { font-size: 11px; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .file-row-size { font-size: 11px; color: var(--text-dim); text-align: right; font-variant-numeric: tabular-nums; } /* Footer (below table) */ .pane-footer { height: 28px; flex-shrink: 0; display: flex; align-items: center; gap: 8px; padding: 0 8px 0 16px; background: var(--bg-tab-idle); border-top: 1px solid var(--border); } .pane-footer-right { margin-left: auto; display: flex; align-items: center; } /* Queue panel */ .queue-panel { flex-shrink: 0; background: var(--bg-queue); border-top: 1px solid var(--border); overflow: hidden; height: var(--queue-collapsed-h); transition: height 0.18s var(--ease-out); } .queue-panel.expanded { height: var(--queue-expanded-h); } .queue-header { height: var(--queue-collapsed-h); flex-shrink: 0; display: flex; align-items: center; gap: 8px; padding: 0 16px; cursor: pointer; } .queue-header-title { display: flex; align-items: center; gap: 6px; color: var(--text-primary); font-size: 11.5px; font-weight: 600; } .queue-header-right { margin-left: auto; display: flex; align-items: center; gap: 8px; font-size: 11px; } .queue-list { overflow-y: auto; height: calc(var(--queue-expanded-h) - var(--queue-collapsed-h)); padding: 2px 0; } .queue-task { display: flex; align-items: center; gap: 10px; height: 36px; padding: 0 16px; } .queue-task-name { width: 150px; flex-shrink: 0; font-size: 12px; color: var(--text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .queue-task-pct { width: 38px; flex-shrink: 0; text-align: right; font-size: 11px; color: var(--text-dim); font-variant-numeric: tabular-nums; } .queue-task-state { flex-shrink: 0; display: flex; align-items: center; gap: 4px; font-size: 10.5px; min-width: 90px; justify-content: flex-end; } .queue-task-actions { display: flex; gap: 2px; flex-shrink: 0; } /* Status bar */ .status-bar { height: var(--status-h); flex-shrink: 0; display: flex; align-items: center; gap: 10px; padding: 0 16px; background: var(--bg-base); border-top: 1px solid var(--border); font-size: 11px; color: var(--text-dim); } .status-bar-right { margin-left: auto; display: flex; align-items: center; gap: 10px; } /* ================= Per-platform adjustments ================= */ /* initPlatform() stamps data-platform (mac/windows/linux), data-desktop (kde/gnome/hyprland/…) and data-tiling on — see js/platform.js. */ /* Tiling compositors (Hyprland, sway, i3, …): the window fills its slot edge-to-edge, so the decorative floating-card gutter reads as wasted space — tighten it and square off the shell slightly. */ html[data-tiling="true"] .app-body { padding: 8px; } /* Windows: Segoe UI sits a touch lower on the line box than SF/Noto; antialiased smoothing also renders it too thin on ClearType displays. */ html[data-platform="windows"] body { -webkit-font-smoothing: auto; } /* GNOME/KDE ship overlay scrollbars — a permanently-reserved 9px gutter looks foreign there; slim it down. */ html[data-platform="linux"] ::-webkit-scrollbar { width: 7px; height: 7px; } /* ── Pane resize handle ──────────────────────────────────────────────── */ .pane-resize-handle { width: 5px; cursor: col-resize; background: transparent; position: relative; z-index: 10; flex-shrink: 0; margin: 0 -2px; transition: background 0.12s var(--ease-out); } .pane-resize-handle:hover, .pane-resize-handle:active { background: var(--accent-soft); } .pane-resize-handle::after { content: ""; position: absolute; left: 2px; top: 50%; transform: translateY(-50%); width: 1px; height: 36px; background: var(--border); border-radius: 1px; transition: background 0.12s var(--ease-out), height 0.12s var(--ease-out); } .pane-resize-handle:hover::after, .pane-resize-handle:active::after { background: var(--accent); height: 48px; } /* ── Split-pane grid ─────────────────────────────────────────────────── */ .pane-grid { display: grid; flex: 1; min-height: 0; } .pane-cell { display: flex; flex-direction: column; min-width: 0; min-height: 0; background: var(--bg-content); position: relative; overflow: hidden; } .pane-cell.drop-zone { background: var(--bg-panel); outline: 2px solid var(--accent); outline-offset: -2px; z-index: 2; } .pane-cell.dragging { opacity: 0.5; } /* Pane header */ .pane-header { display: flex; align-items: center; height: 28px; padding: 0 8px 0 12px; background: var(--bg-tab-idle); border-bottom: 1px solid var(--border); cursor: grab; flex-shrink: 0; gap: 6px; user-select: none; } .pane-header:active { cursor: grabbing; } .pane-label { font-size: 11px; font-weight: 600; color: var(--text-dim); flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .pane-actions { display: flex; align-items: center; gap: 2px; flex-shrink: 0; } .pane-split-btn, .pane-close-btn { width: 24px; height: 24px; border: none; border-radius: 4px; background: transparent; color: var(--text-hint); display: flex; align-items: center; justify-content: center; cursor: pointer; font-size: 13px; padding: 0; } .pane-split-btn:hover, .pane-close-btn:hover { background: rgba(255, 255, 255, 0.08); color: var(--text-primary); } .pane-body { flex: 1; min-height: 0; display: flex; flex-direction: column; } /* Pane count badge in tab bar */ .tab-pane-count { display: inline-flex; align-items: center; justify-content: center; min-width: 18px; height: 18px; padding: 0 4px; border-radius: 4px; background: var(--accent-soft); color: var(--accent); font-size: 10px; font-weight: 700; margin: 0 4px 0 8px; flex-shrink: 0; } /* ── Pane edge drop zones ────────────────────────────────────────────── */ .pane-zone { position: absolute; opacity: 0; transition: opacity 0.1s ease, background 0.1s ease; pointer-events: none; z-index: 5; } .pane-zone--left { left: 0; top: 0; bottom: 0; width: 22%; } .pane-zone--right { right: 0; top: 0; bottom: 0; width: 22%; } .pane-zone--top { left: 0; right: 0; top: 0; height: 22%; } .pane-zone--bottom { left: 0; right: 0; bottom: 0; height: 22%; } .pane-zone--center { left: 22%; right: 22%; top: 22%; bottom: 22%; } .pane-zone--swap { left: 0; right: 0; top: 28px; bottom: 0; } /* below header */ .pane-cell.drop-active[data-drop-zone="left"] .pane-zone--left, .pane-cell.drop-active[data-drop-zone="right"] .pane-zone--right, .pane-cell.drop-active[data-drop-zone="top"] .pane-zone--top, .pane-cell.drop-active[data-drop-zone="bottom"] .pane-zone--bottom, .pane-cell.drop-active[data-drop-zone="center"] .pane-zone--center { opacity: 1; background: var(--accent-soft); } .pane-cell.drop-active[data-drop-zone="center"] .pane-zone--center { background: color-mix(in srgb, var(--accent) 30%, transparent); } /* Pane "spring-loading": shown while a dragged tab hovers a pane that hasn't opened as a drop target yet (see HOVER_OPEN_DELAY in dnd.js) -- distinct from .drop-active so the pause before zones appear still reads as a response to the hover, not a stall. */ .pane-cell.drop-pending { outline: 2px dashed var(--accent-ring); outline-offset: -2px; } /* Tab dragging visual */ .tab-dragging { opacity: 0.45; } .pane-dragging { opacity: 0.4; } /* ── Panel show/hide animations ──────────────────────────────────────── */ /* Sidebar: collapse width + fade */ .sidebar { transition: width 0.25s var(--ease-out), min-width 0.25s var(--ease-out), padding-left 0.25s var(--ease-out), padding-right 0.25s var(--ease-out), border-width 0.25s var(--ease-out), opacity 0.2s var(--ease-out); } .sidebar.hidden { width: 0 !important; min-width: 0 !important; padding-left: 0 !important; padding-right: 0 !important; border: none !important; opacity: 0; overflow: hidden; pointer-events: none; } /* Toolbar: collapse height + fade */ .toolbar { transition: height 0.25s var(--ease-out), margin 0.25s var(--ease-out), padding 0.25s var(--ease-out), opacity 0.2s var(--ease-out); } .toolbar.hidden { height: 0 !important; margin: 0 14px !important; padding-top: 0 !important; padding-bottom: 0 !important; border: none !important; opacity: 0; overflow: hidden; pointer-events: none; } /* Status bar: collapse height + fade */ .status-bar { transition: height 0.25s var(--ease-out), opacity 0.2s var(--ease-out); } .status-bar.hidden { height: 0 !important; opacity: 0; overflow: hidden; pointer-events: none; } /* Queue panel: collapse height + fade */ .queue-panel { transition: height 0.22s var(--ease-out), opacity 0.18s var(--ease-out); } .queue-panel.hidden { height: 0 !important; opacity: 0; overflow: hidden; pointer-events: none; } /* ── Main-content corner rounding ────────────────────────────────────── */ .main-content { transition: border-radius 0.25s var(--ease-out); } /* Default: bottom-right only (sidebar left + toolbar top are flat) */ .main-content:not(.no-sidebar):not(.no-toolbar):not(.no-status) { border-radius: 0 0 var(--radius-card) 0; } /* Sidebar hidden -- round left side */ .main-content.no-sidebar:not(.no-toolbar):not(.no-status) { border-radius: 0 var(--radius-card) var(--radius-card) 0; } /* Toolbar hidden -- round top */ .main-content.no-toolbar:not(.no-sidebar):not(.no-status) { border-radius: var(--radius-card) var(--radius-card) 0 0; } /* Bottom hidden -- round bottom */ .main-content.no-status:not(.no-sidebar):not(.no-toolbar) { border-radius: 0 0 var(--radius-card) var(--radius-card); } /* Sidebar + Toolbar hidden */ .main-content.no-sidebar.no-toolbar:not(.no-status) { border-radius: var(--radius-card) var(--radius-card) var(--radius-card) 0; } /* Sidebar + Bottom hidden */ .main-content.no-sidebar.no-status:not(.no-toolbar) { border-radius: 0 var(--radius-card) var(--radius-card) var(--radius-card); } /* Toolbar + Bottom hidden */ .main-content.no-toolbar.no-status:not(.no-sidebar) { border-radius: var(--radius-card) var(--radius-card) var(--radius-card) var(--radius-card); } /* All three hidden -- fully rounded */ .main-content.no-sidebar.no-toolbar.no-status { border-radius: var(--radius-card); }