Files
restic-manager/web/styles/input.css
T
steve 39030a3bbe
CI / Test (rest) (pull_request) Successful in 41s
CI / Test (store) (pull_request) Successful in 1m16s
CI / Lint (pull_request) Successful in 41s
CI / Build (windows/amd64) (pull_request) Successful in 14s
CI / Build (linux/arm64) (pull_request) Successful in 15s
e2e / Playwright vs docker-compose (pull_request) Failing after 11s
CI / Build (linux/amd64) (pull_request) Successful in 50s
CI / Test (server-http) (pull_request) Failing after 2m53s
ui(host header): boxed tags/presence pills, click-to-edit, simplified out-of-date chip
2026-06-15 22:58:38 +01:00

715 lines
28 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ============================================================
* v1 design tokens + components.
*
* Source of truth for the operator-console register. Anything not
* defined here doesn't exist in v1. New components get added here
* first, then templated.
*
* Built via the Tailwind standalone CLI (no Node):
* make tailwind
* outputs:
* web/static/css/styles.css
* ============================================================ */
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
/* surface */
--bg: oklch(0.17 0.006 250);
--panel: oklch(0.20 0.007 250);
--panel-hi: oklch(0.23 0.008 250);
/* line */
--line: oklch(0.27 0.010 250);
--line-soft: oklch(0.23 0.008 250);
/* ink */
--ink: oklch(0.96 0.005 250);
--ink-mid: oklch(0.78 0.005 250);
--ink-mute: oklch(0.58 0.006 250);
--ink-fade: oklch(0.42 0.006 250);
/* state */
--ok: oklch(0.78 0.14 155);
--warn: oklch(0.82 0.13 80);
--bad: oklch(0.70 0.20 25);
--off: oklch(0.50 0.005 250);
/* accent */
--accent: oklch(0.82 0.12 195);
}
html, body {
background: var(--bg);
color: var(--ink);
font-family: 'Inter', system-ui, -apple-system, sans-serif;
-webkit-font-smoothing: antialiased;
}
body {
font-feature-settings: 'cv11', 'ss01', 'ss03';
}
::selection { background: color-mix(in oklch, var(--accent), transparent 70%); }
}
@layer components {
/* ---------- typography helpers ---------- */
.mono {
font-family: 'JetBrains Mono', ui-monospace, monospace;
font-variant-numeric: tabular-nums;
}
/* ---------- surface helpers ---------- */
.panel { background: var(--panel); border: 1px solid var(--line-soft); }
.hairline { box-shadow: inset 0 -1px 0 var(--line-soft); }
/* ---------- status dots ---------- */
.dot { width: 7px; height: 7px; border-radius: 9999px; display: inline-block; }
.dot-online { background: var(--ok); box-shadow: 0 0 0 3px color-mix(in oklch, var(--ok), transparent 80%); }
.dot-degraded { background: var(--warn); box-shadow: 0 0 0 3px color-mix(in oklch, var(--warn), transparent 80%); }
.dot-offline { background: var(--off); }
.dot-asleep { background: var(--ink-fade); opacity: 0.6; }
.dot-failed { background: var(--bad); box-shadow: 0 0 0 3px color-mix(in oklch, var(--bad), transparent 80%); }
.pulse { animation: rm-pulse 2.4s ease-in-out infinite; }
@keyframes rm-pulse {
0%, 100% { box-shadow: 0 0 0 3px color-mix(in oklch, var(--accent), transparent 80%); }
50% { box-shadow: 0 0 0 6px color-mix(in oklch, var(--accent), transparent 92%); }
}
/* ---------- buttons ---------- */
.btn {
font-size: 12px; font-weight: 500;
padding: 6px 11px; border-radius: 5px;
background: transparent;
border: 1px solid var(--line);
color: var(--ink-mid);
transition: all 120ms ease;
cursor: pointer;
display: inline-flex; align-items: center; gap: 6px;
text-decoration: none;
}
.btn:hover { background: var(--panel-hi); color: var(--ink); }
.btn:disabled, .btn[disabled] { opacity: 0.4; cursor: not-allowed; pointer-events: none; }
.btn-primary { color: oklch(0.18 0.01 195); background: var(--accent); border-color: var(--accent); }
.btn-primary:hover { filter: brightness(1.08); }
.btn-ghost { border-color: transparent; }
.btn-ghost:hover { background: var(--panel-hi); border-color: transparent; }
.btn-danger { color: var(--bad); border-color: color-mix(in oklch, var(--bad), transparent 70%); }
.btn-danger:hover {
background: color-mix(in oklch, var(--bad), transparent 88%);
border-color: color-mix(in oklch, var(--bad), transparent 50%);
color: oklch(0.85 0.10 25);
}
.btn-lg { font-size: 13px; padding: 9px 14px; }
.btn-block { width: 100%; justify-content: center; }
/* Amber action — used for the per-host "Update agent" button and
the fleet-update Start button. Same warning palette as the
update-chip below. */
.btn-amber {
color: oklch(0.18 0.01 80);
background: var(--warn);
border-color: var(--warn);
}
.btn-amber:hover { filter: brightness(1.08); }
.btn-amber:disabled, .btn-amber[disabled] {
opacity: 0.45; cursor: not-allowed; pointer-events: none;
}
/* Update-available chip — small amber pill rendered next to a host's
agent version (in the row OS column and in the host detail
header). Hidden when the host is up to date. */
.update-chip {
display: inline-flex; align-items: center; gap: 4px;
padding: 1px 6px;
border-radius: 3px;
font-size: 10px; font-weight: 500;
line-height: 1.4;
color: oklch(0.18 0.01 80);
background: color-mix(in oklch, var(--warn), transparent 30%);
border: 1px solid color-mix(in oklch, var(--warn), transparent 50%);
white-space: nowrap;
}
/* Hero tile — large, clickable summary card on the dashboard.
Today only used by the "N hosts behind" tile; the existing
four summary boxes use bespoke grid markup. Add more variants
as adjacent dashboard tiles adopt this. */
.hero-tile {
display: flex; flex-direction: column; gap: 4px;
padding: 14px 16px;
border-radius: 7px;
border: 1px solid var(--line-soft);
background: var(--panel);
text-decoration: none;
transition: filter 120ms ease, background 120ms ease;
}
.hero-tile:hover { filter: brightness(1.08); }
.hero-tile .hero-num {
font-family: 'JetBrains Mono', ui-monospace, monospace;
font-size: 22px; font-weight: 500;
letter-spacing: -0.01em;
color: var(--ink);
}
.hero-tile .hero-label {
font-size: 11.5px;
color: var(--ink-mute);
}
.hero-tile--amber {
background: color-mix(in oklch, var(--warn), transparent 88%);
border-color: color-mix(in oklch, var(--warn), transparent 60%);
}
.hero-tile--amber .hero-num { color: oklch(0.86 0.13 80); }
.hero-tile--amber .hero-label { color: oklch(0.78 0.08 80); }
/* ---------- nav tabs ---------- */
.nav-tab {
font-size: 13px; padding: 18px 0;
color: var(--ink-mute);
border-bottom: 2px solid transparent;
margin-right: 28px;
cursor: pointer;
text-decoration: none;
}
.nav-tab.active { color: var(--ink); border-color: var(--accent); }
.nav-tab:hover { color: var(--ink); }
/* secondary tabs (host detail sub-nav) */
.sub-tab {
font-size: 13px; padding: 12px 0;
color: var(--ink-mute);
border-bottom: 1.5px solid transparent;
margin-right: 24px;
cursor: pointer;
text-decoration: none;
}
.sub-tab.active { color: var(--ink); border-color: var(--ink); }
/* ---------- tags ---------- */
.tag {
display: inline-flex; align-items: center; gap: 5px;
font-size: 11px; line-height: 1; padding: 4px 7px;
border: 1px solid var(--line); color: var(--ink-mid);
border-radius: 3px; letter-spacing: 0.01em;
}
.tag-removable .x { color: var(--ink-fade); cursor: pointer; padding-left: 2px; }
/* ---------- header meta groups (boxed tags / presence pills) ---------- */
.meta-group {
display: inline-flex; align-items: center; gap: 6px;
font-size: 11px; line-height: 1; padding: 3px 9px;
border: 1px solid var(--line); border-radius: 5px;
background: color-mix(in oklch, var(--ink), transparent 95%);
}
.meta-group .meta-label { color: var(--ink-mute); }
.meta-group .meta-val { color: var(--ink-mid); text-decoration: none; }
.meta-group a.meta-val:hover { color: var(--ink); text-decoration: underline; }
/* ---------- form fields ---------- */
.field-label { font-size: 12px; color: var(--ink-mid); margin-bottom: 6px; display: block; }
.field-help { font-size: 12px; color: var(--ink-mute); margin-top: 6px; line-height: 1.55; }
.field-error { font-size: 12px; color: oklch(0.85 0.10 25); margin-top: 6px; }
.field {
width: 100%; padding: 9px 12px;
background: var(--bg); border: 1px solid var(--line-soft);
color: var(--ink); border-radius: 5px;
font-size: 13px; font-family: inherit; outline: none;
transition: border-color 120ms ease;
}
.field:focus { border-color: var(--accent); }
.field.invalid { border-color: color-mix(in oklch, var(--bad), transparent 50%); }
.field.mono { font-family: 'JetBrains Mono', monospace; font-size: 12px; }
.field.with-prefix { padding-left: 64px; }
.field-prefix {
position: absolute; left: 12px; top: 50%; transform: translateY(-50%);
font-family: 'JetBrains Mono', monospace; font-size: 12px;
color: var(--ink-mute); pointer-events: none;
}
/* ---------- host row (the dashboard's load-bearing component) ---------- */
.host-row {
display: grid; align-items: center;
grid-template-columns: 24px 1.4fr 0.95fr 1.5fr 0.75fr 96px 0.7fr 0.7fr 1.1fr 92px;
column-gap: 18px;
padding: 11px 16px; font-size: 13px;
border-left: 3px solid transparent;
}
.host-row.head {
padding-top: 10px; padding-bottom: 10px;
font-size: 11px; color: var(--ink-fade);
text-transform: uppercase; letter-spacing: 0.08em;
}
.host-row.degraded { border-left-color: color-mix(in oklch, var(--warn), transparent 50%); }
.host-row.failed { border-left-color: color-mix(in oklch, var(--bad), transparent 50%); }
.host-row.offline { border-left-color: color-mix(in oklch, var(--off), transparent 70%); }
.host-row:hover { background: var(--panel-hi); }
/* Whole-row click → host detail. The action cell sits above via
z-index so its button keeps working. */
.host-row.clickable { position: relative; }
.host-row.clickable .row-link {
position: absolute; inset: 0; z-index: 0;
text-indent: -9999px; overflow: hidden;
}
.host-row.clickable:hover { cursor: pointer; }
.host-row.clickable > * { position: relative; z-index: 1; pointer-events: none; }
.host-row.clickable > .row-link { pointer-events: auto; }
.host-row.clickable > .row-action { pointer-events: auto; }
/* ---------- source-group rows (Sources tab) ---------- */
.src-row {
display: grid; align-items: center;
grid-template-columns: 1fr auto;
column-gap: 18px;
padding: 14px 18px;
}
/* Whole-row click → edit page, mirroring .host-row.clickable on the
dashboard. Action cells sit above via z-index so their buttons
keep working. */
.src-row.clickable { position: relative; }
.src-row.clickable .row-link {
position: absolute; inset: 0; z-index: 0;
text-indent: -9999px; overflow: hidden;
}
.src-row.clickable:hover { background: var(--panel-hi); cursor: pointer; }
.src-row.clickable > * { position: relative; z-index: 1; pointer-events: none; }
.src-row.clickable > .row-link { pointer-events: auto; }
.src-row.clickable > .row-action { pointer-events: auto; }
/* ---------- dropdown menu (header actions) ----------
* Uses native <details><summary> for keyboard + no-JS support.
* The summary is styled like a .btn, the panel sits absolute below.
* Click-outside-to-close handled by CSS via :has() — no JS.
*/
.dropdown { position: relative; display: inline-block; }
.dropdown summary {
list-style: none; cursor: pointer;
/* match .btn shape */
font-size: 12px; font-weight: 500;
padding: 6px 11px; border-radius: 5px;
background: transparent;
border: 1px solid var(--line);
color: var(--ink-mid);
transition: all 120ms ease;
display: inline-flex; align-items: center; gap: 6px;
user-select: none;
}
.dropdown summary::-webkit-details-marker { display: none; }
.dropdown summary::marker { content: ""; }
.dropdown summary:hover { background: var(--panel-hi); color: var(--ink); }
.dropdown summary .chev {
font-size: 9px; color: var(--ink-fade);
transition: transform 120ms ease;
}
.dropdown[open] summary .chev { transform: rotate(180deg); }
.dropdown[open] summary { background: var(--panel-hi); color: var(--ink); }
.dropdown-menu {
position: absolute; top: calc(100% + 4px); right: 0;
z-index: 30;
min-width: 220px;
background: var(--panel);
border: 1px solid var(--line);
border-radius: 6px;
box-shadow: 0 6px 24px -8px rgba(0,0,0,0.55);
padding: 4px;
}
.dropdown-item {
display: block;
padding: 8px 11px;
border-radius: 4px;
text-decoration: none;
color: var(--ink-mid);
font-size: 12.5px;
line-height: 1.35;
}
.dropdown-item:hover { background: var(--panel-hi); color: var(--ink); }
.dropdown-item .label { display: block; color: var(--ink); font-weight: 500; }
.dropdown-item .hint {
display: block; font-size: 11px; color: var(--ink-mute); margin-top: 2px;
font-family: 'JetBrains Mono', ui-monospace, monospace;
}
/* ---------- snapshot picker rows (Restore wizard step 1) ---------- */
.snap-row {
display: grid; align-items: center;
grid-template-columns: 150px 130px 1fr 90px 130px 80px;
column-gap: 16px;
padding: 11px 14px; font-size: 13px;
border-bottom: 1px solid var(--line-soft);
cursor: pointer;
transition: background 100ms ease;
}
.snap-row:last-child { border-bottom: 0; }
.snap-row:hover { background: var(--panel-hi); }
.snap-row.head {
font-size: 11px; color: var(--ink-fade);
text-transform: uppercase; letter-spacing: 0.08em;
padding-top: 9px; padding-bottom: 9px; cursor: default;
}
.snap-row.head:hover { background: transparent; }
/* ---------- alert rows (/alerts list) ---------- */
.alert-row {
display: grid; align-items: center;
grid-template-columns: 18px 110px 130px 1fr 130px 110px 180px;
column-gap: 16px;
padding: 12px 16px; font-size: 13px;
border-bottom: 1px solid var(--line-soft);
border-left: 3px solid transparent;
transition: background 100ms ease;
}
.alert-row:hover { background: var(--panel-hi); }
.alert-row:last-child { border-bottom: 0; }
.alert-row.head {
cursor: default; padding-top: 9px; padding-bottom: 9px;
font-size: 11px; color: var(--ink-fade);
text-transform: uppercase; letter-spacing: 0.08em;
border-left-color: transparent;
}
.alert-row.head:hover { background: transparent; }
.alert-row.severity-warn { border-left-color: color-mix(in oklch, var(--warn), transparent 50%); }
.alert-row.severity-critical { border-left-color: color-mix(in oklch, var(--bad), transparent 30%); }
.alert-row.resolved { opacity: 0.55; }
/* status-dot aliases for alert severity */
.dot-warn { background: var(--warn); box-shadow: 0 0 0 3px color-mix(in oklch, var(--warn), transparent 80%); }
.dot-critical { background: var(--bad); box-shadow: 0 0 0 3px color-mix(in oklch, var(--bad), transparent 80%); }
.dot-resolved { background: var(--ok); box-shadow: 0 0 0 3px color-mix(in oklch, var(--ok), transparent 80%); }
/* Tag in active/selected state — used by the dashboard chip-row
filter and any other UI that wants a "this tag is currently
applied" highlight. Subtle: slight accent tint, accent border,
ink colour shift; doesn't shout. */
.tag.tag-active {
color: var(--accent);
border-color: color-mix(in oklch, var(--accent), transparent 50%);
background: color-mix(in oklch, var(--accent), transparent 92%);
}
/* tag colour variants for alerts */
.tag-warn { color: var(--warn); border-color: color-mix(in oklch, var(--warn), transparent 60%); background: color-mix(in oklch, var(--warn), transparent 92%); }
.tag-critical { color: var(--bad); border-color: color-mix(in oklch, var(--bad), transparent 60%); background: color-mix(in oklch, var(--bad), transparent 92%); }
.tag-info { color: var(--ink-mid); }
/* ---------- audit rows (/audit list) ---------- */
.audit-row {
display: grid; align-items: center;
grid-template-columns: 160px 80px 110px 1.4fr 1.5fr 90px;
column-gap: 16px;
padding: 11px 16px; font-size: 13px;
border-bottom: 1px solid var(--line-soft);
transition: background 100ms ease;
}
.audit-row:hover { background: var(--panel-hi); }
.audit-row:last-child { border-bottom: 0; }
.audit-row.head {
cursor: default; padding-top: 9px; padding-bottom: 9px;
font-size: 11px; color: var(--ink-fade);
text-transform: uppercase; letter-spacing: 0.08em;
}
.audit-row.head:hover { background: transparent; }
/* Sort-header link styling — shared by .audit-row and .user-row
(and any other future sortable table headers). The selectors
scope to .head rows so hover and accent-glyph treatment only
apply to the header, not data rows that happen to contain a
<a class="sort-header">. */
.audit-row.head .sort-header,
.user-row.head .sort-header {
color: inherit; text-decoration: none; cursor: pointer;
display: inline-flex; align-items: baseline; gap: 4px;
}
.audit-row.head .sort-header:hover,
.user-row.head .sort-header:hover { color: var(--ink); }
.audit-row.head .sort-glyph,
.user-row.head .sort-glyph {
font-size: 9px; color: var(--accent);
/* keep the row height stable when the glyph appears/disappears */
min-width: 8px; display: inline-block;
}
/* ---------- schedule rows (Schedules tab) ---------- */
.schd-row {
display: grid; align-items: center;
grid-template-columns: 78px 1fr 1.6fr 100px 110px auto;
column-gap: 14px;
padding: 12px 18px; font-size: 13px;
}
.schd-row.head {
padding-top: 10px; padding-bottom: 10px;
font-size: 11px; color: var(--ink-fade);
text-transform: uppercase; letter-spacing: 0.08em;
}
/* Whole-row click → edit page (matches .host-row.clickable). */
.schd-row.clickable { position: relative; }
.schd-row.clickable .row-link {
position: absolute; inset: 0; z-index: 0;
text-indent: -9999px; overflow: hidden;
}
.schd-row.clickable:hover { background: var(--panel-hi); cursor: pointer; }
.schd-row.clickable > * { position: relative; z-index: 1; pointer-events: none; }
.schd-row.clickable > .row-link { pointer-events: auto; }
.schd-row.clickable > .row-action { pointer-events: auto; }
/* ---------- jobs rows (Jobs tab) ---------- */
.jobs-row {
display: grid;
grid-template-columns: 110px 110px 90px 1fr 1fr 28px;
gap: 14px;
align-items: center;
padding: 9px 14px;
font-size: 12.5px;
}
.jobs-row.head {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--ink-mid);
padding-top: 11px;
padding-bottom: 11px;
}
.jobs-row.clickable { position: relative; }
.jobs-row.clickable .row-link {
position: absolute; inset: 0; display: block; z-index: 0;
}
.jobs-row.clickable:hover { background: var(--panel-hi); cursor: pointer; }
.jobs-row.clickable > * { position: relative; z-index: 1; pointer-events: none; }
.jobs-row.clickable > .row-link { pointer-events: auto; }
/* ---------- cron preset chips ---------- */
.preset-chip {
font-family: 'JetBrains Mono', monospace; font-size: 11.5px;
padding: 4px 9px; border-radius: 4px;
border: 1px solid var(--line-soft); color: var(--ink-mid);
background: var(--bg);
cursor: pointer; user-select: none;
transition: border-color 100ms ease, color 100ms ease;
}
.preset-chip:hover { border-color: var(--accent); color: var(--ink); }
/* ---------- source-group picker (Schedule new/edit) ---------- */
.picker {
display: flex; align-items: center; gap: 12px;
padding: 10px 12px;
background: var(--bg);
border: 1px solid var(--line-soft);
border-radius: 5px;
font-size: 13px; cursor: pointer;
transition: border-color 100ms ease, background 100ms ease;
}
.picker:hover { border-color: var(--ink-mute); }
.picker .check {
display: inline-block; width: 14px; height: 14px;
border: 1px solid var(--line); border-radius: 3px;
flex-shrink: 0; position: relative;
}
.picker.checked {
border-color: color-mix(in oklch, var(--accent), transparent 50%);
background: color-mix(in oklch, var(--accent), transparent 92%);
}
.picker.checked .check {
background: var(--accent); border-color: var(--accent);
}
.picker.checked .check::after {
content: ""; position: absolute;
left: 4px; top: 1px; width: 4px; height: 8px;
border: solid oklch(0.18 0.01 195);
border-width: 0 1.5px 1.5px 0;
transform: rotate(45deg);
}
.picker input[type="checkbox"] {
position: absolute; opacity: 0; pointer-events: none;
}
/* ---------- retention 3×2 keep-* grid (source-group edit) ---------- */
.keep-cell {
background: var(--bg);
border: 1px solid var(--line-soft);
border-radius: 5px;
padding: 9px 11px;
display: flex; flex-direction: column; gap: 4px;
}
.keep-cell label {
font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.08em;
color: var(--ink-fade);
}
.keep-cell input {
background: transparent; border: none; outline: none;
font-family: 'JetBrains Mono', monospace; font-size: 14px;
color: var(--ink); padding: 0; width: 100%;
}
/* ---------- log viewer ---------- */
.log {
background: var(--bg); border: 1px solid var(--line-soft);
border-radius: 7px;
font-family: 'JetBrains Mono', monospace; font-size: 12px; line-height: 1.7;
overflow: hidden;
}
.log-line { display: grid; grid-template-columns: 14ch 8ch 1fr; column-gap: 14px; padding: 1px 16px; align-items: baseline; }
.log-line:first-child { padding-top: 12px; }
.log-line:last-child { padding-bottom: 12px; }
.log-ts { color: var(--ink-fade); }
.log-tag { font-size: 10px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--ink-fade); }
.log-stream-stdout { color: var(--ink-mid); }
.log-stream-stderr { color: oklch(0.78 0.13 50); }
.log-stream-event { color: var(--accent); }
/* ---------- progress bar ---------- */
.progress-track {
background: var(--bg); border: 1px solid var(--line-soft);
height: 6px; border-radius: 9999px; overflow: hidden;
}
.progress-fill { height: 100%; background: var(--accent); border-radius: 9999px; transition: width 250ms ease; }
.progress-fill.ok { background: var(--ok); }
.progress-fill.bad { background: var(--bad); }
/* ---------- crumbs ---------- */
.crumbs { font-size: 12px; color: var(--ink-mute); }
.crumbs a { color: var(--ink-mute); text-decoration: underline; text-underline-offset: 3px; text-decoration-color: var(--line); }
.crumbs .sep { color: var(--ink-fade); margin: 0 8px; }
/* ---------- install snippet ---------- */
.snippet { border: 1px solid var(--line-soft); border-radius: 6px; overflow: hidden; }
.snippet-head {
display: flex; justify-content: space-between; align-items: center;
padding: 10px 14px; border-bottom: 1px solid var(--line-soft);
font-size: 11px; color: var(--ink-fade);
text-transform: uppercase; letter-spacing: 0.1em;
}
.snippet pre {
margin: 0; padding: 14px;
font-family: 'JetBrains Mono', monospace;
font-size: 12px; color: var(--ink-mid); line-height: 1.7;
white-space: pre-wrap; word-break: break-all;
}
.snippet pre .var { color: var(--accent); }
/* ---------- empty state ---------- */
.empty-state {
text-align: center; padding: 60px 40px;
border: 1px dashed var(--line); border-radius: 8px;
background:
radial-gradient(ellipse at top, color-mix(in oklch, var(--accent), transparent 95%), transparent 60%),
var(--panel);
}
/* ---------- notification channel rows (/settings/notifications) ---------- */
.ch-row {
display: grid; align-items: center;
grid-template-columns: 28px 200px 1fr 100px 130px 140px;
column-gap: 16px;
padding: 14px 18px; font-size: 13px;
border-bottom: 1px solid var(--line-soft);
transition: background 100ms ease;
}
.ch-row:last-child { border-bottom: 0; }
.ch-row.head {
cursor: default; font-size: 11px; color: var(--ink-fade);
text-transform: uppercase; letter-spacing: 0.08em;
padding-top: 10px; padding-bottom: 10px;
}
.ch-row.head:hover { background: transparent; }
/* Whole-row click → edit page (mirrors .host-row.clickable). */
.ch-row.clickable { position: relative; cursor: pointer; }
.ch-row.clickable .row-link {
position: absolute; inset: 0; z-index: 0;
text-indent: -9999px; overflow: hidden;
}
.ch-row.clickable:hover { background: var(--panel-hi); }
.ch-row.clickable > * { position: relative; z-index: 1; pointer-events: none; }
.ch-row.clickable > .row-link { pointer-events: auto; }
.ch-row.clickable > .row-action { pointer-events: auto; }
/* Channel kind icons */
.ch-icon {
width: 24px; height: 24px;
border-radius: 5px;
display: inline-flex; align-items: center; justify-content: center;
font-family: 'JetBrains Mono', monospace; font-size: 10px; font-weight: 600;
background: var(--panel-hi); color: var(--ink-mute);
border: 1px solid var(--line);
}
.ch-icon.webhook { color: var(--accent); border-color: color-mix(in oklch, var(--accent), transparent 60%); }
.ch-icon.ntfy { color: var(--warn); border-color: color-mix(in oklch, var(--warn), transparent 60%); }
.ch-icon.smtp { color: var(--ok); border-color: color-mix(in oklch, var(--ok), transparent 60%); }
/* ---------- toggle (enabled/disabled switch) ---------- */
.toggle {
display: inline-block; width: 30px; height: 16px; border-radius: 9999px;
background: var(--line); position: relative; cursor: pointer;
transition: background 120ms ease; flex-shrink: 0;
}
.toggle::after {
content: ""; position: absolute; left: 2px; top: 2px;
width: 12px; height: 12px; border-radius: 9999px;
background: var(--ink-mid);
transition: all 120ms ease;
}
.toggle.on { background: color-mix(in oklch, var(--accent), transparent 50%); }
.toggle.on::after { left: 16px; background: var(--accent); }
/* ---------- kind-picker radio cards (channel edit form) ---------- */
.kind-grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 14px; }
.kind-card {
border: 1px solid var(--line-soft); background: var(--bg);
border-radius: 7px; padding: 16px;
cursor: pointer;
transition: border-color 120ms ease, background 120ms ease;
}
.kind-card:hover { border-color: var(--ink-mute); }
.kind-card.selected {
border-color: color-mix(in oklch, var(--accent), transparent 50%);
background: color-mix(in oklch, var(--accent), transparent 95%);
}
/* Radio pip inside kind cards */
.radio-pip {
width: 14px; height: 14px;
border-radius: 9999px;
border: 1px solid var(--line);
display: inline-flex; align-items: center; justify-content: center;
flex-shrink: 0;
}
.radio-pip.on { border-color: var(--accent); }
.radio-pip.on::after {
content: ""; width: 6px; height: 6px; border-radius: 9999px;
background: var(--accent);
}
/* ---------- user-management rows (/settings/users) ---------- */
.user-row {
display: grid; align-items: center;
grid-template-columns: 180px 1fr 110px 160px 120px 90px;
column-gap: 16px;
padding: 11px 16px; font-size: 13px;
border-bottom: 1px solid var(--line-soft);
transition: background 100ms ease;
}
.user-row:hover { background: var(--panel-hi); }
.user-row:last-child { border-bottom: 0; }
.user-row.head {
cursor: default; padding-top: 9px; padding-bottom: 9px;
font-size: 11px; color: var(--ink-fade);
text-transform: uppercase; letter-spacing: 0.08em;
}
.user-row.head:hover { background: transparent; }
.user-row.disabled { opacity: 0.55; }
/* ---------- test-result pills (notification test button) ---------- */
.test-pill {
display: inline-block;
padding: 5px 10px; border-radius: 5px; font-size: 12.5px;
}
.test-pill-ok {
border: 1px solid color-mix(in oklch, var(--ok), transparent 60%);
background: color-mix(in oklch, var(--ok), transparent 92%);
color: var(--ok);
}
.test-pill-fail {
border: 1px solid color-mix(in oklch, var(--bad), transparent 60%);
background: color-mix(in oklch, var(--bad), transparent 92%);
color: var(--bad);
}
}