: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); --bg-log: #0A0D13; } /* Light theme. * * Not an inversion of the dark palette — a flat inversion turns a deep navy * ground into a saturated cream and makes the brand violet unreadable. The * neutrals keep a slight violet bias so they read as chosen rather than as * the browser's default grey, and the state colours are darkened until they * hold their contrast against white rather than glowing on it. * * Applied by [data-theme] only. The app writes that attribute from the * user's setting, resolving "auto" against prefers-color-scheme itself, so * there is exactly one place that decides which theme is showing. */ :root[data-theme="light"] { --bg: #F7F7FB; --bg-raised: #FFFFFF; --bg-panel: #FFFFFF; --bg-hover: #EEEEF6; --border: #E0E0EC; --border-hi: #C6C6DC; --fg: #1A1B26; --fg-dim: #5A5C72; --fg-faint: #8A8CA3; /* Darkened for contrast on a light ground: the dark theme's #22C55E is 2.2:1 on white and unreadable as text. */ --brand: #5A58C8; --ok: #16A34A; --warn: #B45309; --bad: #DC2626; --off: #6B7280; --shadow: 0 8px 24px rgb(26 27 38 / 0.10); /* A console keeps its recessed feel in light mode without going black. */ --bg-log: #F0F0F7; } * { 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: var(--bg-log); 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; }