:root { --bg: #111418; --bg2: #171b21; --bg3: #1e242c; --border: #2a313b; --fg: #d0d7de; --muted: #7d8590; --accent: #e3a33b; --accent2: #58a6ff; --ok: #3fb950; --bad: #ff7b72; --s1: #3987e5; --s2: #c98500; --track: rgba(57, 135, 229, .18); --btn-fg: #111; --term-bg: var(--term-bg); --font: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace; --header-h: 44px; --footer-h: 28px; --rail-w: 220px; --tabs-h: 34px; } * { box-sizing: border-box; } html, body, #root { height: 100%; margin: 0; } body { background: var(--bg); color: var(--fg); font: 14px/1.4 var(--font); overscroll-behavior: none; transition: background .2s, color .2s; } button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; } .spacer { flex: 1; } .muted { color: var(--muted); } .center { height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; } /* login */ .login { display: flex; flex-direction: column; gap: 10px; width: min(320px, 90vw); } .login-logo { width: 176px; height: 176px; align-self: center; margin-bottom: 8px; } /* the artwork brings its own shape: no circular crop */ .login input { padding: 10px 12px; background: var(--bg2); border: 1px solid var(--border); border-radius: 6px; color: var(--fg); font-size: 16px; } .login button, .primary { padding: 10px 12px; background: var(--accent); color: var(--btn-fg); border-radius: 6px; font-weight: 600; } .login button:disabled { opacity: .5; } /* the password field and its show/hide eye, which sits inside the field's right edge */ .login .pwfield { position: relative; display: flex; } .login .pwfield input { flex: 1; min-width: 0; padding-right: 44px; } .login button.pweye { position: absolute; right: 1px; top: 1px; bottom: 1px; width: 42px; padding: 0; background: none; color: var(--muted); font-weight: 400; border-radius: 0 6px 6px 0; } .login button.pweye:hover, .login button.pweye[aria-pressed="true"] { color: var(--fg); } .login button.pweye:disabled { opacity: .4; } .error { color: var(--bad); font-size: 13px; } .svc-list > .error, .cron-list > .error { margin: 8px 12px; } /* layout */ .app { height: 100%; display: flex; flex-direction: column; padding-top: env(safe-area-inset-top); } .header { height: var(--header-h); display: flex; align-items: center; gap: 12px; padding: 0 12px; background: var(--bg2); border-bottom: 1px solid var(--border); } .brand { font-weight: 700; color: var(--accent); letter-spacing: .5px; } .host { color: var(--muted); font-family: ui-monospace, monospace; font-size: 13px; } .stat { font-family: ui-monospace, monospace; font-size: 12px; color: var(--muted); } .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--muted); } .dot.ok { background: var(--ok); } .dot.bad { background: var(--bad); } .link { color: var(--muted); } .link:hover { color: var(--fg); } .icon-btn { padding: 2px 6px; color: var(--muted); border-radius: 4px; } .icon-btn:hover { color: var(--fg); background: var(--bg3); } .menu { display: none; font-size: 18px; } .body { flex: 1; display: flex; min-height: 0; position: relative; } .footer { height: var(--footer-h); display: flex; align-items: center; gap: 16px; padding: 0 12px; background: var(--bg2); border-top: 1px solid var(--border); font-size: 12px; color: var(--muted); padding-bottom: env(safe-area-inset-bottom); box-sizing: content-box; } /* rail */ .rail { width: var(--rail-w); background: var(--bg2); border-right: 1px solid var(--border); overflow-y: auto; padding: 6px 0; flex-shrink: 0; } .group-head { width: 100%; display: flex; align-items: center; gap: 8px; padding: 8px 12px; text-align: left; } .group-head:hover { background: var(--bg3); } .group.soon .group-head { color: var(--muted); cursor: default; } .group.soon .group-head:hover { background: none; } .chev { width: 10px; color: var(--muted); font-size: 11px; } .gicon { width: 18px; text-align: center; color: var(--accent2); font-family: ui-monospace, monospace; font-size: 12px; display: inline-flex; justify-content: center; align-items: center; } .badge { margin-left: auto; font-size: 10px; text-transform: uppercase; border: 1px solid var(--border); border-radius: 3px; padding: 0 4px; } .sdot { width: 7px; height: 7px; border-radius: 50%; background: var(--muted); flex-shrink: 0; } .sdot.run { background: var(--ok); } /* main + terminal */ .main { flex: 1; min-width: 0; position: relative; background: var(--term-bg); } .term { position: absolute; inset: var(--tabs-h) 0 0 0; padding: 6px 0 0 6px; } .term-tabs { position: absolute; top: 0; left: 0; right: 0; height: var(--tabs-h); display: flex; align-items: stretch; background: var(--bg2); border-bottom: 1px solid var(--border); overflow-x: auto; overflow-y: hidden; scrollbar-width: none; -webkit-overflow-scrolling: touch; } .term-tabs::-webkit-scrollbar { display: none; } .ttab { flex: 0 1 200px; min-width: 96px; display: flex; align-items: center; gap: 6px; padding: 0 4px 0 10px; cursor: pointer; border-right: 1px solid var(--border); color: var(--muted); font-size: 13px; user-select: none; } .ttab:hover { background: var(--bg3); color: var(--fg); } .ttab.active { background: var(--term-bg); color: var(--fg); box-shadow: inset 0 2px 0 var(--accent); } .ttab.exited .tname { text-decoration: line-through; } .ttab .icon-btn { padding: 0 5px; font-size: 12px; } .ttab:not(.active):not(:hover) .icon-btn { visibility: hidden; } .ttab.new { flex: 0 0 auto; min-width: 40px; justify-content: center; padding: 0 12px; border-right: 0; color: var(--accent2); font-size: 16px; position: sticky; right: 0; background: var(--bg2); box-shadow: -1px 0 0 var(--border); } .tname { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .term-inner { height: 100%; } .term .xterm { height: 100%; } .xterm-viewport::-webkit-scrollbar { width: 8px; } .xterm-viewport::-webkit-scrollbar-thumb { background: var(--border); } /* key bar (touch devices / narrow screens) */ .keybar { display: none; gap: 4px; padding: 4px 6px; background: var(--bg2); border-top: 1px solid var(--border); overflow-x: auto; white-space: nowrap; -webkit-overflow-scrolling: touch; } .keybar button { flex: 0 0 auto; padding: 8px 10px; min-width: 40px; background: var(--bg3); border: 1px solid var(--border); border-radius: 5px; font-family: ui-monospace, monospace; font-size: 13px; } .keybar button.on { background: var(--accent); color: var(--btn-fg); border-color: var(--accent); } @media (pointer: coarse), (max-width: 800px) { .keybar { display: flex; } } /* mobile: rail becomes a drawer */ .backdrop { display: none; } @media (max-width: 800px) { .menu { display: inline-block; } .host, .stat { display: none; } .rail { position: absolute; top: 0; bottom: 0; left: 0; z-index: 10; transform: translateX(-100%); transition: transform .15s ease; box-shadow: 4px 0 16px rgba(0,0,0,.5); } .drawer-open .rail { transform: none; } .drawer-open .backdrop { display: block; position: absolute; inset: 0; z-index: 9; background: rgba(0,0,0,.5); } .footer { font-size: 11px; } .ttab { flex: 0 0 auto; max-width: 70vw; } } /* dashboard */ .dash { position: absolute; inset: 0; overflow-y: auto; padding: 12px; display: flex; flex-direction: column; gap: 12px; background: var(--bg); -webkit-overflow-scrolling: touch; } .dash > * { flex-shrink: 0; } .kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 12px; } .cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 12px; align-items: start; } .tile, .card { background: var(--bg2); border: 1px solid var(--border); border-radius: 8px; padding: 12px; min-width: 0; } .tile .label, .card h2 { margin: 0 0 6px; font-size: 12px; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: .5px; } .tile .value { font-size: 22px; font-weight: 600; font-variant-numeric: tabular-nums; line-height: 1.2; } .tile .sub { font-size: 12px; color: var(--muted); margin-top: 2px; font-variant-numeric: tabular-nums; } .tile .spark { margin-top: 8px; } .card .empty { color: var(--muted); font-size: 13px; } /* the out-of-date list: image, what the registry said, which stacks, and the button */ .upd-list .row { grid-template-columns: minmax(130px, 1.3fr) minmax(110px, 1.5fr) auto auto; } .upd-list .row .btn { padding: 2px 10px; } @media (max-width: 560px) { .upd-list .row { grid-template-columns: 1fr auto; } .upd-list .row > :nth-child(2) { grid-column: 1 / -1; } } .row { display: grid; grid-template-columns: minmax(90px, 1fr) 2fr auto; gap: 10px; align-items: center; padding: 4px 0; font-size: 13px; min-width: 0; } .row .k { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .row .k small { color: var(--muted); margin-left: 6px; } .num { font-variant-numeric: tabular-nums; font-family: ui-monospace, monospace; font-size: 12px; white-space: nowrap; } .meter { height: 6px; background: var(--track); border-radius: 3px; overflow: hidden; } .meter .fill { height: 100%; background: var(--s1); border-radius: 3px; transition: width .3s; } .meter .fill.warn { background: var(--s2); } .meter .fill.bad { background: var(--bad); } .sub-card { padding: 8px 0; border-top: 1px solid var(--border); } .sub-card:first-of-type { border-top: 0; padding-top: 0; } .sub-head { display: flex; align-items: center; gap: 8px; font-size: 13px; margin-bottom: 4px; } .sub-head .spacer { flex: 1; } .sub-head .model { color: var(--muted); font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .legend { display: flex; gap: 12px; font-size: 11px; color: var(--muted); margin-top: 4px; } .sw { display: inline-block; width: 12px; height: 2px; vertical-align: middle; margin-right: 5px; border-radius: 1px; } .spark { position: relative; width: 100%; height: 44px; touch-action: pan-y; } .spark svg { display: block; width: 100%; height: 100%; } .spark .tip { position: absolute; top: 0; transform: translateX(-50%); background: var(--bg3); border: 1px solid var(--border); border-radius: 4px; padding: 2px 6px; font-size: 11px; white-space: nowrap; pointer-events: none; font-variant-numeric: tabular-nums; } .status { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; } .rail .group-head.active { background: var(--bg3); box-shadow: inset 3px 0 0 var(--accent); } @media (max-width: 800px) { .dash { padding: 8px; gap: 8px; } .kpis, .cards { gap: 8px; } .cards { grid-template-columns: 1fr; } } /* dashboard: customize mode */ .dash-bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; } .dash-bar .hint { color: var(--muted); font-size: 12px; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; } .dash-bar select.btn { padding: 3px 6px; } .btn { padding: 5px 10px; background: var(--bg3); border: 1px solid var(--border); border-radius: 5px; font-size: 12px; color: var(--fg); } .btn.on { background: var(--accent); color: var(--btn-fg); border-color: var(--accent); } .mod-tools { display: flex; gap: 4px; margin: -4px -4px 6px auto; justify-content: flex-end; } .mod-tools button { padding: 2px 7px; background: var(--bg3); border: 1px solid var(--border); border-radius: 4px; font-size: 12px; color: var(--muted); } .mod-tools button:hover:not(:disabled) { color: var(--fg); } .mod-tools button:disabled { opacity: .35; cursor: default; } .editing .tile, .editing .card { border-style: dashed; } .hide-item { padding: 0 6px; color: var(--muted); font-size: 12px; border-radius: 4px; } .hide-item:hover { color: var(--bad); background: var(--bg3); } .chips { display: flex; flex-wrap: wrap; gap: 6px; } .chip { padding: 3px 9px; background: var(--bg3); border: 1px solid var(--border); border-radius: 12px; font-size: 12px; color: var(--muted); } .chip:hover { color: var(--fg); border-color: var(--accent2); } .chips.inline { margin-top: 6px; } /* services */ .svc { position: absolute; inset: 0; display: flex; background: var(--bg); } .svc-list { width: 360px; flex-shrink: 0; display: flex; flex-direction: column; border-right: 1px solid var(--border); background: var(--bg2); min-height: 0; } .svc-tools { padding: 10px; display: flex; flex-direction: column; gap: 8px; border-bottom: 1px solid var(--border); } .svc-tools input { padding: 8px 10px; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; color: var(--fg); font-size: 14px; } .chip.on { color: var(--fg); border-color: var(--accent); } .chip.bad { color: var(--bad); } .svc-rows { flex: 1; overflow-y: auto; } .svc-row { display: flex; align-items: center; gap: 10px; padding: 8px 12px; cursor: pointer; border-bottom: 1px solid var(--border); } .svc-row:hover { background: var(--bg3); } .svc-row.active { background: var(--bg3); box-shadow: inset 3px 0 0 var(--accent); } .svc-name { flex: 1; min-width: 0; display: flex; flex-direction: column; } .svc-name .n { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .svc-name .d { font-size: 12px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .svc-state { font-size: 11px; color: var(--muted); font-family: ui-monospace, monospace; white-space: nowrap; } .sdot.ok { background: var(--ok); } .sdot.bad { background: var(--bad); } .sdot.warn { background: var(--s2); } .sdot.exited { background: var(--accent2); } .svc-empty { flex: 1; display: flex; align-items: center; justify-content: center; } .svc-detail { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 10px; padding: 12px; overflow-y: auto; } /* The pane scrolls; its parts keep their height. Left to shrink, whatever clips its own overflow (a task's output box) is squeezed to a line the moment the pane is full — on a phone, always. What is meant to give way (.logs, .stack-editor) says so itself, further down. */ .svc-detail > *, .cron-detail > * { flex-shrink: 0; } .svc-detail .back { display: none; align-self: flex-start; } /* The × that closes a detail pane on a wide screen, where the list stays beside it and there is no Back. Sits in the pane's top-right corner; the header keeps clear of it. */ .svc-detail, .cron-detail { position: relative; } .detail-close { position: absolute; top: 10px; right: 10px; z-index: 1; width: 26px; height: 26px; padding: 0; display: inline-flex; align-items: center; justify-content: center; color: var(--muted); background: none; border: 0; border-radius: 50%; opacity: .7; } .detail-close:hover { color: var(--fg); background: var(--bg3); opacity: 1; } .svc-detail > .svc-head, .cron-detail > h2 { padding-right: 36px; } /* Nothing selected: the list has the window to itself rather than a 360px column beside an empty pane. The names, descriptions and commands that get an ellipsis are what it's for. Picking something puts the list back at its own width, beside the detail. A phone shows one pane at a time and is already full width — this is only for a window with room to waste. */ @media (min-width: 801px) { .svc:not(.has-sel) .svc-list, .cron:not(.has-sel) .cron-list { width: auto; flex: 1 1 auto; border-right: 0; } .svc:not(.has-sel) .svc-empty, .cron:not(.has-sel) .svc-empty { display: none; } } /* A tab that is a page rather than a list-and-detail — Docker's Updates — stacks: the tab strip on top, the page below at full width. Otherwise the empty list column takes half the window and the page is squeezed into the other half, which looks like a phone. */ .svc.wide { flex-direction: column; } /* :not(.has-sel) to outrank the wide-window rule above, which grows the list to fill */ .svc.wide:not(.has-sel) .svc-list { width: auto; flex: 0 0 auto; border-right: 0; border-bottom: 1px solid var(--border); } .svc.wide .svc-rows { display: none; } .svc.wide > .svc-detail { flex: 1 1 auto; min-height: 0; } .svc-head { display: flex; align-items: center; gap: 10px; } .svc-head h2 { margin: 0; font-size: 18px; font-weight: 600; word-break: break-all; } .svc-actions { display: flex; flex-wrap: wrap; gap: 6px; } .props { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 4px 16px; font-size: 13px; } .props > div { display: flex; gap: 8px; min-width: 0; } .props .k { color: var(--muted); width: 80px; flex-shrink: 0; } .props span:not(.k) { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .mono { font-family: ui-monospace, monospace; font-size: 12px; } /* One property per row, label above its value. For panes like Inspect, whose values are long paths and lists — side by side they were half truncated and read as columns. */ .props.stacked { grid-template-columns: 1fr; gap: 10px 0; } .props.stacked > div { flex-direction: column; gap: 2px; } .props.stacked .k { width: auto; font-size: 11px; letter-spacing: .04em; text-transform: uppercase; } .props.stacked span:not(.k) { overflow: visible; white-space: normal; overflow-wrap: anywhere; } .props.stacked .k { text-transform: none; } .props.stacked > div > .k { text-transform: uppercase; } .props.stacked .k .muted { text-transform: none; letter-spacing: 0; } .props.stacked span:not(.k) > div + div { margin-top: 3px; } .tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--border); } .tabs button { padding: 6px 12px; color: var(--muted); border-bottom: 2px solid transparent; margin-bottom: -1px; } .tabs button.on { color: var(--fg); border-bottom-color: var(--accent); } .unit-file { margin: 0; padding: 10px; background: var(--term-bg); border: 1px solid var(--border); border-radius: 6px; font: 12px/1.5 ui-monospace, monospace; white-space: pre-wrap; word-break: break-all; } .logs { flex: 1; min-height: 240px; display: flex; flex-direction: column; border: 1px solid var(--border); border-radius: 6px; background: var(--term-bg); overflow: hidden; } .logs-bar { display: flex; align-items: center; gap: 6px; padding: 6px 8px; border-bottom: 1px solid var(--border); font-size: 12px; background: var(--bg2); } .log-lines { flex: 1; overflow: auto; padding: 6px 8px; font: 12px/1.45 ui-monospace, SFMono-Regular, Menlo, monospace; } .log { display: flex; gap: 8px; white-space: pre-wrap; word-break: break-word; } .log .lt { color: var(--muted); flex-shrink: 0; } .log .li { color: var(--accent2); flex-shrink: 0; } /* a phone has no room for three columns: the stamp and the unit go on one line, the message underneath */ @media (max-width: 560px) { .log { flex-wrap: wrap; gap: 0 8px; padding: 2px 0; border-bottom: 1px solid var(--border); } .log .lm { flex-basis: 100%; } .log .lt { font-size: 11px; } } .log.err .lm { color: var(--bad); } .log.warn .lm { color: var(--s2); } .log.debug .lm { color: var(--muted); } @media (max-width: 800px) { .svc-list { width: 100%; border-right: 0; } .has-sel .svc-list { display: none; } .svc-empty { display: none; } .svc-detail .back { display: inline-block; } .props { grid-template-columns: 1fr; } .detail-close { display: none; } } /* cron */ .cron { position: absolute; inset: 0; display: flex; background: var(--bg); } .cron-list { width: 420px; flex-shrink: 0; display: flex; flex-direction: column; border-right: 1px solid var(--border); background: var(--bg2); min-height: 0; } .cron-rows { flex: 1; overflow-y: auto; } .cron-rows h3 { margin: 0; padding: 8px 12px 4px; font-size: 11px; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: .5px; background: var(--bg2); position: sticky; top: 0; } .cron-row { display: grid; grid-template-columns: 8px 1fr auto; gap: 4px 10px; align-items: center; padding: 8px 12px; cursor: pointer; border-bottom: 1px solid var(--border); } .cron-row:hover { background: var(--bg3); } .cron-row.active { background: var(--bg3); box-shadow: inset 3px 0 0 var(--accent); } .cron-row .cmd { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: ui-monospace, monospace; font-size: 12px; } .cron-row .meta { grid-column: 2 / 4; font-size: 11px; color: var(--muted); display: flex; gap: 8px; flex-wrap: wrap; } .cron-row .when { font-size: 11px; color: var(--muted); white-space: nowrap; font-variant-numeric: tabular-nums; } .cron-row.off .cmd { color: var(--muted); text-decoration: line-through; } .cron-detail { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 10px; padding: 12px; overflow-y: auto; } .cron-detail .back { display: none; align-self: flex-start; } .cron-detail h2 { margin: 0; font-size: 15px; font-weight: 600; font-family: ui-monospace, monospace; word-break: break-all; white-space: pre-wrap; } .notice { padding: 8px 10px; border: 1px solid var(--border); border-radius: 6px; font-size: 12px; color: var(--muted); background: var(--bg2); } .editor { width: 100%; min-height: 260px; padding: 10px; background: var(--term-bg); color: var(--fg); border: 1px solid var(--border); border-radius: 6px; font: 12px/1.5 ui-monospace, monospace; resize: vertical; } .run-out { margin: 0; padding: 10px; background: var(--term-bg); border: 1px solid var(--border); border-radius: 6px; font: 12px/1.5 ui-monospace, monospace; white-space: pre-wrap; word-break: break-word; max-height: 50vh; overflow: auto; } @media (max-width: 800px) { .cron-list { width: 100%; border-right: 0; } .has-sel .cron-list { display: none; } .cron-detail .back { display: inline-block; } } /* updates */ .updates .pkgs { padding: 0; } /* the package list is flush to the card's edge, but a one-line "nothing to do" in the same card should sit in it like every other empty state, not as a 20px sliver */ .updates .pkgs:has(> .empty:only-child) { padding: 12px; } .pkg { display: grid; grid-template-columns: auto 1fr auto; gap: 10px; align-items: center; padding: 8px 12px; border-bottom: 1px solid var(--border); } .pkg.sel { background: var(--bg3); } .pkg input { width: 16px; height: 16px; } .pkg-name { min-width: 0; display: flex; flex-direction: column; cursor: pointer; } .pkg-name .n { font-family: ui-monospace, monospace; font-size: 13px; } .pkg-name .d { font-size: 12px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .pkg-ver { font-family: ui-monospace, monospace; font-size: 12px; white-space: nowrap; } .badge.bad { color: var(--bad); border-color: var(--bad); margin-left: 8px; } .changelog { margin: 0 12px 8px; max-height: 40vh; } .notice.attn { border-color: var(--s2); color: var(--fg); } .task { display: flex; flex-direction: column; border: 1px solid var(--border); border-radius: 6px; overflow: hidden; } .task-out { border: 0; border-radius: 0; max-height: 45vh; min-height: 120px; } .task.failed { border-color: var(--bad); } .task .badge { margin-left: 0; flex-shrink: 0; } .badge.ok { color: var(--ok); border-color: var(--ok); } @media (max-width: 800px) { .pkg { grid-template-columns: auto 1fr; } .pkg-ver { grid-column: 2; white-space: normal; } } /* power + modal */ .power { font-size: 15px; } .power.attn { color: var(--s2); } /* The backdrop keeps clear of the phone's status bar and home indicator; a dialog is never taller than what is left, and scrolls inside itself. Its parts keep their height: in a column that may shrink, anything with its own scrollbar (a preview, a list) is squeezed to a sliver instead, and the dialog never scrolls at all. */ .modal-back { position: fixed; inset: 0; z-index: 50; background: rgba(0,0,0,.6); display: flex; align-items: center; justify-content: center; padding: max(16px, env(safe-area-inset-top)) max(16px, env(safe-area-inset-right)) max(16px, env(safe-area-inset-bottom)) max(16px, env(safe-area-inset-left)); } .modal { --modal-bg: var(--bg2); width: min(420px, 100%); max-height: 100%; overflow-y: auto; overscroll-behavior: contain; background: var(--modal-bg); border: 1px solid var(--border); border-radius: 10px; padding: 16px; display: flex; flex-direction: column; gap: 12px; } .modal > * { flex-shrink: 0; } /* a long dialog's buttons (and what went wrong) stay in reach while the rest scrolls */ .modal:has(> .modal-foot) { padding-bottom: 0; } .modal-foot { position: sticky; bottom: 0; z-index: 1; margin: 0 -16px; padding: 12px 16px 16px; background: var(--modal-bg); border-top: 1px solid var(--border); display: flex; flex-direction: column; gap: 10px; } .modal-foot .error { max-height: 30vh; overflow-y: auto; } .modal h3 { margin: 0; font-size: 15px; } .modal label { display: flex; flex-direction: column; gap: 6px; font-size: 12px; } .modal input { padding: 9px 10px; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; color: var(--fg); font-size: 16px; } .btn.danger { color: var(--bad); border-color: var(--bad); } .btn.danger:disabled { opacity: .4; } /* feedback: spinners, buttons, toasts, activity bar */ .spin { display: inline-block; border: 2px solid rgba(128,128,128,.35); border-top-color: currentColor; border-radius: 50%; animation: spin .7s linear infinite; vertical-align: -2px; margin-right: 6px; flex-shrink: 0; box-sizing: border-box; } @keyframes spin { to { transform: rotate(360deg); } } .btn, .primary, .login button { display: inline-flex; align-items: center; justify-content: center; } .btn.busy, .primary.busy { opacity: .85; cursor: progress; } .loading { gap: 10px; padding: 24px; height: auto; min-height: 120px; } .loading .spin { margin: 0; } .tools-row { display: flex; gap: 6px; } .tools-row input { flex: 1; min-width: 0; } .toasts { position: fixed; left: 50%; bottom: calc(var(--footer-h) + 16px + env(safe-area-inset-bottom)); transform: translateX(-50%); z-index: 60; display: flex; flex-direction: column; gap: 6px; align-items: center; pointer-events: none; } .toast { pointer-events: auto; padding: 8px 14px; border-radius: 8px; background: var(--bg3); border: 1px solid var(--border); font-size: 13px; box-shadow: 0 4px 16px rgba(0,0,0,.45); max-width: 90vw; cursor: pointer; animation: toast-in .15s ease-out; } .toast.ok { border-color: var(--ok); } .toast.err { border-color: var(--bad); color: var(--bad); } @keyframes toast-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } } .netbar { position: fixed; top: 0; left: 0; right: 0; height: 2px; z-index: 70; background: var(--accent2); opacity: 0; transition: opacity .2s .3s; pointer-events: none; } .netbar.on { opacity: 1; animation: netbar 1.1s ease-in-out infinite; } @keyframes netbar { 0% { transform: scaleX(0); transform-origin: left; } 50% { transform: scaleX(1); transform-origin: left; } 51% { transform-origin: right; } 100% { transform: scaleX(0); transform-origin: right; } } .term-overlay { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; background: rgba(13,17,23,.55); color: var(--muted); z-index: 2; gap: 4px; font-size: 13px; } .saved { font-size: 11px; color: var(--muted); display: inline-flex; align-items: center; } .task.running .logs-bar { background: rgba(57,135,229,.12); } .task.failed .logs-bar { background: rgba(255,123,114,.12); } .task.done .logs-bar { background: rgba(63,185,80,.10); } /* disks */ h2.section { margin: 0; font-size: 13px; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: .5px; } .disk .dname, .card .dname { font-weight: 600; } .badge { font-size: 10px; text-transform: uppercase; border: 1px solid var(--border); border-radius: 3px; padding: 0 4px; color: var(--muted); } .good { color: var(--ok); } .warn-text { color: var(--s2); } .smart { display: flex; flex-direction: column; gap: 8px; padding-top: 8px; border-top: 1px solid var(--border); } .attrs { width: 100%; border-collapse: collapse; font-size: 12px; } .attrs th { text-align: left; color: var(--muted); font-weight: 500; padding: 4px 4px; border-bottom: 1px solid var(--border); } .attrs td { padding: 3px 4px; border-bottom: 1px solid var(--border); } .attrs td.num, .attrs th:not(:nth-child(2)) { text-align: right; } .attrs tr.warn td { color: var(--s2); } /* A SMART raw value is not always a number — a temperature reads "36 (Min/Max 28/39)" — and a table cannot be narrower than its widest unbreakable cell, so one of those pushed the whole table out through the side of its card. The raw column wraps, but only where there is a space: a number broken across two lines reads as two numbers. If some drive ever reports one long enough to still not fit, the table scrolls inside its card rather than out of it. */ .attrs td.raw { white-space: normal; } .attrs-wrap { overflow-x: auto; scrollbar-width: thin; } /* Six columns of SMART want about 350px between them; a disk card is given room for that, so the fallback above stays a fallback. */ .disk-cards { grid-template-columns: repeat(auto-fill, minmax(min(420px, 100%), 1fr)); } .datasets { padding: 0; overflow-x: auto; } .ds-row { display: grid; grid-template-columns: minmax(160px, 1fr) 80px 90px 90px 90px minmax(120px, 1fr); gap: 8px; align-items: center; padding: 6px 12px; border-bottom: 1px solid var(--border); font-size: 13px; min-width: 700px; } .ds-row.head { color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: .5px; } .ds-row .num { text-align: right; } @media (max-width: 800px) { .ds-row { grid-template-columns: minmax(90px, 1fr) 84px 100px; min-width: 0; gap: 6px; padding: 6px 8px; } .ds-row > :nth-child(3), .ds-row > :nth-child(5), .ds-row > :nth-child(6) { display: none; } .ds-row .ds-name, .ds-row.head > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .ds-row.head { letter-spacing: 0; } } .ds-name { cursor: pointer; display: flex; align-items: center; gap: 6px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .ds-mount { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: ui-monospace, monospace; font-size: 12px; } .ds-snaps { padding: 8px 12px 10px 40px; border-bottom: 1px solid var(--border); background: var(--bg); display: flex; flex-direction: column; gap: 6px; } .snap-list { display: flex; flex-direction: column; gap: 2px; max-height: 40vh; overflow-y: auto; } .snap { display: grid; grid-template-columns: minmax(150px, 1fr) 150px 120px 70px auto; gap: 10px; align-items: center; font-size: 12px; padding: 2px 0; border-bottom: 1px solid var(--border); } .snap .snap-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .snap .snap-when, .snap .snap-exp { white-space: nowrap; } .snap .snap-exp.warn { color: var(--s2); } .snap .num { text-align: right; } .snap .snap-del { padding: 0 6px; font-size: 11px; opacity: .55; } .snap:hover .snap-del { opacity: 1; } @media (max-width: 800px) { .snap { grid-template-columns: minmax(120px, 1fr) auto auto; } .snap .snap-when, .snap .snap-exp { display: none; } } /* files */ .files { position: absolute; inset: 0; display: flex; flex-direction: column; background: var(--bg); } .files.drag { outline: 2px dashed var(--accent2); outline-offset: -4px; } .files-bar { display: flex; flex-direction: column; gap: 6px; padding: 8px 10px; background: var(--bg2); border-bottom: 1px solid var(--border); } .crumbs { display: flex; align-items: center; gap: 2px; flex-wrap: wrap; } .crumb { padding: 3px 6px; border-radius: 4px; font-family: ui-monospace, monospace; font-size: 13px; color: var(--accent2); } .crumb:hover { background: var(--bg3); } .crumb + .crumb::before { content: '/'; color: var(--muted); margin-right: 4px; } .path-input { flex: 1; min-width: 160px; margin-left: 8px; padding: 4px 8px; background: var(--bg); border: 1px solid var(--border); border-radius: 5px; color: var(--muted); font: 12px ui-monospace, monospace; } .path-input:focus { color: var(--fg); border-color: var(--accent2); outline: none; } .files-tools { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; } .files-tools .filter { width: 110px; padding: 5px 8px; background: var(--bg); border: 1px solid var(--border); border-radius: 5px; color: var(--fg); font-size: 13px; } .file-list { flex: 1; overflow-y: auto; } .file-row { display: grid; grid-template-columns: 28px 1fr 80px 80px 130px 32px; gap: 8px; align-items: center; padding: 7px 10px; border-bottom: 1px solid var(--border); cursor: pointer; position: relative; } .file-row:hover { background: var(--bg3); } .file-row.up { color: var(--muted); } .file-row.broken .fname { color: var(--bad); } .ficon { text-align: center; font-size: 15px; } .fname { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .fmeta { color: var(--muted); text-align: right; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .more { justify-self: end; } .ctx { position: absolute; right: 36px; top: 4px; z-index: 5; background: var(--bg2); border: 1px solid var(--border); border-radius: 8px; box-shadow: 0 6px 20px rgba(0,0,0,.5); display: flex; flex-direction: column; min-width: 180px; overflow: hidden; } .ctx button, .ctx a { padding: 9px 14px; text-align: left; color: var(--fg); text-decoration: none; font-size: 13px; } .ctx button:hover, .ctx a:hover { background: var(--bg3); } .ctx .danger { color: var(--bad); } .upload-bar { padding: 6px 12px; font-size: 12px; color: var(--muted); border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 8px; } .upload-bar .meter { flex: 1; } .drop-hint { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; background: rgba(17,20,24,.7); color: var(--fg); font-size: 16px; pointer-events: none; } .viewer { position: absolute; inset: 0; display: flex; flex-direction: column; } .viewer-bar { display: flex; align-items: center; gap: 8px; padding: 8px 10px; background: var(--bg2); border-bottom: 1px solid var(--border); flex-wrap: wrap; } .vname { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 50vw; } .dirty { color: var(--accent); } .file-editor { flex: 1; min-height: 0; border: 0; border-radius: 0; resize: none; font-size: 13px; } .preview { flex: 1; display: flex; align-items: center; justify-content: center; overflow: auto; padding: 12px; } .preview img { max-width: 100%; max-height: 100%; object-fit: contain; } @media (max-width: 800px) { .file-row { grid-template-columns: 24px 1fr 70px 32px; } .file-row .fmeta:nth-child(4), .file-row .perm { display: none; } .path-input { width: 100%; margin: 4px 0 0; } .files-tools .filter { width: 80px; } } /* processes tab — btop-style boxes */ .procs-page { position: absolute; inset: 0; display: flex; flex-direction: column; background: var(--bg); overflow-y: auto; -webkit-overflow-scrolling: touch; } .procs-page > * { flex-shrink: 0; } .procs-top { padding: 14px 10px 8px; display: grid; grid-template-columns: 1.4fr 1fr; gap: 12px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; } .procs-top .netbox { grid-column: 1 / -1; } .box { position: relative; border: 1px solid var(--border); border-radius: 8px; padding: 12px 12px 8px; min-width: 0; } .box-title { position: absolute; top: -8px; left: 10px; padding: 0 6px; background: var(--bg); color: var(--accent2); font-weight: 700; font-size: 12px; letter-spacing: .5px; } .box-right { position: absolute; top: -8px; right: 10px; padding: 0 6px; background: var(--bg); color: var(--muted); font-size: 11px; max-width: 70%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .box-sub { margin: 8px 0 2px; color: var(--accent2); font-size: 11px; } .box-foot { display: flex; gap: 14px; flex-wrap: wrap; margin-top: 8px; font-size: 11px; } .box-foot b { color: var(--fg); font-weight: 600; } .small { font-size: 11px; } .brow { display: grid; grid-template-columns: 64px 1fr auto; gap: 8px; align-items: center; padding: 2px 0; } .bl { color: var(--muted); } .bv { font-variant-numeric: tabular-nums; white-space: nowrap; } .bv.big { font-size: 20px; font-weight: 700; color: var(--fg); } .gbar { position: relative; height: 10px; border-radius: 3px; overflow: hidden; background: linear-gradient(90deg, #3fb950 0%, #9ccc3f 40%, #d29922 65%, #f0883e 82%, #ff7b72 100%); } .gcover { position: absolute; top: 0; right: 0; bottom: 0; background: var(--bg3); } .graph { display: flex; line-height: 1; font-size: 22px; letter-spacing: -1px; overflow: hidden; white-space: nowrap; } .graph span { flex: 1 1 0; min-width: 0; text-align: center; } .graph .gpad { flex: 0 0 auto; color: var(--bg3); } .graph .g1 { color: var(--ok); } .graph .g2 { color: var(--s2); } .graph .g3 { color: var(--bad); } .cpu-total { display: grid; grid-template-columns: 1fr auto; gap: 12px; align-items: end; margin-bottom: 8px; } .cpu-total-v { display: flex; flex-direction: column; align-items: flex-end; } .cores { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 0 16px; } .cores .brow { grid-template-columns: 30px 1fr 42px; } .cores .bv { text-align: right; } .netrow { display: grid; grid-template-columns: 90px auto 1fr; gap: 12px; align-items: center; padding: 2px 0; } .netrow .dn { color: var(--accent2); } .netrow .upc { color: var(--s2); } .procs-sticky { position: sticky; top: 0; z-index: 2; background: var(--bg); } .procs-tools { display: flex; gap: 6px; align-items: center; padding: 8px 10px; flex-wrap: wrap; } .procs-tools input { flex: 1; min-width: 140px; padding: 6px 8px; background: var(--bg2); border: 1px solid var(--border); border-radius: 5px; color: var(--fg); font-size: 13px; } .procs-tools label { display: inline-flex; align-items: center; gap: 4px; } .procs-tools select.btn { padding: 3px 6px; } .ptable { font-size: 13px; min-height: 50vh; } .prow { display: grid; grid-template-columns: 60px 80px minmax(120px, 1fr) 60px 76px 70px 64px; gap: 8px; align-items: center; padding: 4px 10px; border-bottom: 1px solid var(--border); cursor: pointer; } .prow:hover { background: var(--bg3); } .prow.active { background: var(--bg3); box-shadow: inset 3px 0 0 var(--accent); } .prow.zombie { color: var(--muted); font-style: italic; } .prow.running .mono { color: var(--ok); } .prow .num { text-align: right; font-variant-numeric: tabular-nums; font-family: ui-monospace, monospace; font-size: 12px; } .prow .hot { color: var(--s2); } .prow .ell { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .phead { background: var(--bg2); cursor: default; padding: 0 10px; border-top: 1px solid var(--border); } .th { padding: 6px 0; text-align: left; color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: .5px; } .th.num { text-align: right; } .th.on { color: var(--fg); } .pdetail { position: sticky; bottom: 0; z-index: 3; border-top: 1px solid var(--border); background: var(--bg2); padding: 10px; display: flex; flex-direction: column; gap: 8px; box-shadow: 0 -6px 16px rgba(0,0,0,.4); } .cmdline { font-size: 12px; word-break: break-all; white-space: pre-wrap; color: var(--fg); background: var(--term-bg); padding: 8px; border-radius: 6px; border: 1px solid var(--border); max-height: 120px; overflow: auto; } @media (max-width: 800px) { .procs-top { grid-template-columns: 1fr; gap: 14px; padding-top: 16px; } .cmdline { max-height: 72px; } .prow { grid-template-columns: 52px minmax(100px, 1fr) 56px 70px; } .prow > :nth-child(2), .prow > :nth-child(6), .prow > :nth-child(7) { display: none; } .cores { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); } .graph { font-size: 16px; } .netrow { grid-template-columns: 70px 1fr; } .netrow > :nth-child(3) { display: none; } } /* machine switcher + machines page */ .mswitch { position: relative; padding: 4px 8px 8px; border-bottom: 1px solid var(--border); margin-bottom: 4px; } .mswitch-btn { width: 100%; display: flex; align-items: center; gap: 8px; padding: 8px 10px; background: var(--bg3); border: 1px solid var(--border); border-radius: 8px; text-align: left; } .mswitch-btn .mname { font-weight: 700; color: var(--accent); } .mswitch-btn .chev { margin-left: auto; color: var(--muted); } .mmenu { position: absolute; left: 8px; right: 8px; top: calc(100% - 4px); z-index: 20; background: var(--bg2); border: 1px solid var(--border); border-radius: 8px; box-shadow: 0 8px 24px rgba(0,0,0,.5); overflow: hidden; } .mitem { width: 100%; display: flex; align-items: center; gap: 8px; padding: 9px 12px; text-align: left; } .mitem:hover { background: var(--bg3); } .mitem.on { box-shadow: inset 3px 0 0 var(--accent); } .mitem .mname { flex: 1; } .mitem.manage { color: var(--accent2); } /* VPN tab: the profile handed over once, with its QR beside the file itself. */ .vpn-profile .vpn-qr { display: flex; gap: 14px; align-items: flex-start; flex-wrap: wrap; } .vpn-profile .vpn-qr img { background: #fff; border-radius: 6px; padding: 6px; flex: none; } .vpn-profile .vpn-conf { margin: 6px 0 0; padding: 8px 10px; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; font-size: 11px; line-height: 1.5; white-space: pre-wrap; word-break: break-all; user-select: all; } .vpn-peer .sub-head .btn { padding: 2px 8px; } .addform label.row-check { flex-direction: row; align-items: center; gap: 8px; } .addform label.row-check input { width: auto; } /* Network tab: the speed test's four figures, large enough to read at arm's length, and the bar that fills while one is running. */ .net .speedgrid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; } @media (max-width: 560px) { .net .speedgrid { grid-template-columns: repeat(2, 1fr); } } .net .speedfig { min-width: 0; } .net .speedfig .label { font-size: 11px; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: .5px; } .net .speedfig .big { font-size: 28px; font-weight: 600; line-height: 1.15; font-variant-numeric: tabular-nums; } .net .speedfig .big i { font-size: 12px; font-weight: 400; font-style: normal; color: var(--muted); margin-left: 4px; } .net .speedfig.live .big { color: var(--accent); } .net .speedbar { display: flex; flex-direction: column; gap: 4px; margin-top: 10px; } .net .card.speedtest .svc-actions { margin-top: 10px; } .net .card.speedtest .method { margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--border); } .net .card.iface { margin-top: 8px; } .net .card + .dash-bar { margin-top: 14px; } .addform { display: flex; flex-direction: column; gap: 10px; } .addform label { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--muted); } .addform input, .addform select { padding: 8px 10px; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; color: var(--fg); font-size: 15px; } .header .badge.bad { color: var(--bad); border-color: var(--bad); } /* a guest login says so, and how long it has, where the power button would be */ .badge.guest { color: var(--accent2); border-color: var(--accent2); text-transform: none; margin-left: 0; white-space: nowrap; } .card.fresh { border-color: var(--accent2); } .card.fresh .mono { user-select: all; } /* settings + themes */ .themes { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 12px; } .theme-card { text-align: left; background: var(--bg2); border: 1px solid var(--border); border-radius: 10px; padding: 10px; display: flex; flex-direction: column; gap: 6px; } .theme-card:hover { border-color: var(--accent2); } .theme-card.on { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); } .theme-name { font-weight: 600; display: flex; align-items: center; gap: 8px; } .tprev { border: 1px solid; border-radius: 6px; overflow: hidden; font-size: 9px; height: 96px; display: flex; flex-direction: column; } .tprev-head { display: flex; align-items: center; justify-content: space-between; padding: 4px 8px; border-bottom: 1px solid; font-weight: 700; } .tprev-dot { width: 6px; height: 6px; border-radius: 50%; } .tprev-body { flex: 1; display: flex; } .tprev-rail { width: 34px; border-right: 1px solid; display: flex; flex-direction: column; gap: 4px; padding: 6px 4px; } .tprev-rail span { display: block; height: 7px; border-radius: 2px; background: transparent; } .tprev-main { flex: 1; padding: 6px; display: flex; flex-direction: column; gap: 6px; } .tprev-tile { border: 1px solid; border-radius: 4px; padding: 4px 6px; display: flex; flex-direction: column; gap: 2px; } .tprev-tile b { font-size: 12px; } .tprev-bar { height: 4px; border-radius: 2px; overflow: hidden; } .tprev-bar div { height: 100%; } .tprev-term { flex: 1; border-radius: 4px; padding: 4px 6px; font-family: var(--mono); } /* per-theme flourishes */ [data-theme="cyberpunk"] .brand { text-shadow: 0 0 8px rgba(255, 43, 214, .7), 0 0 18px rgba(255, 43, 214, .35); } [data-theme="cyberpunk"] .gicon, [data-theme="cyberpunk"] .box-title { text-shadow: 0 0 6px rgba(0, 229, 255, .6); } [data-theme="cyberpunk"] .group-head.active { box-shadow: inset 3px 0 0 var(--accent), 0 0 12px rgba(255, 43, 214, .25); } [data-theme="retro"] body, [data-theme="retro"] .btn, [data-theme="retro"] input, [data-theme="retro"] select { font-family: var(--mono); } [data-theme="retro"] .brand { text-shadow: 0 0 6px rgba(255, 176, 0, .6); letter-spacing: 1px; } [data-theme="retro"] .app::after { content: ''; position: fixed; inset: 0; pointer-events: none; z-index: 100; background: repeating-linear-gradient(0deg, rgba(0,0,0,0) 0 2px, rgba(0,0,0,.12) 2px 3px); } [data-theme="retro"] .tile, [data-theme="retro"] .card, [data-theme="retro"] .box { border-radius: 2px; } [data-theme="light"] .term-overlay { background: rgba(255,255,255,.6); } [data-theme="light"] .toast { box-shadow: 0 4px 16px rgba(0,0,0,.15); } [data-theme="light"] .modal-back { background: rgba(0,0,0,.35); } .disk-summary { display: flex; flex-wrap: wrap; gap: 4px 14px; margin: 2px 0 6px; } /* touch: every control at least 36px tall (bug 22) */ @media (pointer: coarse) { .btn, .chip, .icon-btn, .link, .th, .mod-tools button, .hide-item, .crumb, .files-tools select, .procs-tools select, .dash-bar select { min-height: 36px; padding-top: 6px; padding-bottom: 6px; } .icon-btn { min-width: 36px; } .path-input, .filter, .procs-tools input, .files-tools input { min-height: 36px; } .crumb { min-width: 32px; } :root { --tabs-h: 40px; } .ttab .icon-btn { min-width: 30px; } .ttab:not(.active) .icon-btn { display: none; } .header .link, .header .icon-btn { min-height: 36px; display: inline-flex; align-items: center; } .mod-tools { margin-top: 0; } } /* sensor widgets */ .gauge-svg { width: 100%; max-width: 180px; display: block; margin: 0 auto; } .w-gauge-v { fill: var(--fg); font-size: 13px; font-weight: 600; font-variant-numeric: tabular-nums; } .tile .value.warn, .w-row .num.warn { color: var(--s2); } .tile .value.bad, .w-row .num.bad { color: var(--bad); } .tile .meter { margin-top: 8px; } .tile.w-list { grid-column: span 2; } .w-list { display: flex; flex-direction: column; gap: 6px; } .w-row { display: grid; grid-template-columns: 1fr auto; gap: 2px 10px; font-size: 13px; align-items: baseline; } .w-row .k { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .w-row .meter { grid-column: 1 / 3; margin-top: 0; } .tile.add-widget { border-style: dashed; color: var(--accent2); min-height: 72px; display: flex; align-items: center; justify-content: center; cursor: pointer; } .modal.picker { width: min(520px, 100%); } .picker-list { overflow-y: auto; max-height: 34vh; border: 1px solid var(--border); border-radius: 6px; background: var(--bg); } .picker-row { display: flex; justify-content: space-between; gap: 10px; width: 100%; padding: 7px 10px; text-align: left; font-size: 12px; border-bottom: 1px solid var(--border); } .picker-row:hover, .picker-row.active { background: var(--bg3); } .picker-row.active { box-shadow: inset 3px 0 0 var(--accent); } @media (max-width: 800px) { .tile.w-list { grid-column: 1 / -1; } } /* a widget alone in an iframe */ .embed { min-height: 100vh; padding: 10px; background: var(--bg2); box-sizing: border-box; display: flex; flex-direction: column; justify-content: center; } .embed.bare { background: transparent; } .embed .tile { border: 0; padding: 0; background: none; } /* widget gallery */ .gallery { overflow-x: hidden; } .gallery section { min-width: 0; display: flex; flex-direction: column; gap: 12px; } .gallery h2.section { margin: 10px 0 0; font-size: 13px; } .gallery-tile { min-width: 0; max-width: 100%; overflow: hidden; } .gallery-tile .label, .kpis.widgets .tile .label { overflow-wrap: anywhere; } /* container names run long */ .gallery .kpis { grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); } .gallery-tile { text-align: left; cursor: pointer; display: flex; flex-direction: column; color: inherit; font: inherit; } .gallery-tile:hover { border-color: var(--accent2); } .gallery-tile .value { font-size: 18px; overflow-wrap: anywhere; } .gallery-id { margin-top: auto; padding-top: 8px; font-size: 10.5px; color: var(--muted); overflow-wrap: anywhere; } @media (max-width: 800px) { .gallery .kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); } } a.btn { text-decoration: none; display: inline-flex; align-items: center; } /* in-app dialogs, gallery search */ /* a question is asked by something, often another dialog: it goes on top of whatever asked */ .modal-back.ask-back { z-index: 70; } .modal.ask { width: min(440px, 100%); } .ask-msg { white-space: pre-wrap; overflow-wrap: anywhere; font-size: 14px; } .gallery-find { flex: 1 1 200px; max-width: 340px; padding: 7px 10px; background: var(--bg2); border: 1px solid var(--border); border-radius: 6px; color: var(--fg); font-size: 14px; } .gallery .dash-bar { flex-wrap: wrap; gap: 8px; } .ttab.drop { box-shadow: inset 3px 0 0 var(--accent2); } .ttab-move { display: none; } .ttab-move .icon-btn:disabled { opacity: .3; } @media (pointer: coarse) { .ttab-move { display: inline-flex; } } /* no drag and drop on a touch screen */ /* each machine's colour */ .header { box-shadow: inset 0 3px 0 var(--machine, var(--accent)); } .mswitch-btn .mname, .header .host { color: var(--machine, var(--accent)); } .swatches { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin-top: 8px; } .swatch { width: 26px; height: 26px; border-radius: 50%; border: 2px solid transparent; cursor: pointer; padding: 0; } .swatch.on { border-color: var(--fg); box-shadow: 0 0 0 2px var(--bg2) inset; } .swatches input[type=color] { width: 30px; height: 30px; padding: 0; border: 1px solid var(--border); border-radius: 50%; background: none; cursor: pointer; } .themes { grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); } /* HUD: light drawn on glass — hairlines, no fills, thin type */ [data-theme="hud"] .tile, [data-theme="hud"] .card, [data-theme="hud"] .box { background: rgba(53, 214, 255, .03); border-color: rgba(53, 214, 255, .28); border-radius: 2px; box-shadow: 0 0 0 1px rgba(53, 214, 255, .05), inset 0 0 24px rgba(53, 214, 255, .04); } /* anything that floats over the page is solid: glass on glass is two layers of text */ [data-theme="hud"] .modal { --modal-bg: #061019; border-color: rgba(53, 214, 255, .45); border-radius: 2px; box-shadow: 0 0 0 1px rgba(53, 214, 255, .08), 0 0 40px rgba(53, 214, 255, .12), inset 0 0 24px rgba(53, 214, 255, .04); } [data-theme="hud"] .modal-back { background: rgba(0, 3, 6, .82); } [data-theme="hud"] .tile::before, [data-theme="hud"] .card::before { content: ''; position: absolute; top: -1px; left: -1px; width: 12px; height: 12px; border-top: 2px solid var(--accent); border-left: 2px solid var(--accent); } [data-theme="hud"] .tile, [data-theme="hud"] .card { position: relative; } [data-theme="hud"] body { font-weight: 300; letter-spacing: .2px; } [data-theme="hud"] .tile .value, [data-theme="hud"] .brand { font-weight: 300; letter-spacing: 1.5px; text-shadow: 0 0 10px rgba(53, 214, 255, .55); } [data-theme="hud"] .tile .label, [data-theme="hud"] .card h2, [data-theme="hud"] h2.section { letter-spacing: 2px; color: var(--accent); opacity: .8; } [data-theme="hud"] .meter { height: 3px; border-radius: 0; } [data-theme="hud"] .meter .fill { border-radius: 0; box-shadow: 0 0 8px var(--s1); } [data-theme="hud"] .rail, [data-theme="hud"] .header, [data-theme="hud"] .footer { background: rgba(3, 7, 12, .92); border-color: rgba(53, 214, 255, .22); } @media (max-width: 800px) { [data-theme="hud"] .rail { background: #03070c; } } [data-theme="hud"] .group-head.active { background: linear-gradient(90deg, rgba(53, 214, 255, .16), transparent); box-shadow: inset 2px 0 0 var(--accent); } [data-theme="hud"] .btn, [data-theme="hud"] .chip { background: transparent; border-color: rgba(53, 214, 255, .35); border-radius: 2px; } [data-theme="hud"] .btn.on, [data-theme="hud"] .chip.on { background: rgba(53, 214, 255, .16); color: var(--accent2); border-color: var(--accent); } [data-theme="hud"] .app { background: radial-gradient(ellipse at 50% -20%, rgba(53, 214, 255, .10), transparent 60%), var(--bg); } [data-theme="hud"] .gauge-svg path:first-child { stroke-width: 3; } [data-theme="hud"] .gauge-svg path:nth-child(2) { stroke-width: 5; filter: drop-shadow(0 0 4px var(--s1)); } /* alerts */ .card.all-clear { padding: 16px; } .card.firing { border-color: var(--s2); } .card.firing.bad { border-color: var(--bad); box-shadow: 0 0 0 1px var(--bad) inset; } .rule { display: flex; align-items: center; gap: 10px; padding: 9px 0; border-top: 1px solid var(--border); } .rule:first-child { border-top: 0; padding-top: 0; } .rule.off .rule-body { opacity: .5; } .rule-body { flex: 1; min-width: 0; cursor: pointer; } .rule-name { display: flex; align-items: center; gap: 8px; } .rule-body .mono { overflow-wrap: anywhere; margin-top: 2px; } .toggle { width: 38px; height: 22px; border-radius: 11px; background: var(--bg3); border: 1px solid var(--border); position: relative; flex-shrink: 0; padding: 0; } .toggle i { position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%; background: var(--muted); transition: left .15s, background .15s; } .toggle.on { background: color-mix(in srgb, var(--ok) 25%, var(--bg3)); border-color: var(--ok); } .toggle.on i { left: 18px; background: var(--ok); } .rule-cond { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; } .rule-cond select, .rule-cond input, .chan-add select, .chan-add input { padding: 8px 9px; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; color: var(--fg); font-size: 14px; } .rule-cond input.mono { flex: 1 1 120px; min-width: 0; } .chan-add { display: flex; gap: 6px; margin-top: 8px; flex-wrap: wrap; } .chan-add input { flex: 1 1 220px; min-width: 0; } .ev { display: flex; align-items: baseline; gap: 8px; padding: 6px 0; border-top: 1px solid var(--border); font-size: 13px; } .ev:first-child { border-top: 0; } .ev-main { flex: 1; min-width: 0; overflow-wrap: anywhere; } .bad-text { color: var(--bad) !important; } /* Arranging the rail: a foot row toggles it; each page then carries a handle to drag by. */ .rail-foot { margin-top: 8px; padding: 6px 12px; display: flex; gap: 10px; align-items: center; font-size: 12px; color: var(--muted); border-top: 1px solid var(--border); } .rail-foot button { color: var(--muted); } .rail-foot button:hover { color: var(--fg); } .rail-foot button.on { color: var(--accent); } .rail.arranging .group-head { cursor: default; } .rail.arranging .group-head:hover { background: none; } .rail .handle { display: none; width: 18px; margin: -4px 0 -4px -4px; align-self: stretch; align-items: center; justify-content: center; color: var(--muted); cursor: grab; touch-action: none; user-select: none; -webkit-user-select: none; font-size: 14px; letter-spacing: -2px; } .rail.arranging .handle { display: inline-flex; } .rail.arranging .handle:hover { color: var(--fg); } .rail .group.dragging .group-head { background: var(--bg3); opacity: .75; box-shadow: inset 3px 0 0 var(--accent2); } .rail.arranging .group { transition: transform .12s ease; } .rail-badge { margin-left: auto; min-width: 18px; padding: 0 5px; border-radius: 9px; background: var(--bad); color: #fff; font-size: 11px; font-weight: 700; text-align: center; line-height: 18px; } @media (pointer: coarse) { .toggle { width: 52px; height: 32px; border-radius: 16px; } .toggle i { width: 26px; height: 26px; } .toggle.on i { left: 22px; } .swatch { width: 34px; height: 34px; } .swatches input[type=color] { width: 36px; height: 36px; } } /* docker */ .stack-head { margin: 0; padding: 8px 12px 4px; font-size: 11px; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: .5px; background: var(--bg2); position: sticky; top: 0; z-index: 1; } .docker .svc-state { text-align: right; line-height: 1.35; } .dtable { width: 100%; border-collapse: collapse; } .dtable th { text-align: left; padding: 8px 12px; font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: .5px; position: sticky; top: 0; background: var(--bg2); } .dtable td { padding: 7px 12px; border-top: 1px solid var(--border); overflow-wrap: anywhere; } .docker:not(.has-sel) .svc-list { flex: 1; width: auto; } .docker .svc-tools .chips { flex-wrap: wrap; } .docker .svc-name { flex: 1; min-width: 0; } .docker .svc-name .n { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .docker .svc-row { min-width: 0; } .docker .svc-state { flex-shrink: 0; } .alerts .row { display: flex; align-items: center; gap: 8px; } .alerts .row .badge { flex: 0 0 auto; } .alerts .row .k { flex: 1; min-width: 0; overflow-wrap: anywhere; } /* docker: the list must never size itself to its longest name (that was the sideways scroll on phones) */ .docker .svc-list { min-width: 0; max-width: 100%; } .docker .svc-rows { overflow-x: hidden; } .docker .svc-name .d { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .docker:not(.has-sel) .svc-list { flex: 1 1 0; } .stack-editor { flex: 1; min-height: 46vh; font-size: 13px; tab-size: 2; white-space: pre; overflow: auto; } /* The code editor: a gutter of line numbers, a painted copy of the text behind a textarea whose own text is transparent, both in the same monospace at the same size so they line up glyph for glyph. .code takes the sizing class the textarea used to (.stack-editor), so it sits where that did. */ .code { display: flex; align-items: stretch; background: var(--term-bg); border: 1px solid var(--border); border-radius: 6px; overflow: hidden; position: relative; padding: 0; min-height: 260px; } .code.ro .code-text { color: var(--muted); } .code-gutter { flex: 0 0 auto; min-width: 2.6em; padding: 10px 6px 10px 0; text-align: right; color: var(--muted); opacity: .7; background: var(--bg2); border-right: 1px solid var(--border); overflow: hidden; user-select: none; font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace; } .code-body { flex: 1; position: relative; min-width: 0; } .code-paint, .code-text { margin: 0; padding: 10px; font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace; tab-size: 2; white-space: pre; letter-spacing: 0; word-spacing: 0; } .code-paint { position: absolute; inset: 0; overflow: hidden; color: var(--fg); pointer-events: none; } .code-text { position: absolute; inset: 0; width: 100%; height: 100%; background: transparent; color: transparent; caret-color: var(--fg); border: 0; outline: 0; resize: none; overflow: auto; } .code-text::placeholder { color: var(--muted); } .code-text::selection { background: rgba(127, 127, 127, .35); } .stack-editor.code, .stack-editor .code-paint, .stack-editor .code-text, .stack-editor .code-gutter { font-size: 13px; } .y-key { color: var(--accent2); } .y-colon, .y-dash { color: var(--muted); } .y-string { color: var(--ok); } .y-number, .y-bool { color: var(--s2); } .y-comment { color: var(--muted); font-style: italic; } .y-anchor, .y-block { color: var(--accent); } .y-value { color: var(--fg); } /* a phone has no Tab key: a bar of the strokes a compose file needs, pinned to the editor's foot */ .code-keys { position: absolute; left: 0; right: 0; bottom: 0; display: flex; gap: 6px; padding: 6px 8px; background: var(--bg2); border-top: 1px solid var(--border); z-index: 1; } .code-keys button { min-width: 40px; height: 32px; padding: 0 10px; border: 1px solid var(--border); border-radius: 6px; background: var(--bg3); color: var(--fg); font: 15px ui-monospace, monospace; } .code-keys ~ * { display: none; } @media (pointer: coarse) { .code .code-body { padding-bottom: 45px; } .code .code-gutter { padding-bottom: 55px; } } @media (max-width: 800px) { .docker .svc-list { width: 100%; flex: 1 1 auto; } .stack-editor { min-height: 52vh; font-size: 12px; } .stack-editor.code, .stack-editor .code-paint, .stack-editor .code-text, .stack-editor .code-gutter { font-size: 12px; } } /* stacks: diff, images, .env */ .stack-extra { display: flex; flex-direction: column; gap: 6px; border-top: 1px solid var(--border); padding-top: 10px; } .stack-extra .sub-head .k { font-size: 12px; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: .5px; } pre.diff { margin: 4px 0 8px; padding: 8px 10px; background: var(--term-bg); border: 1px solid var(--border); border-radius: 6px; font: 12px/1.5 var(--mono); overflow-x: auto; white-space: pre; } pre.diff .add { color: var(--ok); background: color-mix(in srgb, var(--ok) 12%, transparent); } pre.diff .del { color: var(--bad); background: color-mix(in srgb, var(--bad) 12%, transparent); } .img-row { display: flex; align-items: center; gap: 8px; min-width: 0; } .img-row .mono { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; } .badge.attn { color: var(--s2); border-color: var(--s2); } .env-masked { position: relative; display: flex; flex-direction: column; flex: 1; } .env-masked pre { margin: 0; min-height: 30vh; opacity: .8; } .env-masked .btn { position: absolute; top: 10px; right: 10px; } /* new stack / new container */ .modal.stack-new { width: min(680px, 100%); } .stack-new textarea, .stack-new select, .stack-new input:not([type=checkbox]) { padding: 8px 10px; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; color: var(--fg); font-size: 14px; width: 100%; box-sizing: border-box; } .stack-new textarea.mono, .stack-new input.mono { font-family: var(--mono); font-size: 13px; } .stack-new textarea { resize: vertical; } .stack-new .two { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; } .stack-new .stack-editor { min-height: 32vh; } .stack-new .check { display: flex; flex-direction: row; align-items: flex-start; gap: 8px; font-size: 13px; } .stack-new .check input { margin-top: 3px; } .stack-preview { max-height: 180px; overflow: auto; opacity: .85; } .clash { color: var(--s2); font-size: 12px; overflow-wrap: anywhere; } @media (max-width: 800px) { .stack-new .two { grid-template-columns: 1fr; } } .dtable td.row-end { text-align: right; white-space: nowrap; width: 1%; } .dtable .badge { margin-left: 0; } /* ---- Top: a machine drawn the way btop draws it ---- The layout and the dots are btop's; the colours are the skin's. The ground is the skin's terminal background and everything on it comes from the skin's variables and its sixteen terminal colours (Top.tsx hands the few it needs over as --t-*), so switching skin reskins this page like any other. Fixed-width everywhere, because that is the look. */ .top { position: absolute; inset: 0; background: var(--term-bg); color: var(--fg); font-family: var(--mono); font-size: 13px; line-height: 1.35; padding: 6px; display: grid; gap: 4px; grid-template-columns: 1fr 1fr 2.2fr; grid-template-rows: minmax(200px, 1fr) auto minmax(170px, 1fr); overflow-y: auto; } /* every box says how little it can be: a window too short to hold them all scrolls, rather than cutting the bottom off whichever box the grid ran out of room for */ .t-cpu-graph { min-height: 120px; } .t-net-graphs { min-height: 130px; } .top-proc { min-height: 300px; } .top-mem, .top-disks { min-height: 165px; } /* a grid row set to auto will otherwise squeeze them to nothing */ .top-cpu { grid-column: 1 / -1; } .top-mem { grid-column: 1; grid-row: 2; } .top-disks { grid-column: 2; grid-row: 2; } .top-net { grid-column: 1 / 3; grid-row: 3; } .top-proc { grid-column: 3; grid-row: 2 / 4; } /* a machine with a GPU gets a row of gpu boxes under the cpu, as btop lays it out; the rest move down one */ .top.has-gpu { grid-template-rows: minmax(200px, 1fr) minmax(210px, auto) auto minmax(170px, 1fr); } .top-gpus { grid-column: 1 / -1; grid-row: 2; display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: 4px; min-width: 0; } .top.has-gpu .top-mem, .top.has-gpu .top-disks { grid-row: 3; } .top.has-gpu .top-net { grid-row: 4; } .top.has-gpu .top-proc { grid-row: 3 / 5; } .top-gpu { min-height: 210px; } /* a GPU with nothing to measure is one line, and on a machine with no other its row is only as tall as that line. Same weight as .top.has-gpu and after it, so the narrower layouts' own .top.has-gpu, further down, still wins there (their gpu row is already auto). */ .top.gpu-slim { grid-template-rows: minmax(200px, 1fr) auto auto minmax(170px, 1fr); } .top-gpu.slim { align-self: start; } /* beside a full gpu box it keeps its one line, not that box's height */ .t-row.t-gpu-line { flex-wrap: wrap; gap: 0 14px; } .t-gpu-line > span { white-space: nowrap; } .t-gpu-who { margin-left: auto; } /* on a phone it wraps to its own line, still on the right */ .t-gpu { display: flex; gap: 8px; min-width: 0; flex: 1; min-height: 0; } /* the graph and its caption share this box: tall enough for both, so the caption never lands on the stats */ .t-gpu-graph { flex: 1; min-width: 0; min-height: 130px; display: flex; flex-direction: column; } .t-gpu-stats { width: 310px; flex-shrink: 0; border: 1px solid var(--border); border-radius: 3px; padding: 4px 6px; display: flex; flex-direction: column; gap: 1px; overflow-y: auto; scrollbar-width: thin; } .t-gpu-stats .t-core-n { width: 58px; } /* room for "enhance" — the cpu box's 30px fits "C15", not an engine's name */ .t-gpu-name { color: var(--fg); overflow: hidden; text-overflow: ellipsis; max-width: 42vw; } .t-core-n.t-eng { color: var(--muted); font-size: 12px; } .t-gpu-facts { margin-top: 2px; } .t-gpu-facts .t-row { gap: 6px; } .t-fact { color: var(--fg); white-space: nowrap; } .t-gpu-facts .tm { flex: 1; } .t-gpu-clients { margin-top: 4px; border-top: 1px solid var(--border); padding-top: 2px; } .t-gpu-clients .t-thead, .t-crow { display: grid; grid-template-columns: 52px minmax(0, 1fr) 50px 46px; gap: 7px; align-items: center; white-space: nowrap; } .t-gpu-clients .t-thead { border-bottom: 0; padding-right: 0; } .t-crow > span { min-width: 0; overflow: hidden; } .t-crow .c-name { color: var(--ok); text-overflow: ellipsis; } .t-crow .c-cpu { color: var(--fg); } .t-crow-none { display: block; font-size: 12px; padding: 1px 0; } .tbox { position: relative; border: 1px solid var(--border); border-radius: 3px; min-width: 0; min-height: 0; display: flex; flex-direction: column; } .tbox-in { flex: 1; min-height: 0; min-width: 0; padding: 9px 8px 6px; display: flex; flex-direction: column; overflow: hidden; } .tbox-name, .tbox-right { position: absolute; top: -9px; background: var(--term-bg); padding: 0 4px; font-size: 12px; white-space: nowrap; z-index: 1; } .tbox-name { left: 8px; color: var(--fg); } .tbox-name b { color: var(--accent); font-weight: 400; } .tbox-right { right: 8px; color: var(--muted); display: flex; align-items: center; gap: 8px; } .tbox-right button { background: none; border: 0; color: var(--muted); font-family: var(--mono); font-size: 12px; padding: 0 3px; cursor: pointer; } .tbox-right button:hover:not(:disabled), .tbox-right button.on { color: var(--accent); } .tbox-right button:disabled { opacity: .35; cursor: default; } .t-clock { color: var(--fg); } .t-rate { display: inline-flex; align-items: center; gap: 2px; } .t-dim { color: var(--muted); } .top .spacer { flex: 1; } /* cpu */ .t-cpu { display: flex; gap: 8px; min-width: 0; flex: 1; min-height: 0; } .t-cpu-graph { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; } .t-up { color: var(--muted); font-size: 12px; padding-top: 2px; } .t-cores { width: 310px; flex-shrink: 0; border: 1px solid var(--border); border-radius: 3px; padding: 4px 6px; display: flex; flex-direction: column; gap: 1px; overflow-y: auto; scrollbar-width: thin; } .t-cores-head { display: flex; gap: 8px; color: var(--muted); font-size: 12px; white-space: nowrap; } .t-cores-head b { color: var(--fg); font-weight: 400; overflow: hidden; text-overflow: ellipsis; } .t-core { display: flex; align-items: center; gap: 6px; } .t-core-n { width: 30px; } .t-core-p { width: 34px; text-align: right; } .t-core-g { flex: 1; color: var(--ok); letter-spacing: 0; overflow: hidden; white-space: nowrap; } .t-core .tm { flex: 1; } .t-core-t { flex: 0 0 62px; display: flex; } .t-core-c { width: 42px; text-align: right; color: var(--t-blue); } .t-load { display: flex; margin-top: 2px; } /* the dot graph and the dotted meter */ /* The canvas is taken out of flow. Its drawing size is set from the box it sits in; if it also sat *in* that box and the box's height was ever content-sized, each resize would set a bigger canvas, which would make a bigger box, which would set a bigger canvas — the Processes tab growing without end, as it did when the gpu box landed in an auto-height row. */ .tg { position: relative; width: 100%; height: 100%; flex: 1; min-height: 0; overflow: hidden; } .tg canvas { position: absolute; inset: 0; } .tm { position: relative; display: inline-block; height: 9px; flex: 1; min-width: 24px; vertical-align: middle; } .tm-t, .tm-f { position: absolute; inset: 0; -webkit-mask-image: repeating-linear-gradient(90deg, #000 0 3px, transparent 3px 5px); mask-image: repeating-linear-gradient(90deg, #000 0 3px, transparent 3px 5px); } .tm-t { background: var(--border); } /* mem and disks */ .t-row { display: flex; align-items: center; gap: 6px; } .t-row b { font-weight: 400; } .t-sub { color: var(--muted); font-size: 12px; } .t-mem { margin-bottom: 4px; } .t-mem .t-sub { margin-top: -2px; } .t-pct { width: 34px; color: var(--fg); } .t-mem > .tm { display: block; width: 100%; } .t-amt { width: 76px; text-align: right; color: var(--fg); white-space: nowrap; } .t-disks { overflow-y: auto; display: flex; flex-direction: column; gap: 6px; scrollbar-width: thin; } .t-disk .t-row + .t-row { margin-top: -1px; } .t-disk b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } /* net */ .t-net { display: flex; gap: 10px; min-width: 0; flex: 1; min-height: 0; } .t-net-graphs { flex: 1; min-width: 0; display: flex; flex-direction: column; min-height: 140px; } .t-net-g { position: relative; flex: 1; min-height: 0; display: flex; } .t-scale { position: absolute; left: 2px; top: 0; font-size: 11px; color: var(--muted); z-index: 1; background: var(--term-bg); padding-right: 3px; } .t-scale-b { top: auto; bottom: 0; } .t-net-nums { width: 300px; flex-shrink: 0; border: 1px solid var(--border); border-radius: 3px; padding: 4px 8px; white-space: nowrap; overflow: hidden; } .t-dl { color: var(--t-magenta); } .t-ul { color: var(--t-blue); } .t-gap { height: 6px; } .t-up-label { text-align: right; } .t-if { color: var(--fg); } /* proc — the columns that fit are the columns shown: the box measures itself (it is a third of a wide window and all of a narrow one, so the window's width says nothing) and drops the curve, then the threads, then the command, rather than crushing Command to one letter */ .top-proc { container: tproc / inline-size; } .t-proc { display: flex; flex-direction: column; min-height: 0; flex: 1; } .t-filter { background: var(--bg); border: 1px solid var(--border); border-radius: 3px; color: var(--fg); font-family: var(--mono); font-size: 12px; padding: 3px 6px; margin-bottom: 4px; } .t-thead, .t-prow { display: grid; grid-template-columns: 58px 116px minmax(0, 1fr) 62px 58px 50px 56px 42px; gap: 7px; align-items: center; white-space: nowrap; } .t-thead > span, .t-prow > span { min-width: 0; overflow: hidden; } /* a heading never runs into its neighbour */ .t-thead { padding-right: 8px; color: var(--muted); font-size: 12px; border-bottom: 1px solid var(--border); padding-bottom: 2px; } .t-rows { flex: 1; min-height: 0; overflow-y: auto; scrollbar-width: thin; } .t-prow { cursor: pointer; } .t-prow:hover { background: var(--bg2); } .t-prow.on { background: var(--bg3); } .t-prow .c-name { color: var(--ok); text-overflow: ellipsis; } /* --ok, not the terminal's green: it is the one each skin was measured readable with */ .t-prow .c-cmd { color: var(--muted); text-overflow: ellipsis; } .t-prow .c-user { text-overflow: ellipsis; } .c-pid, .c-thr, .c-mem, .c-cpu { text-align: right; } .t-thead .c-pid, .t-thead .c-name, .t-thead .c-cmd, .t-thead .c-user { text-align: left; } .c-g { color: var(--muted); text-align: right; } /* the Gpu% column, on a machine that has one: dim at rest, lit on the rows that are using it */ .c-gpu { text-align: right; color: var(--muted); } .c-gpu.lit { color: var(--t-magenta); } .t-proc.has-gpu .t-thead, .t-proc.has-gpu .t-prow { grid-template-columns: 58px 116px minmax(0, 1fr) 62px 58px 50px 56px 42px 42px; } @container tproc (max-width: 640px) { .t-thead, .t-prow { grid-template-columns: 58px 116px minmax(0, 1fr) 62px 58px 50px 42px; } .t-proc.has-gpu .t-thead, .t-proc.has-gpu .t-prow { grid-template-columns: 58px 116px minmax(0, 1fr) 62px 58px 50px 42px 42px; } .t-thead .c-g, .t-prow .c-g { display: none; } } @container tproc (max-width: 560px) { .t-thead, .t-prow { grid-template-columns: 58px 110px minmax(0, 1fr) 58px 50px 42px; } .t-proc.has-gpu .t-thead, .t-proc.has-gpu .t-prow { grid-template-columns: 58px 110px minmax(0, 1fr) 58px 50px 42px 42px; } .t-thead .c-thr, .t-prow .c-thr { display: none; } } @container tproc (max-width: 470px) { .t-thead, .t-prow { grid-template-columns: 52px minmax(0, 1fr) 52px 48px 40px; gap: 6px; } .t-proc.has-gpu .t-thead, .t-proc.has-gpu .t-prow { grid-template-columns: 52px minmax(0, 1fr) 52px 48px 40px 40px; } .t-thead .c-cmd, .t-prow .c-cmd { display: none; } } .t-foot { display: flex; align-items: center; gap: 8px; border-top: 1px solid var(--border); padding-top: 3px; margin-top: 2px; font-size: 12px; } .t-foot button { background: none; border: 1px solid var(--border); border-radius: 3px; color: var(--fg); font-family: var(--mono); font-size: 12px; padding: 1px 6px; cursor: pointer; } .t-foot button:hover { border-color: var(--bad); color: var(--bad); } .t-count { color: var(--muted); } .t-sorts { display: inline-flex; gap: 2px; } @media (max-width: 1280px) { .top { grid-template-columns: 1fr 1fr; grid-template-rows: minmax(230px, auto) minmax(220px, auto) minmax(200px, auto) minmax(420px, 1fr); } .top-mem, .top-disks { min-height: 220px; } .top-cpu { grid-column: 1 / -1; grid-row: 1; } .top-mem { grid-column: 1; grid-row: 2; } .top-disks { grid-column: 2; grid-row: 2; } .top-net { grid-column: 1 / -1; grid-row: 3; } .top-proc { grid-column: 1 / -1; grid-row: 4; } .top.has-gpu { grid-template-rows: minmax(230px, auto) auto minmax(220px, auto) minmax(200px, auto) minmax(420px, 1fr); } .top.has-gpu .top-mem, .top.has-gpu .top-disks { grid-row: 3; } .top.has-gpu .top-net { grid-row: 4; } .top.has-gpu .top-proc { grid-row: 5; } } @media (max-width: 800px) { .top { display: flex; flex-direction: column; font-size: 12px; } .top > .tbox { flex: 0 0 auto; } .top-gpus { display: flex; flex-direction: column; flex: 0 0 auto; } .top-gpu.slim { align-self: stretch; } .t-gpu { flex-direction: column; } .t-gpu-graph { height: 130px; flex: 0 0 130px; } .t-gpu-stats { width: auto; max-height: none; } .t-cpu { flex-direction: column; } .t-cpu-graph { height: 130px; flex: 0 0 130px; } .t-cores { width: auto; max-height: 40vh; } .t-net { flex-direction: column; } .t-net-nums { width: auto; } .t-net-graphs { flex: 0 0 130px; } .top-proc { height: 70vh; min-height: 420px; } } /* ---- ZFS: the ARC card and each pool's caching tiers ---- */ .arc-sparks { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 8px; } .arc-sparks .small { margin-bottom: 2px; } @media (max-width: 640px) { .arc-sparks { grid-template-columns: 1fr; } } .arc-break { display: grid; grid-template-columns: minmax(140px, 1fr) 80px 110px 110px; gap: 2px 10px; font-size: 12px; margin-top: 10px; } .arc-break .head { color: var(--muted); } .arc-break .num { text-align: right; } @media (max-width: 640px) { .arc-break { grid-template-columns: minmax(0, 1fr) auto auto auto; gap: 2px 8px; font-size: 11px; } .arc-break > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } } .arc-tiers { display: flex; flex-direction: column; gap: 5px; font-size: 13px; margin-top: 4px; } .arc-tier { display: flex; gap: 8px; min-width: 0; } .arc-tier .k { color: var(--muted); width: 80px; flex-shrink: 0; cursor: help; } .arc-tier-v { display: flex; flex-direction: column; gap: 1px; min-width: 0; } .arc-tier-v > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .arc-tier-v .none { color: var(--muted); white-space: normal; } /* ---- Space: what is using a disk ---- */ .space { position: absolute; inset: 0; display: flex; flex-direction: column; background: var(--bg); min-height: 0; } .space-bar { display: flex; flex-direction: column; gap: 8px; padding: 10px 12px; border-bottom: 1px solid var(--border); background: var(--bg2); } .space-pick-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; } .space-status { display: flex; align-items: center; gap: 10px; min-height: 28px; flex-wrap: wrap; } .space-about { display: flex; gap: 0; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .space-about .cur { min-width: 0; overflow: hidden; text-overflow: ellipsis; } /* the disk-or-pool picker */ .tpick { position: relative; min-width: 260px; max-width: 100%; } .tpick-btn { display: flex; align-items: center; gap: 8px; padding: 6px 10px; background: var(--bg3); border: 1px solid var(--border); border-radius: 8px; text-align: left; max-width: 100%; } .tpick-btn b { color: var(--accent); } .tpick-btn .chev { margin-left: auto; color: var(--muted); padding-left: 8px; } .tpick-btn .spin { margin: 0 0 0 4px; } .ticon { color: var(--accent2); font-family: ui-monospace, monospace; width: 16px; text-align: center; flex-shrink: 0; } .tmenu { position: absolute; z-index: 20; top: calc(100% + 4px); left: 0; min-width: min(520px, 90vw); max-height: 60vh; overflow-y: auto; background: var(--bg2); border: 1px solid var(--border); border-radius: 8px; box-shadow: 0 8px 24px rgba(0,0,0,.4); padding: 4px; } .titem { width: 100%; display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-radius: 6px; text-align: left; } .titem:hover { background: var(--bg3); } .titem.on { box-shadow: inset 3px 0 0 var(--accent); background: var(--bg3); } .titem.off { opacity: .5; cursor: default; } .titem .tmain { display: flex; flex-direction: column; min-width: 0; flex: 1; } .titem .tname b { color: var(--fg); } .titem .tsub { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: ui-monospace, monospace; } .titem .tstate { flex-shrink: 0; white-space: nowrap; } .space .warn { color: var(--s2); } .space-tabs { padding: 8px 12px 0; } .space-split { flex: 1; display: flex; flex-direction: column; min-height: 0; padding: 8px 12px 12px; gap: 8px; } @media (max-width: 800px) { .space-split { overflow-y: auto; padding: 8px; } .space-tree { flex: 0 0 auto; height: 40vh; } .space-map { flex: 0 0 auto; height: 60vh; } } .space-tree { flex: 1 1 55%; min-height: 120px; display: flex; flex-direction: column; border: 1px solid var(--border); border-radius: 6px; background: var(--bg2); overflow: hidden; } .space-rows { flex: 1; overflow: auto; min-height: 0; } .space-row { display: grid; grid-template-columns: minmax(160px, 1fr) 84px 120px 70px 64px 88px; gap: 8px; align-items: center; padding: 3px 8px; font-size: 13px; border-bottom: 1px solid var(--border); cursor: pointer; white-space: nowrap; } .space-row.head { position: sticky; top: 0; z-index: 1; background: var(--bg3); color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: .04em; cursor: default; } .space-row:hover:not(.head) { background: var(--bg3); } .space-row.active { background: var(--bg3); box-shadow: inset 3px 0 0 var(--accent); } .space-row.agg { color: var(--muted); font-style: italic; } .space-row .n { display: flex; align-items: center; gap: 4px; min-width: 0; } .space-row .nm { overflow: hidden; text-overflow: ellipsis; } .space-row .tw { width: 16px; flex-shrink: 0; color: var(--muted); padding: 0; font-size: 11px; } .space-row .tw:hover { color: var(--fg); } .space-row .ico { width: 10px; height: 10px; flex-shrink: 0; border-radius: 2px; font-size: 9px; line-height: 10px; color: var(--accent2); text-align: center; } .space-row .r { text-align: right; } .space-row .pct { display: flex; align-items: center; gap: 6px; } .space-row .bar { flex: 1; height: 8px; background: var(--track); border-radius: 4px; overflow: hidden; } .space-row .bar > span { display: block; height: 100%; background: var(--s1); border-radius: 4px; } .space-row.file { grid-template-columns: minmax(160px, 1fr) 84px 120px 88px; } .space-map { flex: 1 1 45%; min-height: 200px; display: flex; flex-direction: column; gap: 6px; } .space-crumbs { display: flex; align-items: center; gap: 2px; flex-wrap: wrap; font-size: 13px; } .space-crumbs .crumb { padding: 2px 6px; border-radius: 4px; color: var(--muted); } .space-crumbs .crumb:hover { color: var(--fg); background: var(--bg3); } .space-crumbs .crumb.on { color: var(--fg); font-weight: 600; } .space-crumbs .crumb + .crumb::before { content: '›'; color: var(--muted); margin-right: 4px; } .space-canvas { flex: 1; position: relative; min-height: 160px; border: 1px solid var(--border); border-radius: 6px; overflow: hidden; background: var(--bg2); } .space-canvas canvas { display: block; cursor: pointer; } .space-tip { position: absolute; z-index: 2; max-width: 260px; padding: 6px 8px; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; font-size: 12px; pointer-events: none; box-shadow: 0 4px 16px rgba(0,0,0,.35); } .space-legend { display: flex; flex-wrap: wrap; gap: 4px 12px; font-size: 11px; color: var(--muted); } .space-legend i { display: inline-block; width: 10px; height: 10px; border-radius: 2px; margin-right: 4px; vertical-align: -1px; } .space-legend i.dir { background: var(--bg3); border: 1px solid var(--border); } .space-legend i.small { background: #3a414b; } .space-files { flex: 1; display: flex; flex-direction: column; min-height: 0; padding: 8px 12px 12px; gap: 8px; } .space-files .space-rows { border: 1px solid var(--border); border-radius: 6px; background: var(--bg2); } .space-files input.filter { padding: 6px 10px; background: var(--bg2); border: 1px solid var(--border); border-radius: 6px; color: var(--fg); font-size: 13px; width: min(320px, 100%); } @media (max-width: 800px) { .space-row { grid-template-columns: minmax(120px, 1fr) 72px 80px; } .space-row .c, .space-row .d { display: none; } .space-row.file { grid-template-columns: minmax(120px, 1fr) 72px 80px; } .space-split { padding: 8px; } .space-tree { flex: 1 1 45%; } .space-map { flex: 1 1 55%; } } /* the Processes tab's drive rows and NIC switcher */ .t-disk .t-model { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 14ch; margin-left: 6px; } .t-disk .t-temp { margin-right: 8px; color: var(--t-blue, var(--accent2)); } .t-disk .t-mounts { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .t-ifs { display: inline-flex; gap: 4px; flex-wrap: wrap; justify-content: flex-end; } .t-ifs .t-if { padding: 0 4px; border-radius: 3px; color: var(--muted); } .t-ifs .t-if:hover { color: var(--fg); background: var(--bg3); } .t-ifs .t-if.on { color: var(--fg); box-shadow: inset 0 -2px 0 var(--accent); } /* ZFS: the schedule dialog's controls match the rest of the modal forms */ .modal select, .modal input[type=time], .modal input[type=number] { padding: 9px 10px; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; color: var(--fg); font-size: 16px; } .modal .chips { margin-top: 2px; } /* ---- KVM: a device's own web page, framed ---- */ .kvm { position: absolute; inset: 0; display: flex; flex-direction: column; background: var(--bg); min-height: 0; } .kvm-bar { display: flex; align-items: center; gap: 8px; padding: 8px 12px; border-bottom: 1px solid var(--border); background: var(--bg2); flex-wrap: wrap; } .kvm-bar .kvm-url { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 28ch; } .kvm-frame { flex: 1; min-height: 0; background: #000; position: relative; } .kvm-frame iframe { width: 100%; height: 100%; border: 0; display: block; background: #000; } .kvm-frame.full { background: #000; } .kvm-hint { padding: 6px 12px; border-top: 1px solid var(--border); } @media (max-width: 800px) { .kvm-bar .kvm-url { display: none; } } .kvm-none { text-align: center; } .kvm-none p { margin: 4px 0; } /* No true fullscreen for a page element (Safari on iOS): fill the viewport in-page instead. */ .kvm-frame.expanded { position: fixed; inset: 0; z-index: 60; } .kvm-frame.expanded iframe { height: 100%; } .kvm-shrink { position: absolute; right: max(10px, env(safe-area-inset-right)); bottom: max(10px, env(safe-area-inset-bottom)); z-index: 61; background: var(--bg2); border: 1px solid var(--border); opacity: .75; } .kvm-shrink:hover, .kvm-shrink:active { opacity: 1; } .updot { margin-left: 6px; font-size: 11px; font-weight: 700; } .updot.behind { color: var(--s2); } .updot.unknown { color: var(--muted); } /* ---- Virtualization ---- */ .virt .svc-tools .task { max-height: 45vh; } .virt-table { width: 100%; border-collapse: collapse; font-size: 13px; } .virt-table th, .virt-table td { text-align: left; padding: 6px 8px; border-bottom: 1px solid var(--border); vertical-align: top; } .virt-table th { font-weight: 500; opacity: .7; } .virt-table td.num, .virt-table th.num { text-align: right; white-space: nowrap; } .virt-pick { max-height: 240px; overflow-y: auto; border: 1px solid var(--border); border-radius: 6px; } .virt-pick .svc-row { padding: 6px 10px; } .virt-kv { display: grid; grid-template-columns: max-content 1fr; gap: 2px 12px; font-size: 12px; word-break: break-all; } .virt-form-row { display: flex; gap: 8px; } .virt-form-row > label { flex: 1; min-width: 0; } .virt .modal { max-width: 640px; }