Antivirus/gui/dist/styles.css
johnmcafee 6ef296caa1 Full feature set: realtime monitor, quarantine vault, rootkit scan, settings, events
Daemon (houndd)
- realtime.rs: inotify monitor over watched dirs (default ~/Downloads,
  ~/Documents, ~/Desktop), ClamAV scan on touch, on_detect action
  (quarantine/rename/remove), ransomware heuristic (writes/renames per
  minute above threshold -> 'watching'/'alarm' + critical event)
- quarantine.rs: SHA-256-keyed vault under ~/.local/share/hound/quarantine,
  add/list/restore/remove with original-path metadata
- rootkit.rs: setuid anomaly detection (allowlisted stock binaries),
  deleted-but-executing inodes, world-writable /usr /bin; 3 severity levels
- settings.rs: persisted ~/.config/hound/settings.json, hot-reload on set
- events.rs: ring buffer of severity-tagged events, query + clear

API (hound-api): Settings, Event, QuarantineEntry, RootkitScan/
RootkitFinding, RealtimeStatus types + 10 client methods; Status gains
engine field (engine-agnostic seam)

CLI (hound): events, quarantine list|add|restore|remove, settings
[show|paused|auto-update|notify|realtime on|off|watch|on-detect|
max-size|exclude], rootkit, realtime [status|on|off] — color human
output, --json everywhere

GUI (Tauri 2):
- 16 backend commands bridging every client method
- tray watcher: 1s poll loop, 4-state icon ladder (green/amber/red/gray),
  desktop notification on fresh critical events
- 6-tab frontend: Protection (hero + scan + update), Quarantine (vault
  manager + manual add), Realtime (stats + watch list + toggle), Rootkit
  (on-demand scan), Alerts (event log + clear), Settings (full editor)
- capabilities/default.json for dialog/notification/event permissions

Verified: 27/27 workspace tests, live E2E — EICAR dropped in ~/Downloads
auto-quarantined by the running daemon (critical event logged, file
removed from origin).
2026-08-20 20:33:44 -05:00

446 lines
16 KiB
CSS

