:root { --mono: "JetBrains Mono", "IBM Plex Mono", "Cascadia Code", "SF Mono", ui-monospace, Menlo, Consolas, "Liberation Mono", monospace; --bg: #0d1317; --bg2: #121a20; --bg3: #19232b; --line: #25323b; --fg: #cdd6d2; --dim: #7d8d92; --accent: #ff9e3d; --accent-ink: #1a0f02; --up: #62d6a4; --down: #ff6b6b; --warn: #f2c14e; --blue: #6fb1ff; --mag: #c79bff; --cyan: #5fd0e0; --sel: #1d2a33; --sel2: #34495a; --radius: 3px; color-scheme: dark; } :root[data-theme="amber"] { --bg: #120d05; --bg2: #19120a; --bg3: #221910; --line: #3a2c18; --fg: #ecd5a3; --dim: #8f7a4f; --accent: #ffb000; --accent-ink: #1a1000; --up: #b5d96b; --down: #ff7043; --warn: #ffd166; --sel: #2a1f10; --sel2: #5a4624; } :root[data-theme="green"] { --bg: #070b08; --bg2: #0c120d; --bg3: #121b14; --line: #1d3022; --fg: #bfe8c9; --dim: #5f8a69; --accent: #4dff8f; --accent-ink: #02140a; --up: #4dff8f; --down: #ff5f6d; --warn: #e8d44d; --sel: #14241a; --sel2: #2b5a3a; } :root[data-theme="paper"] { --bg: #eef1f0; --bg2: #e4e9e7; --bg3: #d9dfdc; --line: #c3ccc8; --fg: #1c2528; --dim: #66757a; --accent: #b4540a; --accent-ink: #fff7ef; --up: #1d7f5a; --down: #c0392b; --warn: #9a6b00; --blue: #1f5fae; --mag: #7a3fb0; --cyan: #137a88; --sel: #d3dbd8; --sel2: #aebcc4; color-scheme: light; } * { box-sizing: border-box; } html, body { height: 100%; } body { margin: 0; background: var(--bg); color: var(--fg); font: 14px/1.5 var(--mono); font-variant-ligatures: none; -webkit-font-smoothing: antialiased; } ::selection { background: var(--accent); color: var(--accent-ink); } a { color: var(--accent); text-decoration: none; } a:hover { text-decoration: underline; } button, input, select, textarea { font: inherit; color: inherit; } :focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; } .dim { color: var(--dim); } .up { color: var(--up); } .down { color: var(--down); } .warn { color: var(--warn); } .hidden { display: none !important; } .right { text-align: right; } .trunc { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; } /* ---------- shell ---------- */ #app { display: flex; flex-direction: column; height: 100vh; height: 100dvh; } header.top { display: flex; align-items: center; gap: 18px; padding: 0 20px; height: 44px; flex: none; border-bottom: 1px solid var(--line); background: var(--bg); } .brand { font-weight: 700; color: var(--fg); letter-spacing: 0.02em; white-space: nowrap; } .brand b { color: var(--accent); } nav.tabs { display: flex; gap: 2px; flex: 1; overflow-x: auto; scrollbar-width: none; } nav.tabs button { background: none; border: 0; padding: 0 12px; height: 44px; color: var(--dim); cursor: pointer; white-space: nowrap; border-bottom: 2px solid transparent; margin-bottom: -1px; } nav.tabs button:hover { color: var(--fg); } nav.tabs button.on { color: var(--fg); border-bottom-color: var(--accent); } nav.tabs kbd { color: var(--dim); font-size: 11px; margin-right: 6px; } nav.tabs .count { color: var(--accent); margin-left: 6px; } .top .tools { display: flex; gap: 6px; align-items: center; } main { flex: 1; min-height: 0; position: relative; display: flex; flex-direction: column; } #page { flex: 1; overflow: auto; padding: 20px; } #termview { flex: 1; min-height: 0; display: none; flex-direction: column; } #termview.show { display: flex; } #page.hidden { display: none; } footer.status { display: flex; gap: 20px; align-items: center; padding: 0 20px; height: 28px; flex: none; font-size: 12px; border-top: 1px solid var(--line); background: var(--bg2); color: var(--dim); white-space: nowrap; overflow: hidden; } footer.status .grow { flex: 1; } /* ---------- controls ---------- */ .btn { background: var(--bg3); border: 1px solid var(--line); border-radius: var(--radius); padding: 3px 12px; cursor: pointer; color: var(--fg); line-height: 1.5; white-space: nowrap; } .btn:hover { border-color: var(--dim); } .btn:active { transform: translateY(1px); } .btn.primary { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); font-weight: 700; } .btn.primary:hover { filter: brightness(1.08); } .btn.danger { color: var(--down); } .btn.danger:hover { border-color: var(--down); } .btn.ghost { background: none; border-color: transparent; color: var(--dim); } .btn.ghost:hover { color: var(--fg); border-color: var(--line); } .btn.sm { padding: 0 8px; font-size: 12px; } .btn[disabled] { opacity: 0.5; cursor: default; } input[type=text], input[type=password], input[type=number], input[type=search], select, textarea { background: var(--bg2); border: 1px solid var(--line); border-radius: var(--radius); padding: 4px 8px; width: 100%; } input:focus, select:focus, textarea:focus { border-color: var(--accent); outline: none; } textarea { resize: vertical; min-height: 64px; } input[type=checkbox] { accent-color: var(--accent); width: 16px; height: 16px; vertical-align: middle; } select { appearance: none; background-image: linear-gradient(45deg, transparent 50%, var(--dim) 50%), linear-gradient(135deg, var(--dim) 50%, transparent 50%); background-position: calc(100% - 14px) 50%, calc(100% - 9px) 50%; background-size: 5px 5px; background-repeat: no-repeat; padding-right: 26px; } /* ---------- page heads ---------- */ .prompt { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 4px; } .prompt .ps1 { color: var(--accent); } .prompt .ps1 i { color: var(--dim); font-style: normal; } .cursor { display: inline-block; width: 0.6em; height: 1.05em; background: var(--accent); vertical-align: text-bottom; animation: blink 1.1s steps(1) infinite; } @keyframes blink { 50% { opacity: 0; } } .summary { display: flex; gap: 22px; flex-wrap: wrap; margin: 6px 0 16px; } .toolbar { display: flex; gap: 8px; align-items: center; margin-bottom: 16px; flex-wrap: wrap; } .toolbar input[type=search] { max-width: 280px; } .toolbar .grow { flex: 1; } /* ---------- dashboard ---------- */ .group { margin-bottom: 22px; } .group h2 { font-size: 14px; font-weight: 400; margin: 0 0 4px; color: var(--dim); display: flex; gap: 10px; align-items: center; } .group h2::before { content: "┌─"; color: var(--line); } .group h2::after { content: ""; flex: 1; border-top: 1px solid var(--line); } .svc { display: grid; align-items: center; column-gap: 14px; padding: 5px 8px; border-radius: var(--radius); cursor: pointer; grid-template-columns: 1.5ch minmax(12ch, 1.1fr) minmax(0, 1.6fr) 8ch 8ch 31ch 13ch 188px; } .svc:hover { background: var(--sel); } .svc.open { background: var(--sel); } .svc .led { color: var(--dim); font-size: 12px; } .svc.s-up .led { color: var(--up); } .svc.s-down .led { color: var(--down); animation: pulse 2.2s ease-in-out infinite; } .svc.s-retry .led { color: var(--warn); } .svc.s-paused { opacity: 0.55; } @keyframes pulse { 50% { opacity: 0.35; } } .svc .name { font-weight: 700; } .svc .addr { color: var(--dim); } .svc .spark { letter-spacing: 0; white-space: pre; overflow: hidden; } .svc .spark .x { color: var(--down); } .svc .spark .ok { color: var(--up); } .svc .acts { display: flex; gap: 4px; opacity: 0; justify-content: flex-end; } .svc:hover .acts, .svc.open .acts, .svc:focus-within .acts { opacity: 1; } .detail { padding: 6px 8px 14px 3.4ch; background: var(--sel); border-radius: 0 0 var(--radius) var(--radius); margin: -2px 0 6px; } .detail dl { display: grid; grid-template-columns: max-content 1fr; gap: 2px 18px; margin: 0 0 10px; } .detail dt { color: var(--dim); } .detail dd { margin: 0; overflow-wrap: anywhere; } .strip { display: flex; gap: 1px; height: 20px; align-items: stretch; margin: 6px 0 2px; } .strip i { flex: 1; background: var(--line); min-width: 2px; } .strip i.g { background: var(--up); } .strip i.y { background: var(--warn); } .strip i.r { background: var(--down); } .strip-legend { display: flex; justify-content: space-between; color: var(--dim); font-size: 12px; } .empty { border: 1px dashed var(--line); border-radius: var(--radius); padding: 28px; text-align: center; color: var(--dim); } .empty p { margin: 4px 0 14px; } /* ---------- tables ---------- */ table.t { width: 100%; border-collapse: collapse; } table.t th { text-align: left; font-weight: 400; color: var(--dim); padding: 4px 10px; border-bottom: 1px solid var(--line); white-space: nowrap; } table.t td { padding: 5px 10px; border-bottom: 1px solid var(--line); vertical-align: top; } table.t tr.row:hover { background: var(--sel); } table.t td.acts { white-space: nowrap; text-align: right; } .chip { display: inline-block; padding: 0 7px; border: 1px solid var(--line); border-radius: 99px; font-size: 12px; color: var(--dim); } .chip.down, .chip.hostkey, .chip.ssh_fail, .chip.login_fail { color: var(--down); border-color: var(--down); } .chip.up { color: var(--up); border-color: var(--up); } .chip.tls { color: var(--warn); border-color: var(--warn); } .chip.ssh { color: var(--cyan); border-color: var(--cyan); } /* ---------- terminals ---------- */ .termbar { display: flex; align-items: stretch; gap: 0; border-bottom: 1px solid var(--line); background: var(--bg2); flex: none; overflow-x: auto; scrollbar-width: none; } .termbar .tab { display: flex; align-items: center; gap: 8px; padding: 0 10px 0 14px; height: 34px; cursor: pointer; color: var(--dim); border-right: 1px solid var(--line); white-space: nowrap; } .termbar .tab.on { background: var(--bg); color: var(--fg); box-shadow: inset 0 -2px 0 var(--accent); } .termbar .tab .dot { font-size: 11px; } .termbar .tab .dot.connected { color: var(--up); } .termbar .tab .dot.connecting { color: var(--warn); } .termbar .tab .dot.closed, .termbar .tab .dot.error { color: var(--down); } .termbar .tab button { background: none; border: 0; cursor: pointer; color: var(--dim); padding: 0 4px; border-radius: 2px; } .termbar .tab button:hover { color: var(--fg); background: var(--bg3); } .termbar .spacer { flex: 1; } .termbar .tools { display: flex; align-items: center; gap: 6px; padding: 0 10px; } .panes { flex: 1; min-height: 0; position: relative; background: var(--bg); } .pane { position: absolute; inset: 0; padding: 8px 0 4px 12px; display: none; } .pane.on { display: block; } .pane .xterm { height: 100%; } .pane .xterm-viewport { background: transparent !important; } .term-empty { padding: 24px; color: var(--dim); } .term-empty .hostlist { margin-top: 14px; display: flex; flex-direction: column; gap: 2px; max-width: 520px; } .term-empty .hostlist button { text-align: left; background: none; border: 0; padding: 4px 8px; cursor: pointer; border-radius: var(--radius); } .term-empty .hostlist button:hover { background: var(--sel); } /* ---------- login ---------- */ .login { max-width: 560px; margin: 12vh auto 0; padding: 0 20px; } .login h1 { font-size: 14px; margin: 0 0 18px; font-weight: 700; } .login h1 b { color: var(--accent); } .login p { color: var(--dim); margin: 0 0 18px; } .login label { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; } .login label span { width: 18ch; color: var(--dim); flex: none; } .login .err { color: var(--down); min-height: 1.5em; margin: 6px 0; } /* ---------- modal / palette / toast ---------- */ .overlay { position: fixed; inset: 0; background: rgba(4, 8, 10, 0.66); display: flex; align-items: flex-start; justify-content: center; padding: 8vh 16px 16px; z-index: 50; overflow: auto; } :root[data-theme="paper"] .overlay { background: rgba(30, 40, 44, 0.4); } .modal { background: var(--bg); border: 1px solid var(--dim); border-radius: var(--radius); width: 100%; max-width: 640px; box-shadow: 0 18px 60px rgba(0, 0, 0, 0.5); } .modal header { padding: 10px 16px; border-bottom: 1px solid var(--line); display: flex; justify-content: space-between; align-items: center; } .modal header h3 { margin: 0; font-size: 14px; font-weight: 700; } .modal .body { padding: 16px; display: flex; flex-direction: column; gap: 12px; } .modal footer { padding: 10px 16px; border-top: 1px solid var(--line); display: flex; gap: 8px; justify-content: flex-end; flex-wrap: wrap; } .modal footer .grow { flex: 1; display: flex; gap: 8px; } .field { display: flex; flex-direction: column; gap: 3px; } .field > label { color: var(--dim); } .field .hint { color: var(--dim); font-size: 12px; } .field.check { flex-direction: row; align-items: center; gap: 10px; } .field.check label { color: var(--fg); } .grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; } .form-error { color: var(--down); min-height: 1.4em; } .palette { background: var(--bg); border: 1px solid var(--dim); border-radius: var(--radius); width: 100%; max-width: 620px; box-shadow: 0 18px 60px rgba(0, 0, 0, 0.5); } .palette input { border: 0; border-bottom: 1px solid var(--line); border-radius: 0; padding: 10px 16px; background: transparent; } .palette ul { list-style: none; margin: 0; padding: 4px; max-height: 50vh; overflow: auto; } .palette li { display: flex; justify-content: space-between; gap: 16px; padding: 5px 12px; border-radius: var(--radius); cursor: pointer; } .palette li.on { background: var(--sel2); } .palette li .hint { color: var(--dim); } .palette .none { padding: 12px 16px; color: var(--dim); } #toasts { position: fixed; right: 16px; bottom: 44px; display: flex; flex-direction: column; gap: 8px; z-index: 80; max-width: min(420px, calc(100vw - 32px)); } .toast { background: var(--bg3); border: 1px solid var(--line); border-left: 3px solid var(--accent); padding: 8px 12px; border-radius: var(--radius); box-shadow: 0 6px 24px rgba(0, 0, 0, 0.4); animation: tin 0.16s ease-out; } .toast.err { border-left-color: var(--down); } @keyframes tin { from { transform: translateY(6px); opacity: 0; } } .section { margin-bottom: 30px; max-width: 760px; } .section h2 { font-size: 14px; margin: 0 0 8px; font-weight: 700; } .section h2::before { content: "# "; color: var(--accent); } .section p { color: var(--dim); margin: 0 0 10px; } .row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-bottom: 8px; } .row > .grow { flex: 1; min-width: 180px; } .themes { display: flex; gap: 8px; flex-wrap: wrap; } .themes button { display: flex; align-items: center; gap: 8px; } .themes .sw { width: 12px; height: 12px; border-radius: 50%; border: 1px solid var(--line); } /* ---------- small screens ---------- */ @media (max-width: 900px) { .svc { grid-template-columns: 1.5ch 1fr 8ch 8ch 150px; row-gap: 0; } .svc .addr, .svc .spark, .svc .since { display: none; } #page { padding: 14px; } header.top { padding: 0 12px; gap: 10px; } .grid2 { grid-template-columns: 1fr; } nav.tabs kbd { display: none; } .login label { flex-direction: column; align-items: stretch; gap: 3px; } .login label span { width: auto; } .svc .acts { opacity: 1; } footer.status .hide-s { display: none; } } @media (prefers-reduced-motion: reduce) { .cursor, .svc.s-down .led, .toast { animation: none; } }