:root {
/* surfaces */
--bg: #0B0E14;
--bg-raised: #12161F;
--bg-panel: #161B26;
--bg-hover: #1D2432;
--border: #232B3A;
--border-hi: #32405A;
/* text */
--fg: #E8ECF4;
--fg-dim: #8B96AB;
--fg-faint: #5A6478;
/* brand + state (dog head, 4-state ladder) */
--brand: #9896E0;
--ok: #22C55E;
--warn: #F59E0B;
--bad: #EF4444;
--off: #6B7280;
--radius: 14px;
--radius-sm: 9px;
--shadow: 0 8px 30px rgb(0 0 0 / 0.45);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body {
height: 100%;
background: radial-gradient(1200px 700px at 20% -10%, #141B2B 0%, var(--bg) 55%);
color: var(--fg);
font: 15px/1.5 "Inter", "Cantarell", "Segoe UI", system-ui, sans-serif;
-webkit-font-smoothing: antialiased;
}
#app {
max-width: 920px;
margin: 0 auto;
padding: 26px 26px 18px;
display: flex;
flex-direction: column;
gap: 18px;
min-height: 100vh;
}
/* ── Header ─────────────────────────────────────────────────────── */
.header {
display: flex;
align-items: center;
justify-content: space-between;
}
.brand { display: flex; align-items: center; gap: 12px; }
.brand-mark { width: 34px; height: 34px; filter: drop-shadow(0 2px 8px rgb(152 150 224 / .35)); }
.brand-text h1 { font-size: 20px; font-weight: 700; letter-spacing: .2px; }
.tagline { font-size: 12px; color: var(--fg-dim); }
.header-status { display: flex; align-items: center; gap: 8px; }
.engine-dot {
width: 9px; height: 9px; border-radius: 50%;
background: var(--off);
box-shadow: 0 0 8px rgb(107 114 128 / .6);
transition: background .3s, box-shadow .3s;
}
.engine-dot.ok { background: var(--ok); box-shadow: 0 0 10px rgb(34 197 94 / .7); }
.engine-dot.warn { background: var(--warn); box-shadow: 0 0 10px rgb(245 158 11 / .7); }
.engine-dot.bad { background: var(--bad); box-shadow: 0 0 10px rgb(239 68 68 / .7); }
.engine-label { font-size: 12.5px; color: var(--fg-dim); }
/* ── Tabs ───────────────────────────────────────────────────────── */
.tabs {
display: flex;
gap: 4px;
background: var(--bg-raised);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 5px;
overflow-x: auto;
}
.tab {
appearance: none;
border: 1px solid transparent;
background: transparent;
color: var(--fg-dim);
font: 600 13px/1 inherit;
border-radius: 10px;
padding: 9px 14px;
cursor: pointer;
display: inline-flex; align-items: center; gap: 7px;
white-space: nowrap;
transition: background .15s, color .15s, border-color .15s;
position: relative;
}
.tab:hover { background: var(--bg-hover); color: var(--fg); }
.tab.active {
background: linear-gradient(180deg, #232B40, #1B2233);
border-color: var(--border-hi);
color: var(--fg);
box-shadow: inset 0 1px 0 rgb(255 255 255 / .04);
}
.tab svg { opacity: .8; }
.tab-badge {
min-width: 18px;
font-size: 10.5px;
font-weight: 700;
text-align: center;
padding: 1px 5px;
border-radius: 999px;
background: var(--bad);
color: #fff;
line-height: 1.5;
}
.tab-badge.quiet { background: var(--warn); }
.tab-panel { display: none; flex-direction: column; gap: 18px; }
.tab-panel.active { display: flex; }
/* ── Hero ───────────────────────────────────────────────────────── */
.hero {
display: flex;
align-items: center;
gap: 22px;
background: linear-gradient(180deg, var(--bg-panel), var(--bg-raised));
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 24px 26px;
box-shadow: var(--shadow);
}
.shield {
flex: 0 0 auto;
width: 84px; height: 84px;
display: grid; place-items: center;
border-radius: 50%;
background: rgb(152 150 224 / .07);
border: 1px solid rgb(152 150 224 / .18);
transition: border-color .3s, background .3s;
}
@keyframes pulse {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.06); opacity: .85; }
}
.hero-copy h2 { font-size: 22px; font-weight: 700; }
.hero-copy p { color: var(--fg-dim); margin: 4px 0 12px; }
.hero[data-state="protected"] .shield { border-color: rgb(34 197 94 / .35); background: rgb(34 197 94 / .08); }
.hero[data-state="scanning"] .shield { border-color: rgb(245 158 11 / .4); background: rgb(245 158 11 / .09); }
.hero[data-state="threat"] .shield { border-color: rgb(239 68 68 / .45); background: rgb(239 68 68 / .1); }
.hero[data-state="paused"] .shield { border-color: rgb(107 114 128 / .4); background: rgb(107 114 128 / .09); }
.hero[data-state="scanning"] #shield-icon { animation: pulse 1.6s ease-in-out infinite; }
.pill {
display: inline-block;
font-size: 12px;
color: var(--fg-dim);
background: var(--bg-hover);
border: 1px solid var(--border);
border-radius: 999px;
padding: 3px 11px;
margin-right: 8px;
white-space: nowrap;
}
.pill.stale { color: var(--warn); border-color: rgb(245 158 11 / .4); }
/* ── Actions ────────────────────────────────────────────────────── */
.actions { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
.btn {
appearance: none;
border: 1px solid var(--border-hi);
background: var(--bg-hover);
color: var(--fg);
font: 600 13.5px/1 inherit;
border-radius: var(--radius-sm);
padding: 11px 16px;
cursor: pointer;
display: inline-flex; align-items: center; gap: 8px;
transition: background .15s, border-color .15s, transform .05s;
}
.btn:hover { background: #232C3E; border-color: #41507a; }
.btn:active { transform: translateY(1px); }
.btn:disabled { opacity: .5; cursor: default; }
.btn.primary {
background: linear-gradient(180deg, #5A58D6, #4543C4);
border-color: #6a68e6;
}
.btn.primary:hover { background: linear-gradient(180deg, #6765e0, #4f4dd4); }
.btn.small { padding: 7px 12px; font-size: 12.5px; }
.btn.danger { border-color: rgb(239 68 68 / .5); }
.btn.danger:hover { background: rgb(239 68 68 / .15); border-color: var(--bad); }
/* ── Panels ─────────────────────────────────────────────────────── */
.panel {
background: var(--bg-panel);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 16px 18px;
}
.panel-head {
display: flex;
align-items: baseline;
justify-content: space-between;
margin-bottom: 10px;
gap: 12px;
}
.panel-head h3 { font-size: 14px; font-weight: 700; letter-spacing: .3px; text-transform: uppercase; color: var(--fg-dim); }
.muted { color: var(--fg-dim); font-size: 13px; }
.empty { padding: 6px 0 2px; }
.hidden { display: none !important; }
.row-actions { display: flex; align-items: center; gap: 10px; }
.scan-target { font-family: ui-monospace, monospace; font-size: 12.5px; color: var(--fg-faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* scan progress */
.progress {
height: 8px;
border-radius: 999px;
background: var(--bg-hover);
overflow: hidden;
margin-bottom: 10px;
}
.progress-bar {
height: 100%;
width: 0%;
background: linear-gradient(90deg, #f59e0b, #fbbf24);
border-radius: inherit;
transition: width .4s ease;
}
.progress.indeterminate .progress-bar {
width: 35%;
animation: slide 1.3s ease-in-out infinite alternate;
}
@keyframes slide { from { margin-left: 0; } to { margin-left: 65%; } }
/* results */
.results { display: flex; flex-direction: column; gap: 6px; }
.result-row {
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
gap: 12px;
background: var(--bg-hover);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 10px 14px;
}
.result-row .sig { font-weight: 700; font-size: 13.5px; }
.result-row .path { color: var(--fg-dim); font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: ui-monospace, monospace; }
.result-row.clean { border-left: 3px solid var(--ok); }
.result-row.clean .sig { color: var(--ok); }
.result-row.infected { border-left: 3px solid var(--bad); }
.result-row.infected .sig { color: var(--bad); }
/* log */
.log {
background: #0A0D13;
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 12px 14px;
font: 12px/1.6 ui-monospace, "JetBrains Mono", monospace;
color: var(--fg-dim);
max-height: 220px;
overflow: auto;
white-space: pre-wrap;
}
.log.ok { color: #7fd79a; }
.log.fail { color: #f2a2a2; }
/* ── Inputs & switches ──────────────────────────────────────────── */
.input {
appearance: none;
background: var(--bg-raised);
border: 1px solid var(--border-hi);
border-radius: var(--radius-sm);
color: var(--fg);
font: 13.5px/1.4 inherit;
padding: 9px 12px;
width: 100%;
}
.input:focus { outline: none; border-color: var(--brand); box-shadow: 0 0 0 3px rgb(152 150 224 / .15); }
.input.mono { font-family: ui-monospace, "JetBrains Mono", monospace; font-size: 12.5px; }
textarea.input { resize: vertical; }
select.input { cursor: pointer; }
.form-row { display: grid; grid-template-columns: 1fr 200px auto; gap: 10px; }
.field { margin: 14px 0; }
.field > label { display: block; font-size: 13px; font-weight: 600; color: var(--fg-dim); margin-bottom: 6px; }
.switch-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
cursor: pointer;
padding: 4px 0;
}
.switch-row.setting { padding: 10px 0; border-bottom: 1px solid var(--border); }
.switch-row.setting:last-of-type { border-bottom: none; }
.switch-row strong { display: block; font-size: 14px; font-weight: 600; }
.switch-row small { display: block; color: var(--fg-dim); font-size: 12px; margin-top: 2px; }
.switch-row input[type="checkbox"] { position: absolute; opacity: 0; pointer-events: none; }
.switch {
flex: 0 0 auto;
width: 40px; height: 22px;
border-radius: 999px;
background: var(--bg-hover);
border: 1px solid var(--border-hi);
position: relative;
transition: background .2s, border-color .2s;
}
.switch::after {
content: "";
position: absolute;
top: 2px; left: 2px;
width: 16px; height: 16px;
border-radius: 50%;
background: var(--fg-dim);
transition: transform .2s, background .2s;
}
.switch-row input:checked + .switch { background: rgb(90 88 214 / .55); border-color: #6a68e6; }
.switch-row input:checked + .switch::after { transform: translateX(18px); background: #fff; }
/* ── Quarantine ─────────────────────────────────────────────────── */
.qt-list { display: flex; flex-direction: column; gap: 6px; }
.qt-row {
display: grid;
grid-template-columns: auto 1fr auto;
gap: 12px;
align-items: center;
background: var(--bg-hover);
border: 1px solid var(--border);
border-left: 3px solid var(--bad);
border-radius: var(--radius-sm);
padding: 10px 14px;
}
.qt-row.restored { border-left-color: var(--off); opacity: .75; }
.qt-row .qt-virus { font-weight: 700; font-size: 13px; color: var(--bad); white-space: nowrap; }
.qt-row.restored .qt-virus { color: var(--fg-dim); }
.qt-row .qt-path { font-family: ui-monospace, monospace; font-size: 12px; color: var(--fg-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.qt-row .qt-meta { font-size: 11.5px; color: var(--fg-faint); white-space: nowrap; }
.qt-row .qt-actions { display: flex; gap: 6px; }
/* ── Realtime ───────────────────────────────────────────────────── */
.stat-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
margin: 12px 0 6px;
}
.stat {
background: var(--bg-raised);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 12px 14px;
display: flex;
flex-direction: column;
gap: 2px;
}
.stat-val { font-size: 20px; font-weight: 700; font-variant-numeric: tabular-nums; }
.stat-val.alarm { color: var(--bad); }
.stat-val.watching { color: var(--warn); }
.stat-val.calm { color: var(--ok); }
.stat-key { font-size: 11.5px; color: var(--fg-faint); text-transform: uppercase; letter-spacing: .4px; }
.kv {
display: flex;
gap: 12px;
padding: 8px 0;
border-top: 1px solid var(--border);
font-size: 13px;
}
.kv-k { flex: 0 0 150px; color: var(--fg-faint); }
.kv-v { color: var(--fg-dim); font-family: ui-monospace, monospace; font-size: 12px; word-break: break-all; }
.watch-list { display: flex; flex-wrap: wrap; gap: 6px; }
.watch-chip {
font-family: ui-monospace, monospace;
font-size: 12px;
color: var(--fg-dim);
background: var(--bg-hover);
border: 1px solid var(--border);
border-radius: 999px;
padding: 4px 11px;
}
/* ── Rootkit ────────────────────────────────────────────────────── */
.verdict {
border-radius: var(--radius-sm);
padding: 12px 16px;
margin-bottom: 12px;
font-weight: 600;
font-size: 14px;
border: 1px solid;
}
.verdict.clean { background: rgb(34 197 94 / .08); border-color: rgb(34 197 94 / .35); color: #7fd79a; }
.verdict.dirty { background: rgb(239 68 68 / .08); border-color: rgb(239 68 68 / .4); color: #f2a2a2; }
.finding-list { display: flex; flex-direction: column; gap: 6px; }
.finding-row {
display: grid;
grid-template-columns: auto auto 1fr;
gap: 12px;
align-items: center;
background: var(--bg-hover);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 10px 14px;
}
.finding-row .sev {
font-size: 10.5px; font-weight: 700;
text-transform: uppercase; letter-spacing: .5px;
padding: 3px 9px; border-radius: 999px;
}
.sev.critical { background: rgb(239 68 68 / .15); color: var(--bad); }
.sev.warn { background: rgb(245 158 11 / .15); color: var(--warn); }
.sev.info { background: rgb(107 114 128 / .2); color: var(--fg-dim); }
.finding-row .check { font-family: ui-monospace, monospace; font-size: 12px; color: var(--fg-dim); }
.finding-row .detail { font-size: 13px; color: var(--fg); word-break: break-all; }
/* ── Alerts ─────────────────────────────────────────────────────── */
.event-list { display: flex; flex-direction: column; gap: 4px; max-height: 520px; overflow-y: auto; }
.event-row {
display: grid;
grid-template-columns: auto auto 1fr auto;
gap: 12px;
align-items: center;
border-radius: var(--radius-sm);
padding: 8px 14px;
background: var(--bg-hover);
border: 1px solid var(--border);
}
.event-row .ev-sev { font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .5px; width: 64px; }
.ev-sev.critical { color: var(--bad); }
.ev-sev.warn { color: var(--warn); }
.ev-sev.info { color: var(--fg-faint); }
.event-row .ev-kind { font-family: ui-monospace, monospace; font-size: 11.5px; color: var(--fg-dim); width: 96px; }
.event-row .ev-msg { font-size: 13px; color: var(--fg); word-break: break-word; }
.event-row .ev-ts { font-size: 11.5px; color: var(--fg-faint); white-space: nowrap; font-variant-numeric: tabular-nums; }
/* footer */
.footer {
margin-top: auto;
display: flex;
justify-content: space-between;
font-size: 12px;
color: var(--fg-faint);
padding-top: 4px;
}