<!DOCTYPE html>
<html><head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="lib/react.production.min.js"></script>
<script src="lib/react-dom.production.min.js"></script>
<script src="support.js"></script>

</head>
<body>
<template id="__bundler_thumbnail">
<svg viewBox="0 0 1200 800" xmlns="http://www.w3.org/2000/svg">
  <rect x="360" y="220" width="480" height="360" rx="48" fill="#2563EB"></rect>
  <path d="M600 300l90 38v64c0 58-38 103-90 129-52-26-90-71-90-129v-64z" fill="none" stroke="#FFFFFF" stroke-width="22" stroke-linejoin="round"></path>
  <path d="M560 415l28 28 56-56" fill="none" stroke="#FFFFFF" stroke-width="22" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
</template>
<x-dc>
<helmet>
<style>
:root{
  --accent:#2563EB; --accent-hover:#1D4ED8; --accent-dim:rgba(37,99,235,.08);
  --accent-soft:#EAF1FD; --accent-ink:#0C447C; --accent-line:#B5D4F4;
  --brand:#1257D6; --brand-hover:#0F49B5; --brand-tint:#F3F8FF; --brand-border:#D3E3FA;
  --desk:#E7EAEF; --bg:#F7F8FA; --surface:#EFF1F5; --card:#FFFFFF; --card-hover:#F4F6F9;
  --icon-bg:#F1F3F6;
  --titlebar:#FFFFFF; --sidebar:#FFFFFF;
  --text-1:#151D2C; --text-2:#3D4757; --text-3:#6B7280;
  --border:#E3E6EB; --border-strong:#C9D0D9;
  --success:#188038; --success-bg:rgba(24,128,56,.08); --success-line:rgba(24,128,56,.35);
  --warning:#B26A00; --warning-bg:rgba(178,106,0,.09); --warning-line:rgba(178,106,0,.35);
  --danger:#D93025; --danger-bg:rgba(217,48,37,.08); --danger-ink:#8a1c14; --danger-line:rgba(217,48,37,.35);
  --info:#2563EB; --info-bg:rgba(37,99,235,.08);
  --hover:rgba(21,29,44,.05);
  --shadow-sm:0 1px 2px rgba(16,24,40,.05);
  --shadow-lg:0 16px 48px rgba(16,24,40,.16);
  --on-accent:#FFFFFF; --on-brand:#FFFFFF;
}
[data-tq="carbon"]{
  --accent:#5B9DFF; --accent-hover:#7BB2FF; --accent-dim:rgba(91,157,255,.14);
  --accent-soft:rgba(91,157,255,.22); --accent-ink:#B0CBFF; --accent-line:rgba(91,157,255,.45);
  --brand:#4A87E8; --brand-hover:#6098EF; --brand-tint:rgba(74,135,232,.10); --brand-border:rgba(74,135,232,.28);
  --desk:#09090B; --bg:#111114; --surface:#18181C; --card:#1B1B1F; --card-hover:#232328;
  --icon-bg:#2C2C33;
  --titlebar:#131316; --sidebar:#141417;
  --text-1:#FFFFFF; --text-2:#E4E4EA; --text-3:#CFCFD6;
  --border:#2A2A30; --border-strong:#3D3D45;
  --success:#3EBB6A; --success-bg:rgba(62,187,106,.06); --success-line:rgba(62,187,106,.30);
  --warning:#E9B65A; --warning-bg:rgba(233,182,90,.08); --warning-line:rgba(233,182,90,.32);
  --danger:#F87171; --danger-bg:rgba(248,113,113,.09); --danger-ink:#FCA5A5; --danger-line:rgba(248,113,113,.36);
  --info:#5B9DFF; --info-bg:rgba(91,157,255,.14);
  --hover:rgba(232,232,236,.06);
  --shadow-sm:none; --shadow-lg:0 24px 64px rgba(0,0,0,.6);
  --on-accent:#0C0C0E; --on-brand:#FFFFFF;
}
*{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%}
body{font-family:"Segoe UI","Segoe UI Variable Text",system-ui,-apple-system,sans-serif;font-size:13px;line-height:1.55;color:#3D4757;-webkit-font-smoothing:antialiased}
::-webkit-scrollbar{width:10px;height:10px}
::-webkit-scrollbar-thumb{background:var(--border-strong);border-radius:8px;border:2px solid transparent;background-clip:content-box}
::-webkit-scrollbar-track{background:transparent}
@keyframes tqFade{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
/* Push-button feedback — every button visibly "presses in" on click so the user always knows the
   tap registered (fixes the "did it click?" uncertainty on the restore filters + action buttons). */
button{transition:transform .06s ease,filter .06s ease}
button:active{transform:scale(.95);filter:brightness(.92)}
/* Sticky column headers — thead stays glued to the top of the scroll container so users always know
   which column is which as they scroll long tables (Logs, Quarantine, Scan history, USB events). */
thead.tq-stky th{position:sticky;top:0;z-index:2;background:var(--card);box-shadow:inset 0 -1px 0 var(--border)}
@keyframes tqToast{from{opacity:0;transform:translateX(32px)}to{opacity:1;transform:none}}
@keyframes tqPulse{0%,100%{opacity:1}50%{opacity:.45}}
/* ---- sidebar rail (manual toggle via hamburger; JS also flips it on narrow first-load) ----
   Width comes from these class-based rules only — the inline width was breaking the transition
   (inline styles win over class rules, so the sidebar jumped instead of sliding). */
.tqside{width:212px;transition:width .22s cubic-bezier(.2,.8,.2,1);overflow:hidden}
.tqside.rail{width:62px}
.tqside.rail .tqlabel{display:none}
.tqside.rail .tqlogo{justify-content:center;padding-left:0;padding-right:0}
.tqside.rail nav a{justify-content:center;padding-left:0;padding-right:0;gap:0}
.tqside.rail nav a span{display:none}
@media (max-width:980px){ .tqcontent{padding:10px !important} }

/* Settings v2 — 5-section layout */
.tqset-block{margin-top:20px}
.tqset-block:first-of-type{margin-top:4px}
.tqset-hd{display:flex;align-items:center;gap:12px;padding:2px 4px 10px}
.tqset-ic{width:32px;height:32px;border-radius:8px;background:var(--sc);color:#fff;display:flex;align-items:center;justify-content:center;flex:none;box-shadow:0 1px 0 rgba(255,255,255,.15) inset}
.tqset-ic svg{width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.tqset-t{font-size:14.5px;font-weight:700;color:var(--text-1);letter-spacing:-.005em;line-height:1.25}
.tqset-st{font-size:11.5px;color:var(--text-3);margin-top:1px;line-height:1.25}
.tqset-grp{background:var(--card);border:1px solid var(--border);border-top:2px solid var(--sc);border-radius:8px;padding:2px 16px;box-shadow:var(--shadow-sm)}
.tqset-row{display:flex;align-items:center;gap:11px;padding:10px 0;border-bottom:1px solid var(--border)}
.tqset-row:last-child{border-bottom:none}
.tqset-rb{flex:1;line-height:1.35;min-width:0}
.tqset-nm{font-size:12.5px;font-weight:600;color:var(--text-1)}
.tqset-sb{font-size:11px;color:var(--text-3);margin-top:1px}
.tqset-gl{font-size:9.5px;font-weight:700;letter-spacing:.7px;color:var(--text-3);text-transform:uppercase;padding:14px 4px 6px;display:flex;align-items:center;gap:8px}
.tqset-gl::before,.tqset-gl::after{content:'';flex:1;height:1px;background:var(--border)}
.tqset-gl::before{max-width:22px}
.tqset-accic{width:28px;height:28px;border-radius:7px;background:var(--icon-bg);color:var(--sc);display:flex;align-items:center;justify-content:center;flex:none}
[data-tq="carbon"] .tqset-accic{color:#CBD5E1;background:rgba(203,213,225,.10)}
.tqset-accic svg{width:15px;height:15px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.tqset-acc{background:var(--card);border:1px solid var(--border);border-radius:8px;overflow:hidden;margin-bottom:10px;box-shadow:var(--shadow-sm)}
.tqset-achd{display:flex;align-items:center;gap:11px;padding:12px 14px;cursor:pointer;user-select:none;color:var(--text-1)}
.tqset-acnm{font-size:12.5px;font-weight:600;flex:1;display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.tqset-pill{font-size:10px;font-weight:600;padding:1px 7px;border-radius:99px;background:var(--brand-tint);color:var(--brand)}
/* GlassWire — 3 always-on effects: A (shimmer sweep) · C (pin auras) · H (scroll) */
.gw-anim-area{transform-box:fill-box;transform-origin:bottom;will-change:transform;animation:gwScroll 12s linear infinite}
.gw-anim-line{transform-box:fill-box;transform-origin:bottom;will-change:transform;animation:gwScroll 12s linear infinite}
/* A · Shimmer sweep — theme-adaptive gradient (dark=white flash, light=green sweep) */
@keyframes gwShimmerSweep{0%{transform:translateX(-400px)}100%{transform:translateX(1600px)}}
.gw-shimmer{fill:url(#gwShimmerLight);animation:gwShimmerSweep 4s linear infinite}
[data-tq="carbon"] .gw-shimmer{fill:url(#gwShimmerDark)}
/* C · Pin auras — expanding red rings from each threat pin */
@keyframes gwPinAura{0%{transform:scale(1);opacity:.7}100%{transform:scale(2.6);opacity:0}}
.gw-pin-aura{transform-box:fill-box;transform-origin:center;animation:gwPinAura 2.4s ease-out infinite}
/* H · Real-time scroll — mountain slides left continuously */
@keyframes gwScroll{0%{transform:translateX(0)}100%{transform:translateX(-100px)}}
/* Slim horizontal "This PC" tile — icon · label · value · sub on one row */
.d-slim{display:flex;align-items:center;gap:10px;padding:7px 12px;background:var(--card);border:1px solid var(--border);border-radius:8px;box-shadow:var(--shadow-sm)}
.d-slim-ic{width:26px;height:26px;border-radius:6px;background:var(--icon-bg);color:var(--text-2);display:flex;align-items:center;justify-content:center;flex:none}
.d-slim-ic svg{width:13px;height:13px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.d-slim-lbl{font-size:10px;font-weight:700;letter-spacing:.5px;color:var(--text-3);text-transform:uppercase;flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.d-slim-val{font-size:13px;font-weight:700;line-height:1;color:var(--text-1);white-space:nowrap;flex:none}
.d-slim-sub{font-size:10.5px;line-height:1.2;padding-left:10px;margin-left:2px;border-left:1px solid var(--border);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:130px}
/* GlassWire — solid red pin-style icon tile (high contrast in both themes) */
.gw-red-tile{background:#DC2626;color:#fff}
[data-tq="carbon"] .gw-red-tile{background:#F87171;color:#fff}
/* GlassWire — brighter red category chip (adaptive text pops in dark mode) */
.gw-red-chip{background:rgba(220,38,38,.14);color:#DC2626}
[data-tq="carbon"] .gw-red-chip{background:rgba(248,113,113,.22);color:#F87171}
/* Tool-window mode — index.html loaded as ?tool=logs|quarantine|whitelist by LogsWindow /
   QuarantineWindow / WhitelistWindow. Hides the sidebar and titlebar chrome so the tool window
   is just the target page's content, matching the standalone-window feel while reusing all of
   the main app's polished rendering. Data-tool set from the constructor before first paint. */
html[data-tool] aside.tqside { display:none !important; }
html[data-tool] .tq-titlebar-slot { display:none !important; }
/* Update-progress dots — REPLACES the progress bar entirely on the Updates page. Motion signals
   "alive"; a percentage would only fuel "why is it stuck at 47%" reactions. Three dots pulse in
   sequence on a staggered 1.4 s cycle so the sequence reads left-to-right. Two sizes:
   .tq-dots           — small inline (12 px height) — reserved slot for future compact use
   .tq-dots.big       — large centered (10 px dots) — the main downloader indicator
   Colour uses --accent so it matches the brand blue, not the muted --text-3 (louder = more
   reassuring in this context). */
.tq-dots{display:inline-flex;align-items:center;gap:3px;height:8px}
.tq-dots span{display:inline-block;width:5px;height:5px;background:var(--accent);border-radius:50%;animation:tqBlink 1.4s infinite both}
.tq-dots.big{gap:8px;height:14px}
.tq-dots.big span{width:10px;height:10px}
.tq-dots span:nth-child(2){animation-delay:.2s}
.tq-dots span:nth-child(3){animation-delay:.4s}
@keyframes tqBlink{0%,80%,100%{opacity:.2;transform:scale(.85)}40%{opacity:1;transform:scale(1)}}
</style>
</helmet>

<svg width="0" height="0" style="position:absolute" aria-hidden="true"><defs>
<symbol id="i-dash" viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="9" rx="1.5"></rect><rect x="14" y="3" width="7" height="5" rx="1.5"></rect><rect x="14" y="12" width="7" height="9" rx="1.5"></rect><rect x="3" y="16" width="7" height="5" rx="1.5"></rect></symbol>
<symbol id="i-scan" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"></circle><path d="M21 21l-4-4"></path><path d="M11 8v6M8 11h6"></path></symbol>
<symbol id="i-shield" viewBox="0 0 24 24"><path d="M12 3l7 3v5c0 4.5-3 8-7 10-4-2-7-5.5-7-10V6z"></path><path d="M9 12l2 2 4-4"></path></symbol>
<symbol id="i-lock" viewBox="0 0 24 24"><rect x="4" y="10" width="16" height="11" rx="2"></rect><path d="M8 10V7a4 4 0 0 1 8 0v3"></path></symbol>
<symbol id="i-update" viewBox="0 0 24 24"><path d="M4 12a8 8 0 0 1 14-5l2 2"></path><path d="M20 12a8 8 0 0 1-14 5l-2-2"></path><path d="M18 4v5h-5M6 20v-5h5"></path></symbol>
<symbol id="i-logs" viewBox="0 0 24 24"><path d="M5 4h14v16H5z"></path><path d="M8 8h8M8 12h8M8 16h5"></path></symbol>
<symbol id="i-gear" viewBox="0 0 24 24"><path d="M4 8h10M18 8h2M4 16h2M10 16h10"></path><circle cx="15" cy="8" r="2.5"></circle><circle cx="8" cy="16" r="2.5"></circle></symbol>
<symbol id="i-info" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"></circle><path d="M12 11v5M12 8h.01"></path></symbol>
<symbol id="i-bolt" viewBox="0 0 24 24"><path d="M13 3L5 13h6l-1 8 8-11h-6z"></path></symbol>
<symbol id="i-disk" viewBox="0 0 24 24"><rect x="3" y="6" width="18" height="12" rx="2"></rect><circle cx="8" cy="12" r="2.5"></circle><path d="M14 10h4M14 14h4"></path></symbol>
<symbol id="i-folder" viewBox="0 0 24 24"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h6a2 2 0 0 1 2 2v3"></path><circle cx="15" cy="15" r="4"></circle><path d="M21 21l-3-3"></path></symbol>
<symbol id="i-cpu" viewBox="0 0 24 24"><rect x="7" y="7" width="10" height="10" rx="2"></rect><path d="M10 3v2M14 3v2M10 19v2M14 19v2M3 10h2M3 14h2M19 10h2M19 14h2"></path></symbol>
<symbol id="i-refresh" viewBox="0 0 24 24"><path d="M23 4v6h-6"></path><path d="M1 20v-6h6"></path><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10"></path><path d="M20.49 15a9 9 0 0 1-14.85 3.36L1 14"></path></symbol>
<symbol id="i-flask" viewBox="0 0 24 24"><path d="M9 3h6M10 3v6l-5 8a2 2 0 0 0 2 3h10a2 2 0 0 0 2-3l-5-8V3"></path><path d="M7.5 15h9"></path></symbol>
<symbol id="i-box" viewBox="0 0 24 24"><path d="M3 8l9-5 9 5v8l-9 5-9-5z"></path><path d="M3 8l9 5 9-5M12 13v8"></path></symbol>
<symbol id="i-key" viewBox="0 0 24 24"><circle cx="8" cy="8" r="4"></circle><path d="M11 11l8 8M16 16l2-2M18 18l2-2"></path></symbol>
<symbol id="i-gauge" viewBox="0 0 24 24"><path d="M4 18a8 8 0 1 1 16 0"></path><path d="M12 18l4-5"></path></symbol>
<symbol id="i-bug" viewBox="0 0 24 24"><rect x="8" y="7" width="8" height="11" rx="4"></rect><path d="M8 11H4M8 15H4M16 11h4M16 15h4M9 7l-1-3M15 7l1-3"></path></symbol>
<symbol id="i-trash" viewBox="0 0 24 24"><path d="M4 7h16M9 7V5a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2M6 7l1 13h10l1-13"></path></symbol>
<symbol id="i-restore" viewBox="0 0 24 24"><path d="M4 12a8 8 0 1 0 3-6"></path><path d="M4 4v4h4"></path></symbol>
<symbol id="i-usb" viewBox="0 0 24 24"><path d="M12 3v14"></path><circle cx="12" cy="3" r="1.5"></circle><rect x="8" y="17" width="8" height="5" rx="1"></rect><path d="M8 10l-2 2h-2M16 8l2-2h2"></path></symbol>
<symbol id="i-pause" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"></circle><path d="M10 8v8M14 8v8"></path></symbol>
<symbol id="i-plug" viewBox="0 0 24 24"><path d="M9 3v6M15 3v6M7 9h10v3a5 5 0 0 1-10 0zM12 17v4"></path></symbol>
<symbol id="i-syringe" viewBox="0 0 24 24"><path d="M16 2l6 6M17 3l-9 9M12 8l4 4M8 12l-3 3v2h2l3-3M3 21l3-3"></path></symbol>
<symbol id="i-check" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"></circle><path d="M8 12l3 3 5-6"></path></symbol>
<symbol id="i-alert" viewBox="0 0 24 24"><path d="M12 4l9 16H3z"></path><path d="M12 10v4M12 17h.01"></path></symbol>
<symbol id="i-clock" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"></circle><path d="M12 7v5l3 2"></path></symbol>
<symbol id="i-menu" viewBox="0 0 24 24"><line x1="3" y1="6" x2="21" y2="6"></line><line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="18" x2="21" y2="18"></line></symbol>
<symbol id="i-chev" viewBox="0 0 24 24"><path d="M9 6l6 6-6 6"></path></symbol>
<symbol id="i-min" viewBox="0 0 24 24"><path d="M5 12h14"></path></symbol>
<symbol id="i-max" viewBox="0 0 24 24"><rect x="6" y="6" width="12" height="12" rx="1"></rect></symbol>
<symbol id="i-close" viewBox="0 0 24 24"><path d="M6 6l12 12M18 6L6 18"></path></symbol>
<symbol id="i-monitor" viewBox="0 0 24 24"><rect x="2" y="3" width="20" height="14" rx="2"></rect><line x1="8" y1="21" x2="16" y2="21"></line><line x1="12" y1="17" x2="12" y2="21"></line></symbol>
<symbol id="i-play" viewBox="0 0 24 24"><polygon points="5 3 19 12 5 21 5 3"></polygon></symbol>
<symbol id="i-usb" viewBox="0 0 24 24"><rect x="9" y="2" width="6" height="4" rx="1"></rect><rect x="6" y="6" width="12" height="16" rx="2"></rect><circle cx="12" cy="17" r="1.5"></circle></symbol>
<symbol id="i-file" viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline></symbol>
<symbol id="i-cat-ransomware" viewBox="0 0 24 24"><path d="M7 10V7a5 5 0 0 1 10 0v3"></path><rect x="4" y="10" width="16" height="12" rx="1.5"></rect><circle cx="9" cy="15" r="1.4" fill="currentColor" stroke="none"></circle><circle cx="15" cy="15" r="1.4" fill="currentColor" stroke="none"></circle><path d="M9 18l1 2M11 18l0 2M13 18l0 2M15 18l-1 2"></path></symbol>
<symbol id="i-cat-backdoor" viewBox="0 0 24 24"><rect x="4" y="3" width="12" height="18" rx="1"></rect><path d="M16 8h5v9h-5"></path><path d="M17 12l4-4M17 12l4 4"></path><circle cx="10" cy="12" r="2.5"></circle><path d="M9 11l2 0" stroke-width="2.5"></path></symbol>
<symbol id="i-cat-trojan" viewBox="0 0 24 24"><path d="M3 18l3-7 4-1 1 1 4-3 4 1v3l-2 2v6"></path><path d="M6 18v3M13 18v3M17 18v3"></path><circle cx="17" cy="8" r="1.2" fill="currentColor" stroke="none"></circle><path d="M15 12l-1 2M17 12l-1 2M19 12l-1 2"></path></symbol>
<symbol id="i-cat-worm" viewBox="0 0 24 24"><path d="M2 14c1.5-3 3-3 4.5 0s3 3 4.5 0"></path><circle cx="16" cy="12" r="4.5"></circle><circle cx="16" cy="12" r="2"></circle><path d="M12 12l1 0M16 8l0 1M20 12l-1 0M16 16l0-1M13.5 9.5l.7.7M18.5 9.5l-.7.7M13.5 14.5l.7-.7M18.5 14.5l-.7-.7"></path></symbol>
<symbol id="i-cat-virus" viewBox="0 0 24 24"><circle cx="12" cy="12" r="3.5"></circle><path d="M12 8.5L10 5L14 5Z" fill="currentColor"></path><path d="M12 15.5L10 19L14 19Z" fill="currentColor"></path><path d="M8.5 12L5 10L5 14Z" fill="currentColor"></path><path d="M15.5 12L19 10L19 14Z" fill="currentColor"></path><path d="M9.5 9.5L7 7L10 7Z" fill="currentColor"></path><path d="M14.5 9.5L17 7L14 7Z" fill="currentColor"></path><path d="M9.5 14.5L7 17L10 17Z" fill="currentColor"></path><path d="M14.5 14.5L17 17L14 17Z" fill="currentColor"></path></symbol>
<symbol id="i-cat-spyware" viewBox="0 0 24 24"><path d="M2 12s4-7 10-7 10 7 10 7-4 7-10 7S2 12 2 12z"></path><circle cx="12" cy="12" r="4.5"></circle><ellipse cx="12" cy="12" rx="1" ry="4" fill="currentColor" stroke="none"></ellipse></symbol>
<symbol id="i-cat-miner" viewBox="0 0 24 24"><path d="M13 3l-3 3 6 6 3-3z"></path><line x1="10" y1="14" x2="14" y2="18"></line><path d="M4 17c0-2.5 2-4.5 4.5-4.5s4.5 2 4.5 4.5v2l-1 1v1H10v-1H8v1H6.5v-1L5.5 19v-2z"></path><circle cx="7" cy="17" r=".7" fill="currentColor" stroke="none"></circle><circle cx="10" cy="17" r=".7" fill="currentColor" stroke="none"></circle></symbol>
<symbol id="i-cat-script" viewBox="0 0 24 24"><rect x="2" y="4" width="20" height="16" rx="1.5"></rect><path d="M2 8h20"></path><circle cx="4.5" cy="6" r=".6" fill="currentColor" stroke="none"></circle><circle cx="6.5" cy="6" r=".6" fill="currentColor" stroke="none"></circle><circle cx="8.5" cy="6" r=".6" fill="currentColor" stroke="none"></circle><path d="M5 12l3 2-3 2"></path><path d="M14 11 L10 17 L18 17 Z"></path><line x1="14" y1="13" x2="14" y2="15"></line><circle cx="14" cy="16" r=".3" fill="currentColor" stroke="none"></circle></symbol>
<symbol id="i-cat-adware" viewBox="0 0 24 24"><path d="M3 10v4l8 4V6z"></path><path d="M13 5v14"></path><path d="M15 8l1 2-1 2 1 2-1 2M18 6l1 2-1 2 1 2-1 2 1 2-1 2"></path></symbol>
<symbol id="i-cat-generic" viewBox="0 0 24 24"><path d="M5 10c0-4 3-7 7-7s7 3 7 7v3l-1.5 1.5V17h-2v-1.5h-1.5V17h-2v-1.5h-1.5V17H8.5v-2.5L7 13z"></path><circle cx="9.5" cy="11" r="1.4" fill="currentColor" stroke="none"></circle><circle cx="14.5" cy="11" r="1.4" fill="currentColor" stroke="none"></circle><path d="M4 19l6 3M20 19l-6 3"></path><circle cx="4" cy="19" r=".8" fill="currentColor" stroke="none"></circle><circle cx="20" cy="19" r=".8" fill="currentColor" stroke="none"></circle><circle cx="10" cy="22" r=".8" fill="currentColor" stroke="none"></circle><circle cx="14" cy="22" r=".8" fill="currentColor" stroke="none"></circle></symbol>
</defs></svg>

<div data-tq="{{ theme }}" ref="{{ rootRef }}" style="height:100vh;display:flex;background:var(--bg);transition:background .25s ease">
<div style="width:100%;height:100vh;background:var(--bg);border:none;border-radius:0;box-shadow:none;overflow:hidden;display:flex;flex-direction:column">

  <!-- Windows titlebar (hidden: the native OS window provides the real one) -->
  <div style="display:none">
    <div style="display:flex;align-items:center;gap:8px;padding:0 12px">
      <div style="width:16px;height:16px;border-radius:4px;background:var(--accent);display:flex;align-items:center;justify-content:center;color:var(--on-accent)"><svg style="width:10px;height:10px;stroke:currentColor;fill:none;stroke-width:2.4;stroke-linecap:round;stroke-linejoin:round"><use href="#i-shield"></use></svg></div>
      <span style="font-size:12px;font-weight:600;color:var(--text-1)">TreeIQ Anti-Malware</span>
    </div>
    <div style="flex:1"></div>
    <div style="display:flex;height:100%">
      <button style="width:46px;height:100%;border:none;background:transparent;color:var(--text-2);cursor:pointer;display:flex;align-items:center;justify-content:center" style-hover="background:var(--hover)"><svg style="width:13px;height:13px;stroke:currentColor;fill:none;stroke-width:1.6;stroke-linecap:round"><use href="#i-min"></use></svg></button>
      <button style="width:46px;height:100%;border:none;background:transparent;color:var(--text-2);cursor:pointer;display:flex;align-items:center;justify-content:center" style-hover="background:var(--hover)"><svg style="width:12px;height:12px;stroke:currentColor;fill:none;stroke-width:1.6"><use href="#i-max"></use></svg></button>
      <button style="width:46px;height:100%;border:none;background:transparent;color:var(--text-2);cursor:pointer;display:flex;align-items:center;justify-content:center" style-hover="background:#E81123;color:#fff"><svg style="width:13px;height:13px;stroke:currentColor;fill:none;stroke-width:1.6;stroke-linecap:round"><use href="#i-close"></use></svg></button>
    </div>
  </div>

  <div style="flex:1;display:flex;min-height:0">
    <!-- Sidebar -->
    <aside class="tqside {{ sideCls }}" style="flex:none;background:var(--sidebar);border-right:1px solid var(--border);display:flex;flex-direction:column;padding:12px 8px 10px">
      <div class="tqlogo" style="display:flex;align-items:center;gap:10px;padding:4px 10px 14px">
        <svg xmlns="http://www.w3.org/2000/svg" width="34" height="34" viewBox="0 0 100 100" shape-rendering="geometricPrecision" style="display:block;flex:none"><rect x="0" y="0" width="100" height="100" rx="24" fill="#1257D6"/><rect x="24" y="26" width="52" height="13" rx="4" fill="#FFFFFF"/><rect x="43.5" y="45" width="13" height="31" rx="4" fill="#FFFFFF"/></svg>
        <div class="tqlabel" style="line-height:1.2">
          <div style="font-weight:700;color:var(--text-1);font-size:13px">TreeIQ</div>
          <div style="font-size:10px;color:var(--text-3);letter-spacing:.6px;text-transform:uppercase">Anti-Malware</div>
        </div>
      </div>
      <nav style="display:flex;flex-direction:column;gap:1px">
        <sc-for list="{{ navMain }}" as="n" hint-placeholder-count="6">
          <a onclick="{{ n.select }}" style="display:flex;align-items:center;gap:10px;height:32px;padding:0 10px;border-radius:6px;font-size:12.5px;font-weight:{{ n.fw }};color:{{ n.color }};background:{{ n.bg }};cursor:pointer;user-select:none" style-hover="background:var(--accent-dim)">
            <svg style="width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;flex:none"><use href="{{ n.icon }}"></use></svg>
            <span style="flex:1">{{ n.label }}</span>
            <sc-if value="{{ n.count }}" hint-placeholder-val="{{ false }}"><span style="font-size:10.5px;font-weight:700;padding:1px 7px;border-radius:99px;background:var(--danger-bg);color:var(--danger)">{{ n.count }}</span></sc-if>
          </a>
        </sc-for>
        <div style="height:1px;background:var(--border);margin:8px 6px"></div>
        <sc-for list="{{ navSys }}" as="n" hint-placeholder-count="2">
          <a onclick="{{ n.select }}" style="display:flex;align-items:center;gap:10px;height:32px;padding:0 10px;border-radius:6px;font-size:12.5px;font-weight:{{ n.fw }};color:{{ n.color }};background:{{ n.bg }};cursor:pointer;user-select:none" style-hover="background:var(--accent-dim)">
            <svg style="width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;flex:none"><use href="{{ n.icon }}"></use></svg>
            <span>{{ n.label }}</span>
          </a>
        </sc-for>
      </nav>
      <div class="tqlabel" style="margin-top:auto;padding:10px;font-size:10.5px;color:var(--text-3);line-height:1.6;border-top:1px solid var(--border)">TreeIQ v{{ productVersion }}<br>Virus DB: {{ dbVersion }}<br><span style="color:{{ licPillFg }};font-weight:600">{{ licPillLabel }}</span></div>
    </aside>

    <!-- Main -->
    <div style="flex:1;display:flex;flex-direction:column;min-width:0">
      <div style="height:50px;flex:none;display:flex;align-items:center;gap:12px;padding:0 20px;border-bottom:1px solid var(--border);background:var(--titlebar)">
        <button onclick="{{ toggleRail }}" title="Toggle sidebar" aria-label="Toggle sidebar" style="width:26px;height:26px;border-radius:6px;border:1px solid var(--border);background:var(--card);cursor:pointer;color:var(--text-2);display:flex;align-items:center;justify-content:center;padding:0"><svg style="width:13px;height:13px;stroke:currentColor;fill:none;stroke-width:2.2;stroke-linecap:round"><use href="#i-menu"></use></svg></button>
        <h1 style="font-size:15px;font-weight:700;color:var(--text-1)">{{ pageTitle }}</h1>
        <div style="flex:1"></div>
        <div style="display:flex;align-items:center;gap:6px;padding:4px 11px;border-radius:99px;font-weight:600;font-size:11.5px;color:{{ statusFg }};background:{{ statusBg }}">
          <span style="width:7px;height:7px;border-radius:50%;background:currentColor"></span>{{ statusText }}
        </div>
        <div style="display:flex;background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:2px" title="UI size">
          <sc-for list="{{ scaleBtns }}" as="c" hint-placeholder-count="3">
            <button onclick="{{ c.select }}" title="{{ c.title }}" style="border:none;padding:3px 11px;border-radius:6px;cursor:pointer;font-size:11px;font-weight:700;background:{{ c.bg }};color:{{ c.color }}">{{ c.label }}</button>
          </sc-for>
        </div>
        <div style="display:flex;background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:2px">
          <sc-for list="{{ themeBtns }}" as="t" hint-placeholder-count="2">
            <button onclick="{{ t.select }}" style="border:none;padding:3px 12px;border-radius:6px;cursor:pointer;font-size:11px;font-weight:600;background:{{ t.bg }};color:{{ t.color }}">{{ t.label }}</button>
          </sc-for>
        </div>
      </div>

      <div class="tqcontent" style="flex:1;overflow:auto;padding:20px">

        <!-- ============ DASHBOARD (v2 — ESET-style) ============ -->
        <sc-if value="{{ pDashboard }}" hint-placeholder-val="{{ true }}">
        <section data-screen-label="Dashboard" style="animation:tqFade .25s ease">

          <div class="d-hero" style="display:flex;align-items:center;gap:14px;padding:12px 16px;border-radius:9px;background:{{ heroTintBg }};border:1px solid {{ heroTintBorder }};margin-bottom:10px">
            <div class="d-hero-ic" style="width:48px;height:48px;border-radius:10px;background:{{ heroStripe }};display:flex;align-items:center;justify-content:center;flex-shrink:0">
              <svg width="24" height="24" viewBox="0 0 48 48" fill="none" aria-hidden="true">
                <rect x="4" y="8" width="34" height="22" rx="2.5" stroke="#fff" stroke-width="2.4"/>
                <line x1="14" y1="36" x2="28" y2="36" stroke="#fff" stroke-width="2.4" stroke-linecap="round"/>
                <line x1="21" y1="30" x2="21" y2="36" stroke="#fff" stroke-width="2.4"/>
                <path d="M35 18 L43 21 V29 C43 34.5 38.5 38 35 39.5 C31.5 38 27 34.5 27 29 V21 Z" fill="#fff" stroke="#fff" stroke-width="2" stroke-linejoin="round"/>
                <path d="M31 28.5 L34 31.5 L39 25.5" stroke="{{ heroStripe }}" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
              </svg>
            </div>
            <div style="flex:1;line-height:1.25">
              <div class="d-hero-title" style="font-size:14px;font-weight:600;color:var(--text-1)">{{ heroTitle }}</div>
              <div class="d-hero-sub" style="font-size:11.5px;color:var(--text-2);margin-top:1px">{{ heroSub }}</div>
            </div>
            <span style="display:inline-flex;align-items:center;padding:3px 10px;border-radius:99px;font-size:11px;font-weight:600;color:{{ licPillFg }};background:{{ licPillBg }};white-space:nowrap">{{ licPillLabel }}</span>
            <div style="display:flex;gap:7px">
              <button class="d-btn" onclick="{{ startQuick }}" style="padding:6px 12px;border-radius:6px;font-size:11.5px;font-weight:600;color:var(--text-1);background:var(--card);border:1px solid var(--border-strong);cursor:pointer;font-family:inherit;display:inline-flex;align-items:center;gap:5px"><svg style="width:11px;height:11px;stroke:currentColor;fill:none;stroke-width:2.2;stroke-linecap:round"><use href="#i-scan"></use></svg>Quick scan</button>
              <button class="d-btn" onclick="{{ startFull }}" style="padding:6px 14px;border-radius:6px;font-size:11.5px;font-weight:600;color:var(--on-brand);background:var(--brand);border:none;cursor:pointer;font-family:inherit">Full scan</button>
            </div>
          </div>

          <!-- Triple-Shield guard cards — 3 in a row (wraps on narrow windows). Each card shows
               the guard's state at a glance + one-click enable when off. -->
          <div style="display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;margin-bottom:12px">
            <sc-for list="{{ guardCards }}" as="g" hint-placeholder-count="3">
              <div style="background:var(--card);border:1px solid var(--border);border-radius:10px;padding:12px 14px 12px 16px;box-shadow:var(--shadow-sm);display:flex;align-items:center;gap:11px;position:relative;overflow:hidden">
                <div style="position:absolute;left:0;top:0;bottom:0;width:4px;background:{{ g.stripe }}"></div>
                <div style="width:36px;height:36px;border-radius:8px;display:flex;align-items:center;justify-content:center;flex:none;background:{{ g.iconBg }};color:{{ g.iconFg }}"><svg style="width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:1.9;stroke-linecap:round;stroke-linejoin:round"><use href="{{ g.icon }}"></use></svg></div>
                <div style="flex:1;min-width:0;line-height:1.3">
                  <div style="font-size:12.5px;font-weight:700;color:var(--text-1);display:flex;align-items:center;gap:6px">{{ g.name }} <span style="font-size:10.5px;font-weight:700;padding:1px 7px;border-radius:99px;color:{{ g.pillFg }};background:{{ g.pillBg }}">{{ g.pillText }}</span></div>
                  <div style="font-size:11px;color:var(--text-3);margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">{{ g.detail }}</div>
                </div>
                <sc-if value="{{ g.showEnable }}" hint-placeholder-val="{{ false }}">
                  <button onclick="{{ g.enable }}" style="padding:6px 12px;border-radius:6px;font-weight:600;font-size:11px;cursor:pointer;border:none;background:var(--brand);color:var(--on-brand);font-family:inherit;flex:none" style-hover="background:var(--brand-hover)">Enable</button>
                </sc-if>
              </div>
            </sc-for>
          </div>

          <div class="fx-A fx-C fx-H" style="background:var(--card);border:1px solid var(--border);border-radius:12px;padding:16px 18px;margin-bottom:12px;box-shadow:var(--shadow-sm)">
            <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:14px;flex-wrap:wrap;gap:10px">
              <div style="line-height:1.35">
                <div style="font-size:13px;font-weight:700;color:var(--text-1)">Activity — last {{ gwRangeText }}</div>
                <div style="font-size:11px;color:var(--text-3);margin-top:2px">{{ gwHeadline }}</div>
              </div>
              <div style="display:flex;gap:12px;font-size:11px;align-items:center;flex-wrap:wrap">
                <div style="display:flex;gap:10px">
                  <span style="display:flex;align-items:center;gap:6px;color:var(--text-2)"><span style="width:9px;height:9px;border-radius:2px;background:#3EBB6A"></span>Activity</span>
                  <span style="display:flex;align-items:center;gap:6px;color:var(--text-2)"><span style="width:9px;height:9px;border-radius:99px;background:#DC2626"></span>Threats</span>
                  <span style="display:flex;align-items:center;gap:6px;color:var(--text-2)"><span style="width:9px;height:9px;border-radius:99px;background:#F59E0B"></span>Script blocks</span>
                </div>
                <div style="display:inline-flex;background:var(--icon-bg);border:1px solid var(--border);border-radius:8px;padding:2px">
                  <sc-for list="{{ gwRangeBtns }}" as="r" hint-placeholder-count="3">
                    <button onclick="{{ r.select }}" style="border:none;padding:4px 11px;border-radius:6px;cursor:pointer;font-size:11px;font-weight:600;background:{{ r.bg }};color:{{ r.color }};box-shadow:{{ r.shadow }};font-family:inherit">{{ r.label }}</button>
                  </sc-for>
                </div>
              </div>
            </div>

            <sc-if value="{{ gwNotEmpty }}" hint-placeholder-val="{{ true }}">
            <div style="display:grid;grid-template-columns:1fr 190px;gap:16px;align-items:stretch">
              <div style="position:relative;height:220px;overflow:hidden;border-radius:6px">
                <svg viewBox="0 0 1200 220" preserveAspectRatio="xMinYMid meet" style="width:100%;height:100%;display:block;overflow:hidden">
                  <defs>
                    <linearGradient id="gwGrad" x1="0" y1="0" x2="0" y2="1">
                      <stop offset="0%" stop-color="#3EBB6A" stop-opacity="0.42"/>
                      <stop offset="60%" stop-color="#3EBB6A" stop-opacity="0.12"/>
                      <stop offset="100%" stop-color="#3EBB6A" stop-opacity="0"/>
                    </linearGradient>
                    <filter id="gwPinShadow" x="-50%" y="-50%" width="200%" height="200%"><feDropShadow dx="0" dy="1.5" stdDeviation="1.5" flood-color="#DC2626" flood-opacity="0.45"/></filter>
                    <linearGradient id="gwShimmerDark" x1="0" y1="0" x2="1" y2="0"><stop offset="0%" stop-color="#fff" stop-opacity="0"/><stop offset="50%" stop-color="#fff" stop-opacity="0.6"/><stop offset="100%" stop-color="#fff" stop-opacity="0"/></linearGradient>
                    <linearGradient id="gwShimmerLight" x1="0" y1="0" x2="1" y2="0"><stop offset="0%" stop-color="#065F46" stop-opacity="0"/><stop offset="50%" stop-color="#059669" stop-opacity="0.7"/><stop offset="100%" stop-color="#065F46" stop-opacity="0"/></linearGradient>
                    <clipPath id="gwMtnClip"><path d="{{ gwFilesArea }}"/></clipPath>
                  </defs>
                  <line class="gw-grid" x1="0" y1="65" x2="1200" y2="65" stroke="var(--border)" stroke-width="1" stroke-dasharray="3,3" opacity="0.55"/>
                  <line class="gw-grid" x1="0" y1="120" x2="1200" y2="120" stroke="var(--border)" stroke-width="1" stroke-dasharray="3,3" opacity="0.55"/>
                  <line class="gw-grid" x1="0" y1="175" x2="1200" y2="175" stroke="var(--border)" stroke-width="1" stroke-dasharray="3,3" opacity="0.55"/>
                  <path class="gw-anim-area" d="{{ gwFilesArea }}" fill="url(#gwGrad)" stroke="none"/>
                  <path class="gw-anim-line" d="{{ gwFilesLine }}" fill="none" stroke="#3EBB6A" stroke-width="1.6" stroke-linejoin="round" stroke-linecap="round"/>
                  <g clip-path="url(#gwMtnClip)"><rect class="gw-shimmer" x="0" y="0" width="300" height="220"/></g>
                  <sc-for list="{{ gwThreatPins }}" as="p" hint-placeholder-count="0">
                    <g onclick="{{ p.openDetail }}" style="cursor:pointer" color="#fff">
                      <circle class="gw-pin-aura" cx="{{ p.x }}" cy="22" r="17" fill="none" stroke="#DC2626" stroke-width="2.5"/>
                      <line x1="{{ p.x }}" y1="28" x2="{{ p.x }}" y2="220" stroke="#DC2626" stroke-width="1.2" stroke-dasharray="3,3" opacity="0.55"/>
                      <circle cx="{{ p.x }}" cy="22" r="17" fill="none" stroke="#DC2626" stroke-opacity="0.22" stroke-width="2"/>
                      <circle cx="{{ p.x }}" cy="22" r="14" fill="#DC2626" filter="url(#gwPinShadow)"/>
                      <svg x="{{ p.x }}" y="10" width="24" height="24" viewBox="0 0 24 24" style="overflow:visible;transform:translateX(-12px)"><g fill="none" stroke="#fff" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><use href="{{ p.catHref }}"></use></g></svg>
                      <sc-if value="{{ p.hasBadge }}" hint-placeholder-val="{{ false }}">
                        <circle cx="{{ p.badgeX }}" cy="12" r="7.5" fill="#fff" stroke="#DC2626" stroke-width="1.5"/>
                        <text x="{{ p.badgeX }}" y="15" text-anchor="middle" fill="#DC2626" font-size="9.5" font-weight="800" font-family="Segoe UI, sans-serif">{{ p.countStr }}</text>
                      </sc-if>
                    </g>
                  </sc-for>
                  <sc-for list="{{ gwAmsiDots }}" as="d" hint-placeholder-count="0">
                    <circle cx="{{ d.x }}" cy="{{ d.y }}" r="4.5" fill="#F59E0B" stroke="#fff" stroke-width="1.4"/>
                  </sc-for>
                  <sc-if value="{{ gwHasDetail }}" hint-placeholder-val="{{ false }}">
                    <foreignObject x="{{ gwDetailX }}" y="42" width="305" height="500" style="overflow:visible;pointer-events:auto">
                      <div xmlns="http://www.w3.org/1999/xhtml" style="background:var(--card);border:1px solid var(--border);border-radius:9px;padding:10px 12px 8px;box-shadow:var(--shadow-lg);font-family:'Segoe UI',system-ui,sans-serif;font-size:11.5px;line-height:1.35;max-height:460px;overflow-y:auto">
                        <div style="display:flex;align-items:center;gap:6px;margin-bottom:7px">
                          <svg style="width:13px;height:13px;stroke:#DC2626;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;flex:none"><use href="#i-cat-generic"></use></svg>
                          <span style="font-size:12px;font-weight:700;color:#DC2626;flex:1">{{ gwDetailHeadline }}</span>
                          <button onclick="{{ gwCloseDetail }}" style="background:transparent;border:none;color:var(--text-3);cursor:pointer;font-size:13px;padding:2px 6px;line-height:1;font-family:inherit" title="Close">✕</button>
                        </div>
                        <sc-if value="{{ gwHasFilterChips }}" hint-placeholder-val="{{ false }}">
                          <div style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:8px;padding-bottom:7px;border-bottom:1px solid var(--border)">
                            <sc-for list="{{ gwFilterChips }}" as="c" hint-placeholder-count="2">
                              <button onclick="{{ c.select }}" style="border:1px solid {{ c.border }};background:{{ c.bg }};color:{{ c.color }};padding:2px 8px;border-radius:99px;font-size:10px;font-weight:700;cursor:pointer;font-family:inherit">{{ c.label }}</button>
                            </sc-for>
                          </div>
                        </sc-if>
                        <sc-for list="{{ gwDetailItems }}" as="d" hint-placeholder-count="1">
                          <div style="display:flex;align-items:flex-start;gap:8px;padding:6px 0;border-top:1px solid var(--border)">
                            <div class="gw-red-tile" style="width:22px;height:22px;border-radius:5px;display:flex;align-items:center;justify-content:center;flex:none;margin-top:1px">
                              <svg style="width:12px;height:12px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"><use href="{{ d.catHref }}"></use></svg>
                            </div>
                            <div style="flex:1;min-width:0">
                              <div style="font-weight:700;color:var(--text-1);font-size:11.5px">{{ d.family }} <span class="gw-red-chip" style="font-size:9.5px;font-weight:700;padding:1px 5px;border-radius:99px;margin-left:2px">{{ d.catLabel }}</span></div>
                              <div style="font-size:10.5px;color:var(--text-2);margin-top:2px"><span style="color:var(--text-3);font-weight:600">{{ d.countLabel }}:</span> {{ d.preview }}{{ d.moreLabel }}</div>
                            </div>
                            <span style="background:var(--success-bg);color:var(--success);font-size:9.5px;font-weight:700;padding:1px 6px;border-radius:99px;flex:none;margin-top:2px">{{ d.act }}</span>
                          </div>
                        </sc-for>
                        <div style="border-top:1px solid var(--border);padding-top:7px;margin-top:2px;text-align:center">
                          <button onclick="{{ gwViewInQuar }}" style="color:var(--brand);font-size:11px;font-weight:600;padding:4px 12px;border:1px solid var(--brand);border-radius:6px;background:transparent;cursor:pointer;font-family:inherit">{{ gwHiddenLabel }}</button>
                        </div>
                      </div>
                    </foreignObject>
                  </sc-if>
                </svg>
              </div>
              <aside style="background:var(--icon-bg);border:1px solid var(--border);border-radius:10px;padding:12px 14px">
                <div style="font-size:9.5px;font-weight:700;letter-spacing:.7px;color:var(--text-3);text-transform:uppercase;padding-bottom:9px;margin-bottom:6px;border-bottom:1px solid var(--border)">Summary</div>
                <div style="display:flex;align-items:center;gap:10px;padding:8px 0">
                  <div style="width:26px;height:26px;border-radius:6px;background:rgba(220,38,38,.10);color:#DC2626;display:flex;align-items:center;justify-content:center;flex:none"><svg style="width:13px;height:13px;stroke:currentColor;fill:none;stroke-width:2"><use href="#i-cat-generic"></use></svg></div>
                  <div><div style="font-size:16px;font-weight:700;color:var(--text-1);line-height:1">{{ gwKpiThreats }}</div><div style="font-size:10.5px;color:var(--text-3);margin-top:2px">Threats blocked</div></div>
                </div>
                <div style="display:flex;align-items:center;gap:10px;padding:8px 0">
                  <div style="width:26px;height:26px;border-radius:6px;background:rgba(62,187,106,.12);color:#3EBB6A;display:flex;align-items:center;justify-content:center;flex:none"><svg style="width:13px;height:13px;stroke:currentColor;fill:none;stroke-width:2"><use href="#i-file"></use></svg></div>
                  <div><div style="font-size:16px;font-weight:700;color:var(--text-1);line-height:1">{{ gwKpiEvents }}</div><div style="font-size:10.5px;color:var(--text-3);margin-top:2px">Log events</div></div>
                </div>
                <div style="display:flex;align-items:center;gap:10px;padding:8px 0">
                  <div style="width:26px;height:26px;border-radius:6px;background:rgba(245,158,11,.10);color:#F59E0B;display:flex;align-items:center;justify-content:center;flex:none"><svg style="width:13px;height:13px;stroke:currentColor;fill:none;stroke-width:2"><use href="#i-cat-script"></use></svg></div>
                  <div><div style="font-size:16px;font-weight:700;color:var(--text-1);line-height:1">{{ gwKpiAmsi }}</div><div style="font-size:10.5px;color:var(--text-3);margin-top:2px">Script blocks</div></div>
                </div>
                <div style="display:flex;align-items:center;gap:10px;padding:14px 0 4px;border-top:1px solid var(--border);margin-top:6px">
                  <div style="width:26px;height:26px;border-radius:6px;background:var(--success-bg);color:var(--success);display:flex;align-items:center;justify-content:center;flex:none"><svg style="width:13px;height:13px;stroke:currentColor;fill:none;stroke-width:2.2"><use href="#i-check"></use></svg></div>
                  <div><div style="font-size:12px;font-weight:700;color:var(--success);line-height:1">{{ rtTitle }}</div><div style="font-size:10.5px;color:var(--text-3);margin-top:2px">{{ rtSub }}</div></div>
                </div>
              </aside>
            </div>
            <div style="display:flex;justify-content:space-between;margin-top:6px;font-size:10px;color:var(--text-3);font-family:Consolas,ui-monospace,monospace;padding-right:200px">
              <sc-for list="{{ gwTimeLabels }}" as="t" hint-placeholder-count="7">
                <span>{{ t.label }}</span>
              </sc-for>
            </div>
            </sc-if>

            <sc-if value="{{ gwIsEmpty }}" hint-placeholder-val="{{ false }}">
            <div style="text-align:center;padding:38px 20px">
              <div style="width:60px;height:60px;border-radius:14px;background:var(--success-bg);color:var(--success);display:inline-flex;align-items:center;justify-content:center;margin-bottom:12px"><svg style="width:34px;height:34px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round"><use href="#i-shield"></use></svg></div>
              <div style="font-size:14px;font-weight:700;color:var(--text-1);margin-bottom:4px">Nothing to report</div>
              <div style="font-size:12px;color:var(--text-3);max-width:400px;margin:0 auto;line-height:1.5">TreeIQ has been protecting your PC quietly for the last {{ gwRangeText }}. No threats detected, no scripts blocked.</div>
            </div>
            </sc-if>
          </div>


          <sc-if value="{{ gwHasDetail }}" hint-placeholder-val="{{ false }}">
          <div style="background:var(--card);border:1px solid var(--border);border-radius:12px;padding:14px 16px;margin-bottom:12px;box-shadow:var(--shadow-sm)">
            <div style="display:flex;align-items:center;gap:8px;margin-bottom:10px;flex-wrap:wrap">
              <div style="font-size:12.5px;font-weight:700;color:#DC2626;display:flex;align-items:center;gap:7px"><svg style="width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"><use href="#i-cat-generic"></use></svg>{{ gwDetailHeadline }}</div>
              <div style="flex:1"></div>
              <button onclick="{{ gwCloseDetail }}" style="background:transparent;border:1px solid var(--border);border-radius:6px;padding:3px 9px;color:var(--text-3);font-size:11px;cursor:pointer;font-family:inherit">Close ✕</button>
            </div>
            <sc-if value="{{ gwHasFilterChips }}" hint-placeholder-val="{{ false }}">
              <div style="display:flex;flex-wrap:wrap;gap:5px;margin-bottom:10px">
                <sc-for list="{{ gwFilterChips }}" as="c" hint-placeholder-count="2">
                  <button onclick="{{ c.select }}" style="border:1px solid {{ c.border }};background:{{ c.bg }};color:{{ c.color }};padding:3px 10px;border-radius:99px;font-size:11px;font-weight:700;cursor:pointer;font-family:inherit">{{ c.label }}</button>
                </sc-for>
              </div>
            </sc-if>
            <div style="display:flex;flex-direction:column;gap:8px">
              <sc-for list="{{ gwDetailItems }}" as="d" hint-placeholder-count="1">
                <div style="display:flex;align-items:center;gap:11px;padding:9px 11px;background:var(--icon-bg);border:1px solid var(--border);border-radius:8px">
                  <div class="gw-red-tile" style="width:32px;height:32px;border-radius:7px;display:flex;align-items:center;justify-content:center;flex:none"><svg style="width:15px;height:15px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"><use href="{{ d.catHref }}"></use></svg></div>
                  <div style="flex:1;line-height:1.4;min-width:0">
                    <div style="font-weight:700;font-size:12.5px;color:var(--text-1)">{{ d.family }} <span class="gw-red-chip" style="font-size:10px;font-weight:700;padding:1px 7px;border-radius:99px;margin-left:4px">{{ d.catLabel }}</span> <span style="font-size:10px;font-weight:700;padding:1px 7px;border-radius:99px;background:var(--icon-bg);color:var(--text-2);margin-left:2px">{{ d.countLabel }}</span></div>
                    <div style="font-size:11px;color:var(--text-2);margin-top:3px"><span style="color:var(--text-3);font-weight:600">Files:</span> {{ d.preview }}{{ d.moreLabel }}</div>
                    <div style="font-family:Consolas,ui-monospace,monospace;font-size:10.5px;color:var(--text-3);margin-top:2px;word-break:break-all"><span style="font-family:'Segoe UI',system-ui,sans-serif;font-weight:600">Path:</span> {{ d.firstPath }}</div>
                  </div>
                  <span style="background:var(--success-bg);color:var(--success);font-size:10px;font-weight:700;padding:2px 8px;border-radius:99px;flex:none">{{ d.act }}</span>
                </div>
              </sc-for>
            </div>
            <div style="border-top:1px solid var(--border);padding-top:10px;margin-top:10px;text-align:center">
              <button onclick="{{ gwViewInQuar }}" style="color:var(--brand);font-size:11.5px;font-weight:600;padding:5px 14px;border:1px solid var(--brand);border-radius:6px;background:transparent;cursor:pointer;font-family:inherit">{{ gwHiddenLabel }}</button>
            </div>
          </div>
          </sc-if>

          <div class="d-sec" style="font-size:10px;font-weight:700;letter-spacing:.6px;color:var(--text-3);text-transform:uppercase;margin:6px 2px 6px">This PC</div>
          <div style="display:grid;grid-template-columns:1fr 1fr;gap:8px">
            <sc-for list="{{ sysTiles }}" as="t" hint-placeholder-count="6">
              <div class="d-slim">
                <div class="d-slim-ic"><svg><use href="{{ t.icon }}"></use></svg></div>
                <div class="d-slim-lbl">{{ t.label }}</div>
                <div class="d-slim-val" style="font-family:{{ t.valueFont }}">{{ t.value }}</div>
                <div class="d-slim-sub" style="color:{{ t.subColor }};font-weight:{{ t.subWeight }}">{{ t.sub }}</div>
              </div>
            </sc-for>
          </div>


        </section>
        </sc-if>

        <!-- ============ SCAN (v2 — hero + tile grid + advanced + recent) ============ -->
        <sc-if value="{{ pScan }}" hint-placeholder-val="{{ false }}">
        <section data-screen-label="Scan" style="animation:tqFade .25s ease">

          <div style="display:flex;align-items:center;gap:16px;padding:16px 20px;border-radius:11px;background:var(--brand-tint);border:1px solid var(--brand-border);margin-bottom:12px">
            <div style="width:48px;height:48px;border-radius:10px;background:var(--brand);color:#fff;display:flex;align-items:center;justify-content:center;flex-shrink:0"><svg style="width:24px;height:24px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"><use href="#i-scan"></use></svg></div>
            <div style="flex:1;line-height:1.3">
              <div style="display:inline-block;padding:2px 9px;border-radius:99px;font-size:9.5px;font-weight:700;color:var(--brand);background:var(--brand-tint);letter-spacing:.4px;text-transform:uppercase;margin-bottom:4px;border:1px solid var(--brand-border)">Recommended</div>
              <div style="font-size:16px;font-weight:600;color:var(--text-1)">Quick Scan</div>
              <div style="font-size:11.5px;color:var(--text-2);margin-top:1px">Scans your PC's high-risk spots — Downloads, Desktop, Temp, AppData, Startup, and running processes. Takes about 2 minutes.</div>
            </div>
            <button onclick="{{ startQuick }}" style="padding:10px 18px;border-radius:8px;font-size:12.5px;font-weight:600;color:#fff;background:var(--brand);border:none;cursor:pointer;font-family:inherit;display:inline-flex;align-items:center;gap:6px;flex:none"><svg style="width:12px;height:12px;stroke:currentColor;fill:currentColor;stroke-linecap:round"><use href="#i-play"></use></svg>Start Quick Scan</button>
          </div>

          <div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:12px">
            <sc-for list="{{ scanTiles }}" as="c" hint-placeholder-count="4">
              <div onclick="{{ c.run }}" style="background:var(--card);border:1px solid var(--border);border-radius:10px;padding:13px 15px;display:flex;align-items:center;gap:12px;cursor:pointer" style-hover="border-color:var(--border-strong)">
                <div style="width:36px;height:36px;border-radius:8px;background:var(--icon-bg);color:var(--text-2);display:flex;align-items:center;justify-content:center;flex:none"><svg style="width:17px;height:17px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"><use href="{{ c.icon }}"></use></svg></div>
                <div style="flex:1;line-height:1.25"><div style="font-size:12.5px;font-weight:600;color:var(--text-1)">{{ c.title }}</div><div style="font-size:10.5px;color:var(--text-3);margin-top:1px">{{ c.sub }}</div></div>
                <div style="display:inline-flex;align-items:center;gap:3px;font-size:11px;font-weight:600;color:var(--brand)">{{ c.cta }} <svg style="width:11px;height:11px;stroke:currentColor;fill:none;stroke-width:2.2;stroke-linecap:round"><use href="#i-chev"></use></svg></div>
              </div>
            </sc-for>
          </div>

          <div style="font-size:10px;font-weight:700;letter-spacing:.6px;color:var(--text-3);text-transform:uppercase;margin:8px 2px 6px">Advanced</div>
          <div onclick="{{ startMemory }}" style="background:var(--card);border:1px solid var(--border);border-radius:10px;padding:12px 15px;display:flex;align-items:center;gap:12px;cursor:pointer;margin-bottom:8px" style-hover="border-color:var(--border-strong)">
            <div style="width:32px;height:32px;border-radius:7px;background:var(--icon-bg);color:var(--text-2);display:flex;align-items:center;justify-content:center;flex:none"><svg style="width:15px;height:15px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"><use href="#i-cpu"></use></svg></div>
            <div style="flex:1;line-height:1.3"><div style="font-size:12px;font-weight:600;color:var(--text-1)">Memory Scan</div><div style="font-size:10.5px;color:var(--text-3);margin-top:1px">Scans running processes for fileless / in-memory threats. Slower, deeper.</div></div>
            <span style="padding:5px 12px;border-radius:6px;font-size:11px;font-weight:600;color:var(--text-1);background:var(--card);border:1px solid var(--border-strong)">Start</span>
          </div>
          <!-- Force Full Scan — bypasses the scan-cache so every file gets re-verified by the engine.
               Use after a big Windows update, backup restore, or when in doubt about the cache. -->
          <div onclick="{{ startForceFull }}" style="background:var(--card);border:1px solid var(--border);border-radius:10px;padding:12px 15px;display:flex;align-items:center;gap:12px;cursor:pointer;margin-bottom:12px" style-hover="border-color:var(--border-strong)">
            <div style="width:32px;height:32px;border-radius:7px;background:var(--icon-bg);color:var(--text-2);display:flex;align-items:center;justify-content:center;flex:none"><svg style="width:15px;height:15px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"><use href="#i-refresh"></use></svg></div>
            <div style="flex:1;line-height:1.3"><div style="font-size:12px;font-weight:600;color:var(--text-1)">Force Full Scan</div><div style="font-size:10.5px;color:var(--text-3);margin-top:1px">Re-verifies every file with the engine, even ones the cache says are clean. Use after a big Windows update, backup restore, or if you suspect something slipped through. Takes as long as your very first scan.</div></div>
            <span style="padding:5px 12px;border-radius:6px;font-size:11px;font-weight:600;color:var(--text-1);background:var(--card);border:1px solid var(--border-strong)">Start</span>
          </div>

          <div style="display:flex;align-items:center;gap:10px;padding:10px 14px;border-radius:9px;background:var(--icon-bg);color:var(--text-2);margin-bottom:12px;font-size:11px">
            <svg style="width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;flex:none"><use href="#i-info"></use></svg>
            <span><b style="color:var(--text-1);font-weight:600">Tip:</b> right-click any file, folder, or drive in File Explorer → <b style="color:var(--text-1);font-weight:600">Scan with TreeIQ Anti-Malware</b>.</span>
          </div>

          <sc-if value="{{ hasRecentScans }}" hint-placeholder-val="{{ true }}">
          <div style="font-size:10px;font-weight:700;letter-spacing:.6px;color:var(--text-3);text-transform:uppercase;margin:8px 2px 6px">Recent scans</div>
          <div style="background:var(--card);border:1px solid var(--border);border-radius:10px;padding:2px 15px;margin-bottom:12px">
            <sc-for list="{{ recentScans }}" as="r" hint-placeholder-count="3">
              <div ondblclick="{{ r.open }}" title="Double-click to open scan report" style="display:flex;align-items:center;gap:11px;padding:9px 0;border-bottom:1px solid var(--border);cursor:pointer" style-hover="background:var(--icon-bg)">
                <div style="width:24px;height:24px;border-radius:5px;background:var(--icon-bg);color:var(--text-2);display:flex;align-items:center;justify-content:center;flex:none"><svg style="width:12px;height:12px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"><use href="{{ r.icon }}"></use></svg></div>
                <div style="font-size:11.5px;font-weight:600;color:var(--text-1);width:120px;flex:none">{{ r.type }}</div>
                <div style="font-size:10.5px;color:var(--text-3);width:130px;flex:none">{{ r.when }}</div>
                <div style="flex:1;font-size:11px;color:var(--text-3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">{{ r.summary }}</div>
                <div style="font-size:10.5px;color:var(--text-3);font-family:Consolas,ui-monospace,monospace">{{ r.duration }}</div>
              </div>
            </sc-for>
          </div>
          </sc-if>

          <sc-if value="{{ scanVisible }}" hint-placeholder-val="{{ false }}">
          <div style="margin-top:14px;background:var(--card);border:1px solid var(--border);border-radius:8px;padding:16px;box-shadow:var(--shadow-sm)">
            <div style="display:flex;align-items:center;gap:12px;margin-bottom:12px">
              <div style="width:34px;height:34px;border-radius:8px;display:flex;align-items:center;justify-content:center;background:var(--accent-dim);color:var(--accent);animation:{{ scanIconAnim }}"><svg style="width:17px;height:17px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round"><use href="#i-scan"></use></svg></div>
              <div style="flex:1;line-height:1.3"><div style="font-size:13px;font-weight:600;color:var(--text-1)">{{ scanTitle }}</div><div style="font-size:11px;color:var(--text-3);font-family:Consolas,ui-monospace,monospace;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:640px">{{ scanFile }}</div></div>
              <sc-if value="{{ scanRunning }}" hint-placeholder-val="{{ false }}">
                <button onclick="{{ stopScan }}" style="padding:6px 12px;border-radius:6px;font-weight:600;font-size:11.5px;cursor:pointer;border:1px solid var(--border-strong);background:transparent;color:var(--text-2);font-family:inherit" style-hover="border-color:var(--danger);color:var(--danger)">Stop</button>
              </sc-if>
            </div>
            <div style="height:6px;background:var(--surface);border-radius:99px;overflow:hidden;border:1px solid var(--border)">
              <div style="height:100%;background:var(--accent);width:{{ scanPctW }};transition:width .2s"></div>
            </div>
            <div style="display:flex;gap:22px;margin-top:10px;font-size:11.5px;color:var(--text-3)">
              <span>Scanned: <b style="color:var(--text-1)">{{ scanCount }}</b></span>
              <span>Threats: <b style="color:var(--danger)">{{ scanThreats }}</b></span>
              <span>{{ scanPctLabel }}</span>
            </div>
          </div>
          </sc-if>

          <sc-if value="{{ resultsVisible }}" hint-placeholder-val="{{ false }}">
          <div style="margin-top:14px">
            <div style="font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.6px;color:var(--text-3);margin-bottom:8px">Scan results</div>
            <sc-if value="{{ scanAllClear }}" hint-placeholder-val="{{ false }}">
            <div style="display:flex;align-items:center;gap:10px;background:var(--success-bg);border:1px solid var(--success);border-radius:8px;padding:11px 13px;margin-bottom:10px">
              <svg style="width:18px;height:18px;stroke:var(--success);fill:none;stroke-width:2;stroke-linecap:round"><use href="#i-check"></use></svg>
              <span style="font-size:12.5px;color:var(--success);font-weight:600">All threats neutralized — your PC is clean.</span>
            </div>
            </sc-if>
            <sc-if value="{{ hasResults }}" hint-placeholder-val="{{ false }}">
            <sc-if value="{{ scanHasActionable }}" hint-placeholder-val="{{ true }}">
            <div style="display:flex;align-items:center;gap:9px;background:var(--accent-dim);border:1px solid var(--border);border-radius:8px;padding:9px 12px;margin-bottom:8px;flex-wrap:wrap">
              <span style="font-size:12px;color:var(--text-2)">Set selected to</span>
              <select value="{{ scanBulk }}" onchange="{{ onScanBulk }}" style="font-size:12px;padding:5px 9px;border-radius:6px;border:1px solid var(--border-strong);background:var(--surface);color:var(--text-1);font-family:inherit;outline:none">
                <option value="quarantine">Quarantine</option>
                <option value="delete">Delete permanently</option>
                <option value="ignore">Ignore</option>
              </select>
              <button onclick="{{ applyScanToSelected }}" style="padding:6px 12px;border-radius:6px;font-weight:600;font-size:11.5px;cursor:pointer;border:1px solid var(--border-strong);background:transparent;color:var(--text-2);font-family:inherit" style-hover="border-color:var(--accent);color:var(--accent)">Apply to selected</button>
              <span style="flex:1"></span>
              <button onclick="{{ applyScanActions }}" style="display:inline-flex;align-items:center;gap:6px;padding:8px 15px;border-radius:7px;font-weight:600;font-size:12px;cursor:pointer;border:none;background:var(--accent);color:var(--on-accent);font-family:inherit" style-hover="background:var(--accent-hover)"><svg style="width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:1.9;stroke-linecap:round"><use href="#i-check"></use></svg> {{ scanApplyLabel }}</button>
            </div>
            </sc-if>
            <div style="background:var(--card);border:1px solid var(--border);border-radius:8px;box-shadow:var(--shadow-sm);overflow-x:auto">
              <table style="width:100%;min-width:900px;border-collapse:collapse;font-size:12px">
                <thead class="tq-stky"><tr>
                  <th style="width:34px;text-align:left;padding:9px 0 9px 14px;border-bottom:1px solid var(--border)"><input type="checkbox" checked="{{ scanAllSel }}" onchange="{{ scanToggleAll }}" style="accent-color:var(--accent)"></th>
                  <th style="text-align:left;padding:9px 14px;color:var(--text-3);font-weight:600;border-bottom:1px solid var(--border);text-transform:uppercase;font-size:10px;letter-spacing:.5px">File</th>
                  <th style="text-align:left;padding:9px 14px;color:var(--text-3);font-weight:600;border-bottom:1px solid var(--border);text-transform:uppercase;font-size:10px;letter-spacing:.5px">Threat</th>
                  <th style="text-align:left;padding:9px 14px;color:var(--text-3);font-weight:600;border-bottom:1px solid var(--border);text-transform:uppercase;font-size:10px;letter-spacing:.5px">Category</th>
                  <th style="text-align:left;padding:9px 14px;color:var(--text-3);font-weight:600;border-bottom:1px solid var(--border);text-transform:uppercase;font-size:10px;letter-spacing:.5px">Original path</th>
                  <th style="text-align:left;padding:9px 14px;color:var(--text-3);font-weight:600;border-bottom:1px solid var(--border);text-transform:uppercase;font-size:10px;letter-spacing:.5px">Action</th>
                </tr></thead>
                <tbody>
                  <sc-for list="{{ scanResults }}" as="r" hint-placeholder-count="2">
                  <tr style-hover="background:var(--accent-dim)">
                      <td style="padding:9px 0 9px 14px;border-bottom:1px solid var(--border)"><sc-if value="{{ r.actionable }}" hint-placeholder-val="{{ true }}"><input type="checkbox" checked="{{ r.sel }}" onchange="{{ r.onSel }}" style="accent-color:var(--accent)"></sc-if></td>
                      <td style="padding:9px 14px;border-bottom:1px solid var(--border)"><b style="color:var(--text-1)">{{ r.file }}</b></td>
                      <td style="padding:9px 14px;border-bottom:1px solid var(--border);font-family:Consolas,ui-monospace,monospace;font-size:11.5px;color:var(--text-2)">{{ r.det }}</td>
                      <td style="padding:9px 14px;border-bottom:1px solid var(--border)"><span style="display:inline-flex;padding:2px 9px;border-radius:99px;font-size:11px;font-weight:600;color:{{ r.catFg }};background:{{ r.catBg }}">{{ r.cat }}</span></td>
                      <td title="{{ r.pathFull }}" style="padding:9px 14px;border-bottom:1px solid var(--border);font-family:Consolas,ui-monospace,monospace;font-size:10.5px;color:var(--text-3);max-width:190px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">{{ r.pathShort }}</td>
                      <td style="padding:9px 14px;border-bottom:1px solid var(--border)">
                        <sc-if value="{{ r.actionable }}" hint-placeholder-val="{{ true }}"><select value="{{ r.choice }}" onchange="{{ r.onChoice }}" style="font-size:11.5px;padding:5px 9px;border-radius:6px;font-weight:600;font-family:inherit;outline:none;border:1px solid {{ r.choiceBd }};background:{{ r.choiceBg }};color:{{ r.choiceFg }}">
                          <option value="quarantine">Quarantine</option>
                          <option value="delete">Delete permanently</option>
                          <option value="ignore">Ignore</option>
                        </select></sc-if>
                        <sc-if value="{{ r.notActionable }}" hint-placeholder-val="{{ false }}"><span style="display:inline-flex;padding:2px 9px;border-radius:99px;font-size:11px;font-weight:600;color:{{ r.actFg }};background:{{ r.actBg }}">{{ r.actionLabel }}</span></sc-if>
                      </td>
                    </tr>
                  </sc-for>
                </tbody>
              </table>
            </div>
            </sc-if>
            <sc-if value="{{ noResults }}" hint-placeholder-val="{{ false }}">
            <div style="background:var(--card);border:1px solid var(--border);border-radius:8px;box-shadow:var(--shadow-sm);text-align:center;padding:36px 20px;color:var(--text-3)">
              <svg style="width:36px;height:36px;stroke:var(--success);fill:none;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round;margin-bottom:8px"><use href="#i-check"></use></svg>
              <div style="font-size:12.5px">No threats found. You’re clean.</div>
            </div>
            </sc-if>
          </div>
          </sc-if>
        </section>
        </sc-if>

        <!-- ============ REAL-TIME ============ -->
        <sc-if value="{{ pRealtime }}" hint-placeholder-val="{{ false }}">
        <section data-screen-label="Protection" style="animation:tqFade .25s ease">

          <!-- Triple-Shield state banner — mirrors Dashboard hero + 3-card row so the user gets the
               same at-a-glance status. The old single-toggle File-Guard hero was removed (redundant
               with the File Guard card). Detailed sub-toggles for each guard are still available in
               the Advanced accordions below. -->
          <div style="display:flex;align-items:center;gap:14px;padding:12px 18px;border-radius:11px;background:{{ heroTintBg }};border:1px solid {{ heroTintBorder }};margin-bottom:10px">
            <div style="width:40px;height:40px;border-radius:9px;background:{{ heroStripe }};color:#fff;display:flex;align-items:center;justify-content:center;flex:none"><svg style="width:20px;height:20px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"><use href="#i-shield"></use></svg></div>
            <div style="flex:1;line-height:1.3">
              <div style="font-size:14px;font-weight:600;color:var(--text-1)">{{ heroTitle }}</div>
              <div style="font-size:11.5px;color:var(--text-2);margin-top:1px">{{ heroSub }}</div>
            </div>
          </div>

          <!-- Triple-Shield guard cards — same data source as Dashboard (`guardCards`) so state, colors,
               timestamps and Enable buttons stay perfectly in sync between the two pages. -->
          <div style="display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;margin-bottom:12px">
            <sc-for list="{{ guardCards }}" as="g" hint-placeholder-count="3">
              <div style="background:var(--card);border:1px solid var(--border);border-radius:10px;padding:12px 14px 12px 16px;box-shadow:var(--shadow-sm);display:flex;align-items:center;gap:11px;position:relative;overflow:hidden">
                <div style="position:absolute;left:0;top:0;bottom:0;width:4px;background:{{ g.stripe }}"></div>
                <div style="width:36px;height:36px;border-radius:8px;display:flex;align-items:center;justify-content:center;flex:none;background:{{ g.iconBg }};color:{{ g.iconFg }}"><svg style="width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:1.9;stroke-linecap:round;stroke-linejoin:round"><use href="{{ g.icon }}"></use></svg></div>
                <div style="flex:1;min-width:0;line-height:1.3">
                  <div style="font-size:12.5px;font-weight:700;color:var(--text-1);display:flex;align-items:center;gap:6px">{{ g.name }} <span style="font-size:10.5px;font-weight:700;padding:1px 7px;border-radius:99px;color:{{ g.pillFg }};background:{{ g.pillBg }}">{{ g.pillText }}</span></div>
                  <div style="font-size:11px;color:var(--text-3);margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">{{ g.detail }}</div>
                </div>
                <sc-if value="{{ g.showEnable }}" hint-placeholder-val="{{ false }}">
                  <button onclick="{{ g.enable }}" style="padding:6px 12px;border-radius:6px;font-weight:600;font-size:11px;cursor:pointer;border:none;background:var(--brand);color:var(--on-brand);font-family:inherit;flex:none" style-hover="background:var(--brand-hover)">Enable</button>
                </sc-if>
              </div>
            </sc-for>
          </div>

          <sc-if value="{{ rtWarn }}" hint-placeholder-val="{{ false }}">
          <div style="display:flex;gap:10px;align-items:center;padding:9px 13px;border-radius:8px;background:var(--warning-bg);color:var(--warning);font-size:11.5px;font-weight:500;margin-bottom:10px"><svg style="width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;flex:none"><use href="#i-alert"></use></svg> Turning off protection requires administrator approval (UAC).</div>
          </sc-if>

          <div class="d-grid-2" style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:10px">
            <div class="d-kpi" style="background:var(--card);border:1px solid var(--border);border-radius:9px;padding:10px 13px">
              <div style="display:flex;align-items:center;gap:8px;margin-bottom:5px">
                <div class="d-kpi-ic" style="width:22px;height:22px;border-radius:5px;background:var(--danger-bg);color:var(--danger-ink);display:flex;align-items:center;justify-content:center"><svg style="width:12px;height:12px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round"><use href="#i-bug"></use></svg></div>
                <div class="d-kpi-label" style="font-size:10px;font-weight:700;letter-spacing:.5px;color:var(--text-3);text-transform:uppercase">Threats blocked</div>
              </div>
              <div class="d-kpi-value" style="font-size:19px;font-weight:600;line-height:1.15;color:var(--text-1)">{{ kpiThreats }}</div>
              <div class="d-kpi-sub" style="font-size:10.5px;color:var(--text-3);margin-top:2px">{{ kpiThreatsSub }}</div>
            </div>
            <div class="d-kpi" style="background:var(--card);border:1px solid var(--border);border-radius:9px;padding:10px 13px">
              <div style="display:flex;align-items:center;gap:8px;margin-bottom:5px">
                <div class="d-kpi-ic" style="width:22px;height:22px;border-radius:5px;background:var(--accent-soft);color:var(--accent-ink);display:flex;align-items:center;justify-content:center"><svg style="width:12px;height:12px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"><use href="#i-file"></use></svg></div>
                <div class="d-kpi-label" style="font-size:10px;font-weight:700;letter-spacing:.5px;color:var(--text-3);text-transform:uppercase">Files scanned</div>
              </div>
              <div class="d-kpi-value" style="font-size:19px;font-weight:600;line-height:1.15;color:var(--text-1)">{{ kpiScanned }}</div>
              <div class="d-kpi-sub" style="font-size:10.5px;color:var(--text-3);margin-top:2px">{{ kpiScannedSub }}</div>
            </div>
          </div>

          <!-- Last USB event chip — only shows when there IS a recent USB event (auto-hides otherwise). -->
          <sc-if test="{{ lastUsbEvent.show }}">
            <div style="display:flex;align-items:center;gap:9px;padding:9px 12px;background:{{ lastUsbEvent.bg }};border:1px solid {{ lastUsbEvent.border }};border-radius:9px;margin-bottom:10px">
              <div style="width:22px;height:22px;border-radius:5px;background:rgba(13,148,136,.14);color:#0D9488;display:flex;align-items:center;justify-content:center;flex:none"><svg style="width:12px;height:12px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"><use href="#i-usb"></use></svg></div>
              <div style="flex:1;line-height:1.25;min-width:0"><div style="font-size:11.5px;font-weight:600;color:var(--text-1)">Last USB event · <span style="color:var(--text-3);font-weight:500">{{ lastUsbEvent.summary }}</span></div><div style="font-size:10px;color:var(--text-3);margin-top:1px">{{ lastUsbEvent.detail }}</div></div>
              <div style="font-size:10.5px;color:{{ lastUsbEvent.pillColor }};font-weight:600">{{ lastUsbEvent.pillText }}</div>
            </div>
          </sc-if>

          <div class="d-comp" style="background:var(--card);border:1px solid var(--border);border-radius:9px;padding:2px 0;margin-bottom:10px;overflow:hidden">
            <sc-for list="{{ compRows }}" as="c" hint-placeholder-count="3">
              <div class="d-comp-row" style="display:flex;align-items:center;gap:10px;padding:8px 14px 8px 14px;border-bottom:1px solid var(--border);background:{{ c.rowBg }};position:relative">
                <div style="position:absolute;left:0;top:0;bottom:0;width:3px;background:{{ c.stripe }}"></div>
                <div class="d-comp-ic" style="width:26px;height:26px;border-radius:6px;background:{{ c.iconBg }};color:{{ c.iconFg }};display:flex;align-items:center;justify-content:center"><svg style="width:13px;height:13px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"><use href="{{ c.icon }}"></use></svg></div>
                <div style="flex:1;line-height:1.25;min-width:0"><div class="d-comp-name" style="font-size:12px;font-weight:600;color:{{ c.nameColor }}">{{ c.name }}</div><div class="d-comp-desc" style="font-size:10.5px;color:{{ c.descColor }};overflow:hidden;text-overflow:ellipsis;white-space:nowrap">{{ c.desc }}</div></div>
                <div class="d-comp-state" style="display:inline-flex;align-items:center;gap:4px;font-size:11px;font-weight:700;color:{{ c.stateColor }}"><svg style="width:11px;height:11px;stroke:currentColor;fill:none;stroke-width:2.6;stroke-linecap:round"><use href="{{ c.stateIcon }}"></use></svg>{{ c.state }}</div>
                <sc-if value="{{ c.showEnable }}" hint-placeholder-val="{{ false }}">
                  <button onclick="{{ c.enable }}" style="padding:5px 11px;border-radius:6px;font-weight:600;font-size:11px;cursor:pointer;border:none;background:var(--brand);color:var(--on-brand);font-family:inherit;flex:none" style-hover="background:var(--brand-hover)">Enable</button>
                </sc-if>
              </div>
            </sc-for>
          </div>

          <div style="font-size:10.5px;font-weight:700;letter-spacing:.6px;color:var(--text-3);text-transform:uppercase;margin:12px 2px 6px">Watched locations</div>
          <div style="background:var(--card);border:1px solid var(--border);border-radius:10px;padding:4px 16px;margin-bottom:12px">
            <sc-for list="{{ drives }}" as="d" hint-placeholder-count="4">
              <div style="display:flex;align-items:center;gap:11px;padding:9px 0;border-bottom:1px solid var(--border)">
                <div style="width:28px;height:28px;border-radius:6px;background:var(--icon-bg);color:var(--text-2);display:flex;align-items:center;justify-content:center;flex:none"><svg style="width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"><use href="#i-disk"></use></svg></div>
                <div style="flex:1;font-family:Consolas,ui-monospace,monospace;font-size:11.5px;color:var(--text-1);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">{{ d.name }}</div>
                <span style="font-size:10.5px;padding:2px 8px;border-radius:99px;color:var(--text-3);background:var(--icon-bg);flex:none">{{ d.desc }}</span>
                <label style="display:inline-flex;align-items:center;gap:5px;cursor:pointer;font-size:11px;color:var(--text-2)"><input type="checkbox" checked="{{ d.on }}" onchange="{{ d.toggle }}" style="accent-color:var(--brand);width:14px;height:14px">Watch</label>
              </div>
            </sc-for>
          </div>

        </section>
        </sc-if>

        <!-- ============ WHITELIST ============ -->
        <sc-if value="{{ pWhitelist }}" hint-placeholder-val="{{ false }}">
        <section data-screen-label="Whitelist" style="animation:tqFade .25s ease">
          <div style="display:flex;align-items:center;gap:14px;padding:14px 18px;border-radius:12px;background:var(--brand-tint);border:1px solid var(--brand-border);margin-bottom:14px">
            <div style="width:48px;height:48px;border-radius:10px;background:var(--brand);color:#fff;display:flex;align-items:center;justify-content:center;flex:none"><svg style="width:24px;height:24px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"><use href="#i-check"></use></svg></div>
            <div style="flex:1;line-height:1.3">
              <div style="font-size:15px;font-weight:600;color:var(--text-1)">Whitelist</div>
              <div style="font-size:11.5px;color:var(--text-2);margin-top:1px">{{ wlHeroSub }}</div>
            </div>
          </div>
          <div style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:14px">
            <button onclick="{{ wlAddFile }}" style="display:inline-flex;align-items:center;gap:7px;padding:8px 13px;border-radius:7px;font-weight:600;font-size:12px;cursor:pointer;border:1px solid var(--border-strong);background:var(--card);color:var(--text-2);font-family:inherit" style-hover="border-color:var(--accent);color:var(--accent)"><svg style="width:15px;height:15px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round"><use href="#i-box"></use></svg> Add file</button>
            <button onclick="{{ wlAddFolder }}" style="display:inline-flex;align-items:center;gap:7px;padding:8px 13px;border-radius:7px;font-weight:600;font-size:12px;cursor:pointer;border:1px solid var(--border-strong);background:var(--card);color:var(--text-2);font-family:inherit" style-hover="border-color:var(--accent);color:var(--accent)"><svg style="width:15px;height:15px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round"><use href="#i-folder"></use></svg> Add folder</button>
            <button onclick="{{ wlAddProcess }}" style="display:inline-flex;align-items:center;gap:7px;padding:8px 13px;border-radius:7px;font-weight:600;font-size:12px;cursor:pointer;border:1px solid var(--border-strong);background:var(--card);color:var(--text-2);font-family:inherit" style-hover="border-color:var(--accent);color:var(--accent)"><svg style="width:15px;height:15px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round"><use href="#i-cpu"></use></svg> Add process</button>
            <button onclick="{{ wlAddDrive }}" style="display:inline-flex;align-items:center;gap:7px;padding:8px 13px;border-radius:7px;font-weight:600;font-size:12px;cursor:pointer;border:1px solid var(--border-strong);background:var(--card);color:var(--text-2);font-family:inherit" style-hover="border-color:var(--accent);color:var(--accent)"><svg style="width:15px;height:15px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round"><use href="#i-disk"></use></svg> Add drive</button>
          </div>
          <div style="background:var(--card);border:1px solid var(--border);border-radius:8px;overflow:hidden;box-shadow:var(--shadow-sm)">
            <div style="display:flex;padding:9px 14px;border-bottom:1px solid var(--border);background:var(--surface);font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;color:var(--text-3)">
              <div style="width:120px">Type</div><div style="flex:1">Item</div><div style="width:180px">Scope</div><div style="width:78px">Added</div><div style="width:28px"></div>
            </div>
            <sc-for list="{{ whitelistRows }}" as="w" hint-placeholder-count="2">
              <div style="display:flex;align-items:center;padding:9px 14px;border-bottom:1px solid var(--border)">
                <div style="width:120px;display:flex;align-items:center;gap:7px;font-weight:600;font-size:12.5px;color:{{ w.tint }}"><svg style="width:15px;height:15px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;flex:none"><use href="{{ w.icon }}"></use></svg>{{ w.typeLabel }}</div>
                <div style="flex:1;min-width:0;font-family:Consolas,ui-monospace,monospace;font-size:11.5px;color:var(--text-1);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">{{ w.path }}</div>
                <div style="width:180px;font-size:11.5px;color:var(--text-3)">{{ w.scope }}</div>
                <div style="width:78px;font-size:11.5px;color:var(--text-3)">{{ w.added }}</div>
                <div style="width:28px;text-align:right"><button onclick="{{ w.remove }}" style="border:none;background:none;color:var(--text-3);cursor:pointer;padding:0;display:flex" style-hover="color:var(--danger)"><svg style="width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round"><use href="#i-trash"></use></svg></button></div>
              </div>
            </sc-for>
            <sc-if value="{{ wlIsEmpty }}" hint-placeholder-val="{{ true }}">
              <div style="text-align:center;padding:44px 20px">
                <div style="width:60px;height:60px;border-radius:14px;background:var(--brand-tint);color:var(--brand);display:inline-flex;align-items:center;justify-content:center;margin-bottom:12px"><svg style="width:34px;height:34px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round"><use href="#i-check"></use></svg></div>
                <div style="font-size:14px;font-weight:700;color:var(--text-1);margin-bottom:4px">No trusted items yet</div>
                <div style="font-size:12px;color:var(--text-3);max-width:400px;margin:0 auto;line-height:1.5">Use the buttons above to add a file, folder, process or drive. Trusted items are skipped by real-time protection and every scan.</div>
              </div>
            </sc-if>
          </div>
        </section>
        </sc-if>

        <!-- whitelist picker modal (process / drive) -->
        <sc-if value="{{ wlModalOpen }}" hint-placeholder-val="{{ false }}">
        <div onclick="{{ wlModalClose }}" style="position:fixed;inset:0;background:rgba(15,23,42,.45);display:flex;align-items:center;justify-content:center;z-index:200">
          <div onclick="{{ wlModalStop }}" style="width:560px;max-height:74vh;display:flex;flex-direction:column;background:var(--card);border:1px solid var(--border);border-radius:12px;box-shadow:0 20px 60px rgba(0,0,0,.35);overflow:hidden">
            <div style="display:flex;align-items:center;padding:13px 16px;border-bottom:1px solid var(--border)"><div style="flex:1;font-size:13.5px;font-weight:700;color:var(--text-1)">{{ wlModalTitle }}</div><button onclick="{{ wlModalClose }}" style="border:none;background:none;color:var(--text-3);cursor:pointer;padding:2px;display:flex"><svg style="width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round"><use href="#i-close"></use></svg></button></div>
            <div style="overflow-y:auto;flex:1">
              <sc-for list="{{ wlModalRows }}" as="m" hint-placeholder-count="4">
                <div onclick="{{ m.toggle }}" style="display:flex;align-items:center;gap:11px;padding:9px 16px;border-bottom:1px solid var(--border);cursor:pointer" style-hover="background:var(--accent-dim)">
                  <input type="checkbox" checked="{{ m.checked }}" style="width:16px;height:16px;flex:none;accent-color:var(--accent);pointer-events:none">
                  <div style="min-width:0;flex:1">
                    <div style="font-size:12.5px;font-weight:600;color:var(--text-1)">{{ m.label }}</div>
                    <div style="font-size:10.5px;color:var(--text-3);font-family:Consolas,ui-monospace,monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">{{ m.sub }}</div>
                  </div>
                </div>
              </sc-for>
            </div>
            <div style="display:flex;align-items:center;gap:10px;padding:11px 16px;border-top:1px solid var(--border);background:var(--surface)">
              <span style="font-size:12px;color:var(--text-3)">{{ wlSelCount }}</span><span style="flex:1"></span>
              <button onclick="{{ wlModalClose }}" style="padding:7px 14px;border-radius:7px;font-weight:600;font-size:12px;cursor:pointer;border:1px solid var(--border-strong);background:transparent;color:var(--text-2);font-family:inherit">Cancel</button>
              <button onclick="{{ wlAddSelected }}" style="padding:7px 16px;border-radius:7px;font-weight:600;font-size:12px;cursor:pointer;border:none;background:var(--accent);color:var(--on-accent);font-family:inherit">Add selected</button>
            </div>
          </div>
        </div>
        </sc-if>

        <!-- Upgrade confirmation modal — styled in-app dialog (replaces native confirm() box) -->
        <sc-if value="{{ ucOpen }}" hint-placeholder-val="{{ false }}">
        <div onclick="{{ ucCancel }}" style="position:fixed;inset:0;background:rgba(15,23,42,.45);display:flex;align-items:center;justify-content:center;z-index:220">
          <div onclick="{{ ucStop }}" style="width:440px;max-width:92vw;background:var(--card);border:1px solid var(--border);border-radius:12px;box-shadow:0 20px 60px rgba(0,0,0,.35);overflow:hidden">
            <div style="display:flex;align-items:center;gap:12px;padding:18px 20px 14px">
              <div style="width:42px;height:42px;border-radius:10px;background:rgba(18,87,214,.12);display:flex;align-items:center;justify-content:center;flex-shrink:0">
                <svg style="width:22px;height:22px;stroke:var(--accent);fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round"><use href="#i-update"></use></svg>
              </div>
              <div style="flex:1;min-width:0">
                <div style="font-size:15px;font-weight:700;color:var(--text-1)">Update available — v{{ ucVersion }}</div>
                <div style="font-size:12px;color:var(--text-3);margin-top:2px">A newer version of TreeIQ is ready to install.</div>
              </div>
            </div>
            <sc-if value="{{ ucHasNotes }}" hint-placeholder-val="{{ false }}">
            <div style="margin:0 20px 4px;padding:11px 13px;background:var(--surface);border:1px solid var(--border);border-radius:8px;font-size:12.5px;line-height:1.5;color:var(--text-2)">{{ ucNotes }}</div>
            </sc-if>
            <div style="padding:8px 20px 4px;font-size:12px;color:var(--text-3)">TreeIQ will restart to finish. This takes a few seconds.</div>
            <div style="display:flex;align-items:center;gap:10px;padding:14px 20px 18px;justify-content:flex-end">
              <button onclick="{{ ucCancel }}" style="padding:8px 16px;border-radius:7px;font-weight:600;font-size:12.5px;cursor:pointer;border:1px solid var(--border-strong);background:transparent;color:var(--text-2);font-family:inherit">Not now</button>
              <button onclick="{{ ucProceed }}" style="padding:8px 18px;border-radius:7px;font-weight:600;font-size:12.5px;cursor:pointer;border:none;background:var(--accent);color:var(--on-accent);font-family:inherit">Download &amp; install</button>
            </div>
          </div>
        </div>
        </sc-if>

        <sc-if value="{{ rmOpen }}" hint-placeholder-val="{{ false }}">
        <div onclick="{{ rmClose }}" style="position:fixed;inset:0;background:rgba(15,23,42,.45);display:flex;align-items:center;justify-content:center;z-index:210">
          <div onclick="{{ rmStop }}" style="width:580px;max-height:88vh;display:flex;flex-direction:column;background:var(--card);border:1px solid var(--border);border-radius:12px;box-shadow:0 20px 60px rgba(0,0,0,.35);overflow:hidden">
            <div style="display:flex;align-items:center;gap:11px;padding:14px 18px;border-bottom:1px solid var(--border)">
              <div style="width:30px;height:30px;border-radius:8px;background:var(--accent-soft);display:flex;align-items:center;justify-content:center;color:var(--accent)"><svg style="width:17px;height:17px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round"><use href="#i-restore"></use></svg></div>
              <div style="flex:1"><div style="font-size:14px;font-weight:600">{{ rmTitle }}</div><div style="font-size:11.5px;color:var(--text-3);font-family:Consolas,ui-monospace,monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">{{ rmSubtitle }}</div></div>
              <button onclick="{{ rmClose }}" style="border:none;background:none;color:var(--text-3);cursor:pointer;padding:2px;display:flex"><svg style="width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round"><use href="#i-close"></use></svg></button>
            </div>
            <div style="overflow-y:auto;flex:1;padding:14px 18px 4px">
              <div style="font-size:10.5px;font-weight:700;letter-spacing:.5px;color:var(--text-2);margin-bottom:8px">DESTINATION</div>
              <label onclick="{{ rmPickOrig }}" style="display:flex;align-items:center;gap:9px;padding:9px 11px;border:1px solid {{ rmOrigBorder }};border-radius:7px;background:{{ rmOrigBg }};cursor:pointer;margin-bottom:6px">
                <input type="radio" checked="{{ rmDestOrig }}" style="accent-color:var(--accent)">
                <div style="flex:1"><div style="font-size:12.5px;font-weight:600">Original location</div><div style="font-family:Consolas,ui-monospace,monospace;font-size:10.5px;color:var(--text-3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">{{ rmOrigPath }}</div></div>
              </label>
              <label onclick="{{ rmPickFolder }}" style="display:flex;align-items:center;gap:9px;padding:9px 11px;border:1px solid {{ rmFolderBorder }};border-radius:7px;background:{{ rmFolderBg }};cursor:pointer">
                <input type="radio" checked="{{ rmDestFolder }}" style="accent-color:var(--accent)">
                <div style="flex:1;min-width:0"><div style="font-size:12.5px;font-weight:600">Choose folder or drive (incl. USB)</div><div style="font-family:Consolas,ui-monospace,monospace;font-size:10.5px;color:var(--text-3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">{{ rmFolderPath }}</div></div>
                <button onclick="{{ rmBrowse }}" style="padding:4px 10px;border:1px solid var(--border-strong);border-radius:6px;font-size:11px;color:var(--text-2);background:transparent;font-family:inherit;cursor:pointer">Browse…</button>
              </label>
              <div style="margin:10px 0 4px;padding:9px 11px;background:var(--warning-bg);border:1px solid var(--warning-line);border-radius:6px;display:flex;gap:8px;align-items:flex-start;font-size:11px;color:var(--warning);font-weight:500">
                <svg style="width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;flex:none;margin-top:1px"><use href="#i-alert"></use></svg>
                <span>Restoring a detected threat may re-infect this PC. Restoring to a USB drive can spread it to another PC. Continue only if you're sure the file is safe.</span>
              </div>
              <div style="font-size:10.5px;font-weight:700;letter-spacing:.5px;color:var(--text-2);margin:14px 0 6px">TRUST OPTIONS</div>
              <label style="display:flex;align-items:flex-start;gap:9px;padding:8px 0;cursor:pointer">
                <input type="checkbox" checked="{{ rmWlFile }}" onchange="{{ rmToggleWlFile }}" style="accent-color:var(--accent);margin-top:2px">
                <div style="flex:1"><div style="font-size:12px;font-weight:600">Add file to whitelist</div></div>
              </label>
              <label style="display:flex;align-items:flex-start;gap:9px;padding:8px 0;cursor:pointer;border-top:1px solid var(--border)">
                <input type="checkbox" checked="{{ rmWlProc }}" onchange="{{ rmToggleWlProc }}" style="accent-color:var(--danger);margin-top:2px">
                <div style="flex:1"><div style="display:flex;align-items:center;gap:6px;font-size:12px;font-weight:600;color:var(--danger)"><svg style="width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round"><use href="#i-alert"></use></svg> Also whitelist the process <span style="padding:1px 7px;border-radius:99px;background:var(--danger-bg);color:var(--danger);font-size:10px;font-weight:700;letter-spacing:.5px">ADVANCED · RISKY</span></div>
                <div style="font-size:11px;color:var(--text-3);line-height:1.5;margin-top:3px">If this file runs later, real-time will not scan anything the process does. Use only for known-safe tools that touch many files (e.g. an offline installer). A wrong choice here disables protection for that program.</div></div>
              </label>
              <sc-if value="{{ rmWlProc }}" hint-placeholder-val="{{ false }}">
              <div style="margin-left:26px;margin-top:2px;padding:10px 12px;background:var(--danger-bg);border:1px solid var(--danger-line);border-radius:6px">
                <div style="font-size:11px;font-weight:600;color:var(--danger);margin-bottom:6px">Reason (required — recorded in your event log)</div>
                <textarea rows="2" oninput="{{ rmReasonInput }}" placeholder="e.g. Corporate KMS activator for offline setup — reviewed by IT, confirmed safe" style="width:100%;padding:7px 9px;border:1px solid var(--border-strong);border-radius:6px;font-family:inherit;font-size:11.5px;background:var(--card);color:var(--text-1);resize:vertical">{{ rmReason }}</textarea>
                <div style="font-size:10.5px;color:var(--danger);margin-top:5px">Cracks and keygens are a common malware vector. Your reason is logged for audit.</div>
              </div>
              </sc-if>
            </div>
            <div style="display:flex;align-items:center;gap:8px;padding:12px 18px;border-top:1px solid var(--border);background:var(--surface)">
              <span style="flex:1;font-size:11px;color:var(--text-3)">Action will be logged as an event.</span>
              <button onclick="{{ rmClose }}" style="padding:7px 14px;border:1px solid var(--border-strong);border-radius:7px;font-size:12px;color:var(--text-2);font-weight:600;background:transparent;font-family:inherit;cursor:pointer">Cancel</button>
              <button onclick="{{ rmSubmit }}" disabled="{{ rmDisabled }}" style="padding:7px 17px;border:none;border-radius:7px;font-size:12px;background:var(--accent);color:var(--on-accent);font-weight:600;font-family:inherit;cursor:pointer;opacity:{{ rmOpacity }}">Restore</button>
            </div>
          </div>
        </div>
        </sc-if>

        <!-- ============ QUARANTINE ============ -->
        <sc-if value="{{ pQuarantine }}" hint-placeholder-val="{{ false }}">
        <section data-screen-label="Quarantine" style="animation:tqFade .25s ease">
          <div style="display:flex;align-items:center;gap:14px;padding:14px 18px;border-radius:12px;background:{{ quarHeroBg }};border:1px solid {{ quarHeroBorder }};margin-bottom:12px">
            <div style="width:48px;height:48px;border-radius:10px;background:{{ quarHeroIconBg }};color:#fff;display:flex;align-items:center;justify-content:center;flex:none"><svg style="width:24px;height:24px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"><use href="#i-lock"></use></svg></div>
            <div style="flex:1;line-height:1.3">
              <div style="font-size:15px;font-weight:600;color:var(--text-1)">Quarantine</div>
              <div style="font-size:11.5px;color:var(--text-2);margin-top:1px">{{ quarHeading }} — they can't run.</div>
            </div>
          </div>
          <div style="display:flex;gap:8px;align-items:center;margin-bottom:8px">
            <input type="text" placeholder="Search file, detection or path…" oninput="{{ quarSearch }}" style="flex:1;padding:6px 10px;border-radius:6px;border:1px solid var(--border);background:var(--card);color:var(--text-1);font-family:inherit;font-size:12px"/>
            <label style="display:inline-flex;align-items:center;gap:5px;font-size:11.5px;color:var(--text-2);cursor:pointer"><input type="checkbox" checked="{{ quarHideDups }}" onchange="{{ quarToggleHideDups }}" style="accent-color:var(--accent)"> Hide vault duplicates</label>
          </div>
          <sc-if value="{{ quarHasDups }}" hint-placeholder-val="{{ false }}">
          <div style="display:flex;gap:10px;align-items:center;padding:8px 13px;border-radius:7px;background:var(--accent-soft);color:var(--accent-ink);font-size:11.5px;font-weight:500;margin-bottom:8px">
            <svg style="width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;flex:none"><use href="#i-lock"></use></svg>
            <span><b>{{ quarDupText }}</b> — leftover records from an older bug (fixed). Safe to clear.</span>
            <span style="flex:1"></span>
            <button onclick="{{ quarClearDups }}" style="padding:4px 12px;border-radius:5px;font-weight:600;font-size:11px;cursor:pointer;border:1px solid var(--accent);background:var(--accent);color:var(--on-accent);font-family:inherit">Clear duplicates</button>
          </div>
          </sc-if>
          <sc-if value="{{ quarHasSel }}" hint-placeholder-val="{{ false }}">
          <div style="display:flex;gap:10px;align-items:center;padding:9px 13px;border-radius:7px;background:var(--success-bg);border:1px solid var(--success-line);margin-bottom:8px">
            <span style="font-size:12.5px;font-weight:700;color:var(--success)">{{ quarSelCount }}</span>
            <span style="flex:1"></span>
            <button onclick="{{ quarClearSel }}" style="padding:5px 12px;border:1px solid var(--border-strong);border-radius:6px;font-size:11px;font-weight:600;color:var(--text-2);background:var(--card);font-family:inherit;cursor:pointer">Clear selection</button>
            <button onclick="{{ quarRestore }}" style="padding:5px 12px;border:1px solid var(--border-strong);border-radius:6px;font-size:11px;font-weight:600;color:var(--accent);background:var(--card);font-family:inherit;cursor:pointer">Restore selected…</button>
            <button onclick="{{ quarDelete }}" style="padding:5px 12px;border:none;border-radius:6px;font-size:11px;font-weight:600;color:#fff;background:var(--danger);font-family:inherit;cursor:pointer">Delete selected</button>
          </div>
          </sc-if>
          <div style="background:var(--card);border:1px solid var(--border);border-radius:8px;box-shadow:var(--shadow-sm);overflow:auto;max-height:calc(100vh - 195px)">
            <sc-if value="{{ quarHasItems }}" hint-placeholder-val="{{ true }}"><table style="min-width:960px;width:100%;border-collapse:collapse;font-size:12px;table-layout:fixed">
              <colgroup><col style="width:36px"/><col style="width:22%"/><col style="width:100px"/><col style="width:18%"/><col style="width:auto"/><col style="width:120px"/><col style="width:170px"/></colgroup>
              <thead class="tq-stky"><tr>
                <th style="width:36px;padding:9px 0 9px 14px;border-bottom:1px solid var(--border)"><input type="checkbox" checked="{{ quarAllSel }}" onchange="{{ quarToggleAll }}" style="accent-color:var(--accent)"></th>
                <th style="text-align:left;padding:9px 12px;color:var(--text-3);font-weight:600;border-bottom:1px solid var(--border);text-transform:uppercase;font-size:10px;letter-spacing:.5px">File</th>
                <th style="text-align:left;padding:9px 12px;color:var(--text-3);font-weight:600;border-bottom:1px solid var(--border);text-transform:uppercase;font-size:10px;letter-spacing:.5px">Category</th>
                <th style="text-align:left;padding:9px 12px;color:var(--text-3);font-weight:600;border-bottom:1px solid var(--border);text-transform:uppercase;font-size:10px;letter-spacing:.5px">Detection</th>
                <th style="text-align:left;padding:9px 12px;color:var(--text-3);font-weight:600;border-bottom:1px solid var(--border);text-transform:uppercase;font-size:10px;letter-spacing:.5px">Original path</th>
                <th style="text-align:left;padding:9px 12px;color:var(--text-3);font-weight:600;border-bottom:1px solid var(--border);text-transform:uppercase;font-size:10px;letter-spacing:.5px">Date</th>
                <th style="text-align:left;padding:9px 12px;color:var(--text-3);font-weight:600;border-bottom:1px solid var(--border);text-transform:uppercase;font-size:10px;letter-spacing:.5px">Action</th>
              </tr></thead>
              <tbody>
                <sc-for list="{{ quarView }}" as="q" hint-placeholder-count="5">
                <tr style-hover="background:var(--accent-dim)">
                    <td style="padding:8px 0 8px 14px;border-bottom:1px solid var(--border)"><input type="checkbox" checked="{{ q.sel }}" onchange="{{ q.toggle }}" style="accent-color:var(--accent)"></td>
                    <td style="padding:8px 12px;border-bottom:1px solid var(--border);overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="{{ q.file }}"><b style="color:{{ q.fileColor }}">{{ q.file }}</b></td>
                    <td style="padding:8px 12px;border-bottom:1px solid var(--border)"><span style="display:inline-flex;padding:2px 9px;border-radius:99px;font-size:11px;font-weight:600;color:{{ q.catFg }};background:{{ q.catBg }}">{{ q.cat }}</span></td>
                    <td style="padding:8px 12px;border-bottom:1px solid var(--border);font-family:Consolas,ui-monospace,monospace;font-size:11px;color:var(--text-2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="{{ q.det }}">{{ q.det }}</td>
                    <td style="padding:8px 12px;border-bottom:1px solid var(--border);font-family:Consolas,ui-monospace,monospace;font-size:10.5px;color:var(--text-3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="{{ q.path }}">{{ q.path }}</td>
                    <td style="padding:8px 12px;border-bottom:1px solid var(--border);color:var(--text-3);white-space:nowrap">{{ q.date }}</td>
                    <td style="padding:8px 12px;border-bottom:1px solid var(--border);white-space:nowrap">
                      <span style="display:inline-flex;gap:5px">
                        <button onclick="{{ q.allow }}" style="padding:3px 10px;border-radius:5px;font-weight:600;font-size:11px;cursor:pointer;border:1px solid var(--accent-line);background:var(--accent-soft);color:var(--accent);font-family:inherit">Restore</button>
                        <button onclick="{{ q.del }}" style="padding:3px 10px;border-radius:5px;font-weight:600;font-size:11px;cursor:pointer;border:1px solid var(--danger-line);background:var(--danger-bg);color:var(--danger);font-family:inherit">Delete</button>
                      </span>
                    </td>
                  </tr>
                
                
                </sc-for>
              </tbody>
            </table></sc-if>
            <sc-if value="{{ quarIsEmpty }}" hint-placeholder-val="{{ false }}">
              <div style="text-align:center;padding:44px 20px">
                <div style="width:60px;height:60px;border-radius:14px;background:var(--success-bg);color:var(--success);display:inline-flex;align-items:center;justify-content:center;margin-bottom:12px"><svg style="width:34px;height:34px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round"><use href="#i-shield"></use></svg></div>
                <div style="font-size:14px;font-weight:700;color:var(--text-1);margin-bottom:4px">Quarantine is empty</div>
                <div style="font-size:12px;color:var(--text-3);max-width:400px;margin:0 auto;line-height:1.5">No threats have been locked away. TreeIQ hasn't found anything harmful on your PC yet.</div>
              </div>
            </sc-if>
          </div>
        </section>
        </sc-if>

        <!-- ============ SYSTEM GUARD (EDR cluster — shell) ============ -->
        <sc-if value="{{ pSystemGuard }}" hint-placeholder-val="{{ false }}">
        <section data-screen-label="System Guard" style="animation:tqFade .25s ease">
          <!-- Hero -->
          <div style="background:var(--card);border:1px solid var(--border);border-radius:12px;padding:16px 18px;box-shadow:var(--shadow-sm);display:flex;align-items:center;gap:14px;margin-bottom:12px">
            <div style="width:48px;height:48px;border-radius:10px;background:var(--accent);display:flex;align-items:center;justify-content:center;flex:none">
              <svg style="width:24px;height:24px;stroke:#fff;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round" viewBox="0 0 24 24"><path d="M12 3l8 3v6c0 5-3.5 8-8 9-4.5-1-8-4-8-9V6z"/><path d="M12 8v8M8 12h8"/></svg>
            </div>
            <div style="flex:1;line-height:1.3">
              <div style="font-size:14px;font-weight:700;color:var(--text-1)">System Guard</div>
              <div style="font-size:11.5px;color:var(--text-3);margin-top:2px"><span style="color:{{ sgView.statusFg }};font-weight:600">&#9679; {{ sgView.statusText }}</span> &middot; {{ sgView.subText }}</div>
            </div>
            <div onclick="{{ sgView.toggleMaster }}" style="width:44px;height:24px;border-radius:99px;background:{{ sgView.masterBg }};cursor:pointer;position:relative;transition:background .2s;flex:none"><div style="position:absolute;top:2.5px;left:2.5px;width:19px;height:19px;border-radius:50%;background:#fff;transition:transform .2s;transform:translateX({{ sgView.masterKnob }});box-shadow:0 1px 2px rgba(0,0,0,.25)"></div></div>
          </div>

          <!-- Tier note (only when the current plan doesn't include System Guard) -->
          <sc-if value="{{ sgView.showTierNote }}" hint-placeholder-val="{{ false }}">
          <div style="background:var(--warning-dim);border:1px solid var(--warning);border-radius:10px;padding:10px 14px;margin-bottom:12px;font-size:11.5px;color:var(--warning)">System Guard is included with Internet Security and Total Security. Your current plan doesn&#39;t include it &mdash; modules stay off.</div>
          </sc-if>

          <!-- 3 stat tiles -->
          <div style="display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-bottom:12px">
            <div style="background:var(--card);border:1px solid var(--border);border-radius:10px;padding:12px 14px"><div style="font-size:11px;color:var(--text-3)">Modules active</div><div style="font-size:20px;font-weight:700;color:var(--text-1);margin:4px 0 2px">{{ sgView.activeCount }} <span style="font-size:12px;color:var(--text-3);font-weight:500">of 7</span></div></div>
            <div style="background:var(--card);border:1px solid var(--border);border-radius:10px;padding:12px 14px"><div style="font-size:11px;color:var(--text-3)">Protection</div><div style="font-size:16px;font-weight:700;margin:6px 0 2px;color:{{ sgView.statusFg }}">{{ sgView.statusText }}</div></div>
            <div style="background:var(--card);border:1px solid var(--border);border-radius:10px;padding:12px 14px"><div style="font-size:11px;color:var(--text-3)">Idle cost</div><div style="font-size:16px;font-weight:700;color:var(--text-1);margin:6px 0 2px">~20 MB &middot; &lt;1%</div></div>
          </div>

          <!-- Module health -->
          <div style="background:var(--card);border:1px solid var(--border);border-radius:10px;overflow:hidden;margin-bottom:12px">
            <div style="padding:11px 16px;border-bottom:1px solid var(--border);font-size:13px;font-weight:600;color:var(--text-1)">Modules</div>
            <sc-for list="{{ sgView.modules }}" as="m" hint-placeholder-count="7">
              <div style="display:flex;align-items:center;gap:12px;padding:12px 16px;border-bottom:1px solid var(--border)">
                <span style="width:8px;height:8px;border-radius:50%;background:{{ m.dot }};flex:none"></span>
                <div style="flex:1;line-height:1.35"><div style="font-size:13px;font-weight:600;color:var(--text-1)">{{ m.name }}</div><div style="font-size:11px;color:var(--text-3)">{{ m.desc }}</div></div>
                <span style="font-size:10px;font-weight:600;color:var(--text-3);background:var(--surface);padding:2px 7px;border-radius:5px">{{ m.stage }}</span>
                <div onclick="{{ m.toggle }}" style="width:38px;height:21px;border-radius:99px;background:{{ m.bg }};cursor:pointer;position:relative;transition:background .2s;flex:none"><div style="position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;transform:translateX({{ m.knob }});box-shadow:0 1px 2px rgba(0,0,0,.25)"></div></div>
              </div>
            </sc-for>
            <!-- Simulation -->
            <div style="display:flex;align-items:center;gap:12px;padding:12px 16px;background:var(--surface)">
              <span style="width:8px;height:8px;border-radius:50%;background:{{ sgView.simDot }};flex:none"></span>
              <div style="flex:1;line-height:1.35"><div style="font-size:13px;font-weight:600;color:var(--text-1)">Simulation mode</div><div style="font-size:11px;color:var(--text-3)">Behavior rules only log what they would do &mdash; nothing is killed. Use to tune.</div></div>
              <div onclick="{{ sgView.toggleSim }}" style="width:38px;height:21px;border-radius:99px;background:{{ sgView.simBg }};cursor:pointer;position:relative;transition:background .2s;flex:none"><div style="position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;transform:translateX({{ sgView.simKnob }});box-shadow:0 1px 2px rgba(0,0,0,.25)"></div></div>
            </div>
          </div>

          <div style="text-align:center;font-size:10.5px;color:var(--text-3)">Detection engines roll out module by module. Your choices here are saved and take effect as each module ships.</div>
        </section>
        </sc-if>

        <!-- ============ RANSOM GUARD (v1.1.0 dashboard) ============ -->
        <sc-if value="{{ pRansomGuard }}" hint-placeholder-val="{{ false }}">
        <section data-screen-label="Ransom Guard" style="animation:tqFade .25s ease">
          <!-- Hero card -->
          <div style="background:var(--card);border:1px solid var(--border);border-radius:12px;padding:16px 18px;box-shadow:var(--shadow-sm);display:flex;align-items:center;gap:14px;margin-bottom:12px">
            <div style="width:48px;height:48px;border-radius:10px;background:#DC2626;display:flex;align-items:center;justify-content:center;flex:none">
              <svg style="width:24px;height:24px;stroke:#fff;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round" viewBox="0 0 24 24"><rect x="5" y="10" width="14" height="10" rx="2"/><path d="M8 10V7a4 4 0 0 1 8 0v3"/></svg>
            </div>
            <div style="flex:1;line-height:1.3">
              <div style="font-size:14px;font-weight:700;color:var(--text-1)">Ransom Guard</div>
              <div style="font-size:11.5px;color:var(--text-3);margin-top:2px">
                <span style="color:{{ rgHero.statusFg }};font-weight:600">&#9679; {{ rgHero.statusText }}</span> &middot; {{ rgHero.modeText }} &middot; {{ rgHero.sensText }} sensitivity
              </div>
            </div>
            <button onclick="{{ rgHero.testRun }}" style="padding:7px 14px;border-radius:6px;font-size:12px;font-weight:600;color:var(--text-1);background:var(--card);border:1px solid var(--border-strong);cursor:pointer;font-family:inherit">Simulate ransomware</button>
          </div>

          <!-- 4 stat tiles -->
          <div style="display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-bottom:12px">
            <div style="background:var(--card);border:1px solid var(--border);border-radius:10px;padding:12px 14px">
              <div style="font-size:11px;color:var(--text-3)">Canary honeypots</div>
              <div style="font-size:20px;font-weight:700;color:var(--text-1);margin:4px 0 2px">{{ rgHero.canaryCount }}</div>
              <div style="font-size:11px;color:var(--success)">All intact</div>
            </div>
            <div style="background:var(--card);border:1px solid var(--border);border-radius:10px;padding:12px 14px">
              <div style="font-size:11px;color:var(--text-3)">Rollback snapshots</div>
              <div style="font-size:20px;font-weight:700;color:var(--text-1);margin:4px 0 2px">{{ rgHero.snapCount }}</div>
              <div style="font-size:11px;color:var(--text-3)">{{ rgHero.snapMB }} MB used</div>
            </div>
            <div style="background:var(--card);border:1px solid var(--border);border-radius:10px;padding:12px 14px">
              <div style="font-size:11px;color:var(--text-3)">Snapshot window</div>
              <div style="font-size:20px;font-weight:700;color:var(--text-1);margin:4px 0 2px">15 min</div>
              <div style="font-size:11px;color:var(--text-3)">sliding, per file</div>
            </div>
            <div style="background:var(--card);border:1px solid var(--border);border-radius:10px;padding:12px 14px">
              <div style="font-size:11px;color:var(--text-3)">Attacks blocked</div>
              <div style="font-size:20px;font-weight:700;color:var(--text-1);margin:4px 0 2px">{{ rgHero.attackCount }}</div>
              <div style="font-size:11px;color:var(--text-3)">since install</div>
            </div>
          </div>

          <!-- Activity strip (placeholder — timeline chart deferred to next pass) -->
          <div style="background:var(--card);border:1px solid var(--border);border-radius:10px;padding:14px 16px;margin-bottom:12px">
            <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:10px">
              <div style="font-size:13px;font-weight:600;color:var(--text-1)">Activity &mdash; last 30 days</div>
              <a onclick="{{ rgHero.viewLog }}" style="font-size:11px;color:var(--accent);cursor:pointer">View full log &rarr;</a>
            </div>
            <!-- P4 · 30-day bar chart. Bars are painted inline via SVG rects sized off rgHero.chartBars. -->
            <div style="height:60px;background:var(--surface);border-radius:6px;padding:6px 8px;display:flex;align-items:flex-end;gap:2px">
              <sc-for list="{{ rgHero.chartBars }}" as="bar" hint-placeholder-count="30">
                <div title="{{ bar.title }}" style="flex:1;height:{{ bar.h }};min-height:2px;background:{{ bar.color }};border-radius:2px 2px 0 0"></div>
              </sc-for>
            </div>
            <div style="text-align:center;font-size:10px;color:var(--text-3);margin-top:4px">{{ rgHero.activityText }}</div>
          </div>

          <!-- Quick actions -->
          <div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
            <a onclick="{{ rgHero.viewCanaries }}" style="background:var(--card);border:1px solid var(--border);border-radius:10px;padding:12px 14px;display:flex;align-items:center;gap:12px;cursor:pointer;text-decoration:none;color:var(--text-1)">
              <svg style="width:18px;height:18px;stroke:var(--accent);fill:none;stroke-width:1.8;stroke-linecap:round"><use href="#i-eye"></use></svg>
              <div style="flex:1"><div style="font-size:12.5px;font-weight:600">View canary paths</div><div style="font-size:10.5px;color:var(--text-3);margin-top:2px">{{ rgHero.canaryCount }} decoy files across 4 folders</div></div>
              <svg style="width:14px;height:14px;stroke:var(--text-3);fill:none;stroke-width:2"><use href="#i-chevron-right"></use></svg>
            </a>
            <a onclick="{{ rgHero.openRestore }}" style="background:var(--card);border:1px solid var(--border);border-radius:10px;padding:12px 14px;display:flex;align-items:center;gap:12px;cursor:pointer;text-decoration:none;color:var(--text-1)">
              <svg style="width:18px;height:18px;stroke:var(--success);fill:none;stroke-width:1.8;stroke-linecap:round"><use href="#i-update"></use></svg>
              <div style="flex:1"><div style="font-size:12.5px;font-weight:600">Rollback snapshots</div><div style="font-size:10.5px;color:var(--text-3);margin-top:2px">Browse or force-refresh cache</div></div>
              <svg style="width:14px;height:14px;stroke:var(--text-3);fill:none;stroke-width:2"><use href="#i-chevron-right"></use></svg>
            </a>
          </div>
        </section>
        </sc-if>

        <!-- ============ ROLLBACK / RESTORE (#7) ============ -->
        <sc-if value="{{ pRollback }}" hint-placeholder-val="{{ false }}">
        <section data-screen-label="Restore" style="animation:tqFade .25s ease">
          <div style="background:var(--card);border:1px solid var(--border);border-radius:12px;padding:16px 18px;box-shadow:var(--shadow-sm);display:flex;align-items:center;gap:14px;margin-bottom:12px">
            <div style="width:44px;height:44px;border-radius:10px;background:var(--success-bg);display:flex;align-items:center;justify-content:center;flex:none">
              <svg style="width:22px;height:22px;stroke:var(--success);fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round" viewBox="0 0 24 24"><path d="M3 12a9 9 0 1 0 3-6.7L3 8"/><path d="M3 3v5h5"/></svg>
            </div>
            <div style="flex:1;line-height:1.3">
              <div style="font-size:14px;font-weight:700;color:var(--text-1)">Rollback snapshots</div>
              <div style="font-size:11.5px;color:var(--text-3);margin-top:2px">Your files, kept safe from before an attack. Restore anything.</div>
            </div>
            <button onclick="{{ rbView.reload }}" style="padding:7px 14px;border-radius:6px;font-size:12px;font-weight:600;color:var(--text-1);background:var(--card);border:1px solid var(--border-strong);cursor:pointer;font-family:inherit">Refresh</button>
          </div>

          <div style="display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-bottom:12px">
            <div style="background:var(--card);border:1px solid var(--border);border-radius:10px;padding:12px 14px"><div style="font-size:11px;color:var(--text-3)">Files protected</div><div style="font-size:20px;font-weight:700;color:var(--text-1);margin-top:4px">{{ rbView.total }}</div></div>
            <div style="background:var(--card);border:1px solid var(--border);border-radius:10px;padding:12px 14px"><div style="font-size:11px;color:var(--text-3)">Needs restore</div><div style="font-size:20px;font-weight:700;color:{{ rbView.encFg }};margin-top:4px">{{ rbView.encCount }}</div></div>
            <div style="background:var(--card);border:1px solid var(--border);border-radius:10px;padding:12px 14px"><div style="font-size:11px;color:var(--text-3)">Cache used</div><div style="font-size:20px;font-weight:700;color:var(--text-1);margin-top:4px">{{ rbView.usedMB }} MB</div></div>
          </div>

          <div style="display:flex;align-items:center;gap:6px;margin-bottom:10px;flex-wrap:wrap">
            <sc-for list="{{ rbView.chips }}" as="c" hint-placeholder-count="6">
              <button onclick="{{ c.select }}" style="font-size:11.5px;padding:5px 12px;border-radius:6px;cursor:pointer;font-weight:600;font-family:inherit;background:{{ c.bg }};color:{{ c.fg }};border:1px solid {{ c.bd }}">{{ c.label }}</button>
            </sc-for>
            <div style="flex:1;min-width:12px"></div>
            <input type="search" placeholder="Search files…" value="{{ rbView.queryVal }}" oninput="{{ rbView.setQuery }}" style="padding:5px 10px;border-radius:6px;border:1px solid var(--border-strong);background:var(--card);color:var(--text-1);font-size:11.5px;font-family:inherit;width:210px;outline:none" />
          </div>

          <div style="background:var(--card);border:1px solid var(--border);border-radius:10px;overflow:hidden">
            <div style="display:flex;align-items:center;gap:10px;padding:9px 14px;border-bottom:1px solid var(--border);font-size:11px;color:var(--text-3)">
              <span onclick="{{ rbView.toggleAll }}" style="cursor:pointer;display:flex;align-items:center;gap:7px;color:var(--text-2);font-weight:600">
                <span style="width:15px;height:15px;flex:none;border:1.5px solid {{ rbView.allBd }};border-radius:4px;background:{{ rbView.allBg }};display:flex;align-items:center;justify-content:center"><svg style="width:10px;height:10px;stroke:#fff;fill:none;stroke-width:3;display:{{ rbView.allChk }}"><use href="#i-check"></use></svg></span>
                Select all
              </span>
              <span style="flex:1"></span><span style="width:70px">Size</span><span style="width:96px">Snapshot</span><span style="width:88px;text-align:right">Status</span>
            </div>
            <div style="max-height:calc(100vh - 340px);min-height:220px;overflow:auto">
              <sc-for list="{{ rbView.rows }}" as="f" hint-placeholder-count="6">
                <div onclick="{{ f.toggle }}" style="display:flex;align-items:center;gap:10px;padding:10px 14px;border-bottom:1px solid var(--border);cursor:pointer;background:{{ f.rowBg }}">
                  <span style="width:16px;height:16px;flex:none;border:1.5px solid {{ f.boxBd }};border-radius:4px;background:{{ f.boxBg }};display:flex;align-items:center;justify-content:center"><svg style="width:11px;height:11px;stroke:#fff;fill:none;stroke-width:3;display:{{ f.checkDisp }}"><use href="#i-check"></use></svg></span>
                  <div style="flex:1;min-width:0"><div style="font-size:12.5px;color:var(--text-1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis">{{ f.name }}</div><div style="font-size:10.5px;color:var(--text-3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis">{{ f.folder }}</div></div>
                  <span style="width:70px;font-size:11.5px;color:var(--text-2)">{{ f.sizeStr }}</span>
                  <span style="width:96px;font-size:11.5px;color:var(--text-2)">{{ f.timeStr }}</span>
                  <span style="width:88px;text-align:right;font-size:10.5px;font-weight:600;color:{{ f.stFg }}">{{ f.stLabel }}</span>
                </div>
              </sc-for>
              <sc-if value="{{ rbView.empty }}" hint-placeholder-val="{{ false }}">
                <div style="padding:26px 14px;text-align:center;font-size:12px;color:var(--text-3)">{{ rbView.emptyMsg }}</div>
              </sc-if>
            </div>
            <div style="display:flex;align-items:center;gap:10px;padding:12px 14px;border-top:1px solid var(--border);background:var(--surface)">
              <div style="flex:1;font-size:11.5px;color:var(--text-2)">{{ rbView.selText }}</div>
              <button onclick="{{ rbView.restoreAll }}" style="padding:7px 13px;border-radius:6px;font-size:12px;font-weight:600;color:var(--text-1);background:var(--card);border:1px solid var(--border-strong);cursor:pointer;font-family:inherit">Restore all touched</button>
              <button onclick="{{ rbView.restoreSel }}" style="padding:7px 14px;border-radius:6px;font-size:12px;font-weight:700;color:#fff;background:var(--success);border:none;cursor:pointer;font-family:inherit;opacity:{{ rbView.restoreOpacity }}">{{ rbView.restoreLabel }}</button>
            </div>
          </div>

          <sc-if value="{{ rbView.msgVisible }}" hint-placeholder-val="{{ false }}">
            <div style="margin-top:10px;font-size:12px;color:{{ rbView.msgFg }};background:var(--card);border:1px solid var(--border);border-radius:8px;padding:10px 12px">{{ rbView.msg }}</div>
          </sc-if>
        </section>
        </sc-if>

        <!-- ============ UPDATES ============ -->
        <sc-if value="{{ pUpdates }}" hint-placeholder-val="{{ false }}">
        <section data-screen-label="Updates" style="animation:tqFade .25s ease">
          <div style="background:var(--card);border:1px solid var(--border);border-radius:12px;padding:16px 18px;box-shadow:var(--shadow-sm);display:flex;align-items:center;gap:16px">
            <div style="width:48px;height:48px;border-radius:10px;display:flex;align-items:center;justify-content:center;flex:none;background:{{ updHeaderBg }};color:{{ updHeaderFg }}"><svg style="width:24px;height:24px;stroke:currentColor;fill:none;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round"><use href="{{ updHeaderIcon }}"></use></svg></div>
            <div style="flex:1;line-height:1.35"><div style="font-size:13.5px;font-weight:700;color:var(--text-1)">{{ updHeadline }}</div><div style="font-size:11.5px;color:var(--text-3)">Version {{ dbVersionStr }}{{ updCheckedAgoTail }}</div></div>
            <div style="display:flex;align-items:center;gap:8px">
              <sc-if value="{{ updPending }}" hint-placeholder-val="{{ false }}">
                <button onclick="{{ applyReload }}" title="Apply the downloaded signatures now — protection reloads in ~5 seconds." style="display:inline-flex;align-items:center;gap:6px;padding:8px 14px;border-radius:6px;font-weight:600;font-size:12px;cursor:pointer;border:none;background:#B45309;color:#fff;font-family:inherit" style-hover="background:#92400E"><svg style="width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round"><path d="M20 6L9 17l-5-5"/></svg> Apply now</button>
              </sc-if>
              <button onclick="{{ checkUpdate }}" style="display:inline-flex;align-items:center;gap:7px;padding:8px 16px;border-radius:6px;font-weight:600;font-size:12px;cursor:pointer;border:none;background:var(--accent);color:var(--on-accent);font-family:inherit;opacity:{{ updBtnOpacity }}" style-hover="background:var(--accent-hover)"><svg style="width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round"><use href="#i-update"></use></svg> {{ updBtnLabel }}</button>
            </div>
          </div>
          <!-- v1.0.6.1 — Analyze Logs (below Update). Collects diagnostic logs + uploads them via
               the SYSTEM service, then shows the exact step-by-step result inline. -->
          <div style="margin-top:10px;display:flex;flex-direction:column;gap:8px">
            <button onclick="{{ analyzeLogs }}" title="Collect diagnostic logs and upload them for analysis." style="align-self:flex-start;display:inline-flex;align-items:center;gap:7px;padding:8px 16px;border-radius:6px;font-weight:600;font-size:12px;cursor:pointer;border:1px solid var(--border);background:var(--card);color:var(--text-1);font-family:inherit;opacity:{{ analyzeBtnOpacity }}"><svg style="width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round"><use href="#i-update"></use></svg> {{ analyzeBtnLabel }}</button>
            <sc-if value="{{ analyzeMsgVisible }}" hint-placeholder-val="{{ false }}">
              <div style="font-size:11px;color:{{ analyzeMsgColor }};line-height:1.5;white-space:pre-wrap;font-family:ui-monospace,Consolas,monospace;background:var(--card);border:1px solid var(--border);border-radius:6px;padding:10px 12px;max-height:200px;overflow:auto">{{ analyzeMsg }}</div>
            </sc-if>
          </div>
          <!-- v1.0.6.2 — Upgrade (delta self-upgrade over SFTP, test builds). Checks the upgrade
               server for a newer fix, then downloads + verifies + applies it (app restarts). -->
          <div style="margin-top:8px;display:flex;flex-direction:column;gap:8px">
            <button onclick="{{ upgradeNow }}" title="Download and apply the latest test fix from the upgrade server." style="align-self:flex-start;display:inline-flex;align-items:center;gap:7px;padding:8px 16px;border-radius:6px;font-weight:600;font-size:12px;cursor:pointer;border:none;background:#0f766e;color:#fff;font-family:inherit;opacity:{{ upgradeBtnOpacity }}"><svg style="width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round"><path d="M12 15V3m0 0L8 7m4-4l4 4M4 21h16"/></svg> {{ upgradeBtnLabel }}</button>
            <sc-if value="{{ upgradeMsgVisible }}" hint-placeholder-val="{{ false }}">
              <div style="font-size:11.5px;color:{{ upgradeMsgColor }};line-height:1.5;white-space:pre-wrap">{{ upgradeMsg }}</div>
            </sc-if>
          </div>
          <!-- Live progress panel during a check / download / verify / install. Hidden when idle.
               During the DOWNLOAD phase, shows a 4-row list ("Signature block N") with a mini bar
               per row — the customer sees real per-part progress instead of a lone percentage. In
               other phases the list collapses and the phase label + overall bar remain. -->
          <!-- Live progress panel. One calm bar with a reassuring sub-line beneath. Behind the
               scenes the service still tracks 4 parallel per-part downloads (useful for logs), but
               the UI presents a single unified view — customers shouldn't see or think about how
               many files we split the VDB into. Rebalanced 3-90% for download so the bar reflects
               real work being done. -->
          <!-- Update-progress panel. No bar, no percentage — just centered bouncing dots that
               signal "alive without judgement". Phase label above, reassuring one-liner below.
               When phase = done, dots are replaced by a green ✓ so the panel resolves cleanly. -->
          <sc-if value="{{ updProgressVisible }}" hint-placeholder-val="{{ false }}">
            <div style="background:var(--card);border:1px solid var(--border);border-radius:8px;padding:20px 18px;margin-top:12px;box-shadow:var(--shadow-sm);text-align:center">
              <div style="font-size:12.5px;font-weight:600;color:var(--text-1);margin-bottom:14px">{{ updPhaseLabel }}</div>
              <div style="display:flex;justify-content:center;margin-bottom:14px;min-height:16px">
                <sc-if value="{{ updDone }}" hint-placeholder-val="{{ false }}">
                  <div style="font-size:16px;color:var(--success);font-weight:700;line-height:1">✓</div>
                </sc-if>
                <sc-if value="{{ updWorking }}" hint-placeholder-val="{{ true }}">
                  <div class="tq-dots big"><span></span><span></span><span></span></div>
                </sc-if>
              </div>
              <div style="font-size:11.5px;color:var(--text-3);line-height:1.5">{{ updSubText }}</div>
            </div>
          </sc-if>
          <div style="margin-top:12px">
            <div style="background:var(--card);border:1px solid var(--border);border-radius:8px;padding:4px 16px;box-shadow:var(--shadow-sm)">
              <div style="display:flex;align-items:center;justify-content:space-between;padding:11px 0;border-bottom:1px solid var(--border)">
                <div style="line-height:1.35"><div style="font-weight:600;color:var(--text-1);font-size:12.5px">Automatic updates</div></div>
                <div onclick="{{ updAuto.toggle }}" style="width:38px;height:21px;border-radius:99px;background:{{ updAuto.bg }};cursor:pointer;position:relative;transition:background .2s;flex:none"><div style="position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;transform:translateX({{ updAuto.knob }});box-shadow:0 1px 2px rgba(0,0,0,.25)"></div></div>
              </div>
              <div style="display:flex;align-items:center;justify-content:space-between;padding:11px 0">
                <div style="line-height:1.35"><div style="font-weight:600;color:var(--text-1);font-size:12.5px">Update frequency</div></div>
                <select value="{{ updHoursStr }}" onchange="{{ updHoursChange }}" style="background:var(--surface);border:1px solid var(--border);border-radius:6px;padding:6px 10px;color:var(--text-1);font-family:inherit;font-size:12px;outline:none">
                  <option value="1">Every hour</option>
                  <option value="4">Every 4 hours</option>
                  <option value="6">Every 6 hours</option>
                  <option value="24">Daily</option>
                </select>
              </div>
            </div>
          </div>
        </section>
        </sc-if>

        <!-- ============ LOGS ============ -->
        <sc-if value="{{ pLogs }}" hint-placeholder-val="{{ false }}">
        <section data-screen-label="Logs" style="animation:tqFade .25s ease">
          <div style="display:flex;align-items:center;gap:14px;padding:14px 18px;border-radius:12px;background:var(--brand-tint);border:1px solid var(--brand-border);margin-bottom:14px">
            <div style="width:48px;height:48px;border-radius:10px;background:var(--brand);color:#fff;display:flex;align-items:center;justify-content:center;flex:none"><svg style="width:24px;height:24px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"><use href="#i-logs"></use></svg></div>
            <div style="flex:1;line-height:1.3">
              <div style="font-size:15px;font-weight:600;color:var(--text-1)">Activity logs</div>
              <div style="font-size:11.5px;color:var(--text-2);margin-top:1px">Scans, detections, real-time events and system updates from the last 5,000 log lines.</div>
            </div>
          </div>
          <div style="display:flex;gap:2px;border-bottom:1px solid var(--border);margin-bottom:12px;align-items:center">
            <sc-for list="{{ logTabs }}" as="t" hint-placeholder-count="5">
              <button onclick="{{ t.select }}" style="border:none;background:none;padding:8px 13px;color:{{ t.color }};font-weight:600;font-size:12px;cursor:pointer;border-bottom:2px solid {{ t.line }};font-family:inherit;margin-bottom:-1px">{{ t.label }}</button>
            </sc-for>
            <div style="flex:1"></div>
            <input type="search" placeholder="Search logs…" value="{{ logQueryVal }}" oninput="{{ setLogQuery }}" style="padding:5px 10px;border-radius:6px;border:1px solid var(--border-strong);background:var(--card);color:var(--text-1);font-size:11.5px;font-family:inherit;width:200px;margin:0 8px 6px 0;outline:none" />
            <button onclick="{{ exportLogs }}" style="padding:5px 12px;border-radius:6px;font-weight:600;font-size:11.5px;cursor:pointer;border:1px solid var(--border-strong);background:transparent;color:var(--text-2);font-family:inherit;margin-bottom:6px" style-hover="border-color:var(--accent);color:var(--accent)">Export</button>
          </div>
          <sc-if value="{{ isFlatLogTab }}" hint-placeholder-val="{{ true }}">
            <div style="background:var(--card);border:1px solid var(--border);border-radius:8px;box-shadow:var(--shadow-sm);overflow:auto;max-height:calc(100vh - 190px)">
              <table style="width:100%;border-collapse:collapse;font-size:12px">
                <thead class="tq-stky"><tr>
                  <th style="width:160px;text-align:left;padding:9px 14px;color:var(--text-3);font-weight:600;border-bottom:1px solid var(--border);text-transform:uppercase;font-size:10px;letter-spacing:.5px">Time</th>
                  <th style="width:110px;text-align:left;padding:9px 14px;color:var(--text-3);font-weight:600;border-bottom:1px solid var(--border);text-transform:uppercase;font-size:10px;letter-spacing:.5px">Type</th>
                  <th style="text-align:left;padding:9px 14px;color:var(--text-3);font-weight:600;border-bottom:1px solid var(--border);text-transform:uppercase;font-size:10px;letter-spacing:.5px">Event</th>
                </tr></thead>
                <tbody>
                  <sc-for list="{{ logRows }}" as="l" hint-placeholder-count="8">
                  <tr title="{{ l.rowTitle }}" style="background:{{ l.rowBg }}" style-hover="background:var(--accent-dim)">
                      <td onclick="{{ l.openReadonly }}" style="padding:8px 14px;border-bottom:1px solid var(--border);font-family:Consolas,ui-monospace,monospace;font-size:11px;color:var(--text-3);white-space:nowrap;cursor:{{ l.cursor }}">{{ l.t }}</td>
                      <td onclick="{{ l.openReadonly }}" style="padding:8px 14px;border-bottom:1px solid var(--border);cursor:{{ l.cursor }}"><span style="display:inline-flex;align-items:center;gap:5px;padding:2px 9px;border-radius:99px;font-size:10.5px;font-weight:600;color:{{ l.fg }};background:{{ l.bg }}"><svg style="width:11px;height:11px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;flex:none"><use href="{{ l.typeIcon }}"></use></svg>{{ l.type }}</span></td>
                      <td onclick="{{ l.openReadonly }}" style="padding:8px 14px;border-bottom:1px solid var(--border);color:var(--text-2);cursor:{{ l.cursor }}">{{ l.msg }}</td>
                    </tr>
                  </sc-for>
                </tbody>
              </table>
              <sc-if value="{{ logsEmpty }}" hint-placeholder-val="{{ false }}">
                <div style="text-align:center;padding:32px;color:var(--text-3);font-size:12px">No log entries</div>
              </sc-if>
            </div>
          </sc-if>
          <sc-if value="{{ isScanTab }}" hint-placeholder-val="{{ false }}">
            <div style="background:var(--card);border:1px solid var(--border);border-radius:8px;box-shadow:var(--shadow-sm);overflow:auto;max-height:calc(100vh - 190px)">
              <table style="width:100%;border-collapse:collapse;font-size:12px">
                <thead class="tq-stky"><tr>
                  <th style="width:120px;text-align:left;padding:6px 12px;color:var(--text-3);font-weight:600;border-bottom:1px solid var(--border);text-transform:uppercase;font-size:10px;letter-spacing:.5px">Type</th>
                  <th style="text-align:left;padding:6px 12px;color:var(--text-3);font-weight:600;border-bottom:1px solid var(--border);text-transform:uppercase;font-size:10px;letter-spacing:.5px">Target</th>
                  <th style="width:130px;text-align:left;padding:6px 12px;color:var(--text-3);font-weight:600;border-bottom:1px solid var(--border);text-transform:uppercase;font-size:10px;letter-spacing:.5px">Status</th>
                  <th style="width:80px;text-align:right;padding:6px 12px;color:var(--text-3);font-weight:600;border-bottom:1px solid var(--border);text-transform:uppercase;font-size:10px;letter-spacing:.5px">Files</th>
                  <th style="width:78px;text-align:right;padding:6px 12px;color:var(--text-3);font-weight:600;border-bottom:1px solid var(--border);text-transform:uppercase;font-size:10px;letter-spacing:.5px">Threats</th>
                  <th style="width:100px;text-align:right;padding:6px 12px;color:var(--text-3);font-weight:600;border-bottom:1px solid var(--border);text-transform:uppercase;font-size:10px;letter-spacing:.5px">Neutralized</th>
                  <th style="width:78px;text-align:right;padding:6px 12px;color:var(--text-3);font-weight:600;border-bottom:1px solid var(--border);text-transform:uppercase;font-size:10px;letter-spacing:.5px">Elapsed</th>
                  <th style="width:140px;text-align:left;padding:6px 12px;color:var(--text-3);font-weight:600;border-bottom:1px solid var(--border);text-transform:uppercase;font-size:10px;letter-spacing:.5px">When</th>
                </tr></thead>
                <tbody>
                  <sc-for list="{{ scanHistoryRows }}" as="r" hint-placeholder-count="6">
                    <tr title="Click to open full scan details" style="background:{{ r.rowBg }}" style-hover="background:var(--accent-dim)">
                      <td onclick="{{ r.openReadonly }}" style="padding:5px 12px;border-bottom:1px solid var(--border);cursor:pointer;white-space:nowrap"><span style="display:inline-flex;align-items:center;gap:5px;padding:2px 9px;border-radius:99px;font-size:11px;font-weight:600;color:{{ r.modeFg }};background:{{ r.modeBg }}"><svg style="width:11px;height:11px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;flex:none"><use href="{{ r.modeIcon }}"></use></svg>{{ r.modeLabel }}</span></td>
                      <td onclick="{{ r.openReadonly }}" style="padding:5px 12px;border-bottom:1px solid var(--border);color:var(--text-2);font-family:Consolas,ui-monospace,monospace;font-size:11px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:0">{{ r.target }}</td>
                      <td onclick="{{ r.openReadonly }}" style="padding:5px 12px;border-bottom:1px solid var(--border);cursor:pointer;white-space:nowrap"><span style="display:inline-flex;align-items:center;gap:5px;padding:2px 9px;border-radius:99px;font-size:11px;font-weight:600;color:{{ r.statusFg }};background:{{ r.statusBg }}"><svg style="width:11px;height:11px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;flex:none"><use href="{{ r.statusIcon }}"></use></svg>{{ r.statusLabel }}</span></td>
                      <td onclick="{{ r.openReadonly }}" style="padding:5px 12px;border-bottom:1px solid var(--border);color:var(--text-1);text-align:right;cursor:pointer;font-variant-numeric:tabular-nums">{{ r.files }}</td>
                      <td onclick="{{ r.openReadonly }}" style="padding:5px 12px;border-bottom:1px solid var(--border);text-align:right;cursor:pointer"><span style="display:inline-block;padding:2px 10px;border-radius:99px;font-size:11px;font-weight:600;color:{{ r.threatsColor }};background:{{ r.threatsBg }};font-variant-numeric:tabular-nums">{{ r.threats }}</span></td>
                      <td onclick="{{ r.openReadonly }}" style="padding:5px 12px;border-bottom:1px solid var(--border);color:var(--text-1);text-align:right;cursor:pointer;font-variant-numeric:tabular-nums">{{ r.neutralized }}</td>
                      <td onclick="{{ r.openReadonly }}" style="padding:5px 12px;border-bottom:1px solid var(--border);color:var(--text-1);text-align:right;cursor:pointer;font-variant-numeric:tabular-nums">{{ r.elapsed }}</td>
                      <td onclick="{{ r.openReadonly }}" style="padding:5px 12px;border-bottom:1px solid var(--border);color:var(--text-3);font-family:Consolas,ui-monospace,monospace;font-size:11px;cursor:pointer;white-space:nowrap">{{ r.when }}</td>
                    </tr>
                  </sc-for>
                </tbody>
              </table>
              <sc-if value="{{ scanHistoryEmpty }}" hint-placeholder-val="{{ false }}">
                <div style="text-align:center;padding:32px;color:var(--text-3);font-size:12px">No completed scans yet. Run any scan and it'll be recorded here.</div>
              </sc-if>
            </div>
          </sc-if>
          <!-- PROTECTION TAB — structured detection log: which guard caught what, and the action taken -->
          <sc-if value="{{ isDetectionTab }}" hint-placeholder-val="{{ false }}">
            <div style="background:var(--card);border:1px solid var(--border);border-radius:8px;box-shadow:var(--shadow-sm);overflow:auto;max-height:calc(100vh - 190px)">
              <table style="width:100%;border-collapse:collapse;font-size:12px">
                <thead class="tq-stky"><tr>
                  <th style="width:150px;text-align:left;padding:6px 12px;color:var(--text-3);font-weight:600;border-bottom:1px solid var(--border);text-transform:uppercase;font-size:10px;letter-spacing:.5px">Time</th>
                  <th style="width:120px;text-align:left;padding:6px 12px;color:var(--text-3);font-weight:600;border-bottom:1px solid var(--border);text-transform:uppercase;font-size:10px;letter-spacing:.5px">Guard</th>
                  <th style="text-align:left;padding:6px 12px;color:var(--text-3);font-weight:600;border-bottom:1px solid var(--border);text-transform:uppercase;font-size:10px;letter-spacing:.5px">Threat</th>
                  <th style="width:110px;text-align:left;padding:6px 12px;color:var(--text-3);font-weight:600;border-bottom:1px solid var(--border);text-transform:uppercase;font-size:10px;letter-spacing:.5px">Category</th>
                  <th style="text-align:left;padding:6px 12px;color:var(--text-3);font-weight:600;border-bottom:1px solid var(--border);text-transform:uppercase;font-size:10px;letter-spacing:.5px">File</th>
                  <th style="width:120px;text-align:left;padding:6px 12px;color:var(--text-3);font-weight:600;border-bottom:1px solid var(--border);text-transform:uppercase;font-size:10px;letter-spacing:.5px">Action</th>
                </tr></thead>
                <tbody>
                  <sc-for list="{{ detectionRows }}" as="d" hint-placeholder-count="8">
                    <tr title="{{ d.path }}" style="background:{{ d.rowBg }}" style-hover="background:var(--accent-dim)">
                      <td style="padding:6px 12px;border-bottom:1px solid var(--border);color:var(--text-3);font-family:Consolas,ui-monospace,monospace;font-size:11px;white-space:nowrap">{{ d.time }}</td>
                      <td style="padding:6px 12px;border-bottom:1px solid var(--border);white-space:nowrap"><span style="display:inline-block;padding:2px 9px;border-radius:99px;font-size:10.5px;font-weight:600;color:{{ d.guardFg }};background:{{ d.guardBg }}">{{ d.guard }}</span></td>
                      <td style="padding:6px 12px;border-bottom:1px solid var(--border);color:var(--text-1);font-weight:500">{{ d.threat }}</td>
                      <td style="padding:6px 12px;border-bottom:1px solid var(--border);color:var(--text-2)">{{ d.category }}</td>
                      <td style="padding:6px 12px;border-bottom:1px solid var(--border);color:var(--text-2);font-family:Consolas,ui-monospace,monospace;font-size:11px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:0">{{ d.path }}</td>
                      <td style="padding:6px 12px;border-bottom:1px solid var(--border);white-space:nowrap"><span style="display:inline-block;padding:2px 9px;border-radius:99px;font-size:10.5px;font-weight:600;color:var(--accent);background:var(--accent-dim)">{{ d.action }}</span></td>
                    </tr>
                  </sc-for>
                </tbody>
              </table>
              <sc-if value="{{ detectionsEmpty }}" hint-placeholder-val="{{ false }}">
                <div style="text-align:center;padding:32px;color:var(--text-3);font-size:12px">No detections yet. Every block by File / Script / Process / Ransom / USB Guard will appear here.</div>
              </sc-if>
            </div>
          </sc-if>
          <!-- USB TAB — structured events (matches Scan tab pattern) -->
          <sc-if value="{{ isUsbTab }}" hint-placeholder-val="{{ false }}">
            <div style="background:var(--card);border:1px solid var(--border);border-radius:8px;box-shadow:var(--shadow-sm);overflow:auto;max-height:calc(100vh - 190px)">
              <table style="width:100%;border-collapse:collapse;font-size:12px">
                <thead class="tq-stky"><tr>
                  <th style="width:140px;text-align:left;padding:6px 12px;color:var(--text-3);font-weight:600;border-bottom:1px solid var(--border);text-transform:uppercase;font-size:10px;letter-spacing:.5px">When</th>
                  <th style="width:70px;text-align:left;padding:6px 12px;color:var(--text-3);font-weight:600;border-bottom:1px solid var(--border);text-transform:uppercase;font-size:10px;letter-spacing:.5px">Drive</th>
                  <th style="text-align:left;padding:6px 12px;color:var(--text-3);font-weight:600;border-bottom:1px solid var(--border);text-transform:uppercase;font-size:10px;letter-spacing:.5px">Label</th>
                  <th style="width:80px;text-align:right;padding:6px 12px;color:var(--text-3);font-weight:600;border-bottom:1px solid var(--border);text-transform:uppercase;font-size:10px;letter-spacing:.5px">Files</th>
                  <th style="width:78px;text-align:right;padding:6px 12px;color:var(--text-3);font-weight:600;border-bottom:1px solid var(--border);text-transform:uppercase;font-size:10px;letter-spacing:.5px">Threats</th>
                  <th style="width:130px;text-align:left;padding:6px 12px;color:var(--text-3);font-weight:600;border-bottom:1px solid var(--border);text-transform:uppercase;font-size:10px;letter-spacing:.5px">Status</th>
                  <th style="width:110px;text-align:left;padding:6px 12px;color:var(--text-3);font-weight:600;border-bottom:1px solid var(--border);text-transform:uppercase;font-size:10px;letter-spacing:.5px">Action</th>
                </tr></thead>
                <tbody>
                  <sc-for list="{{ usbRows }}" as="u" hint-placeholder-count="6">
                    <tr title="{{ u.rowTitle }}" style="background:{{ u.rowBg }}" style-hover="background:var(--accent-dim)">
                      <td onclick="{{ u.openReadonly }}" style="padding:5px 12px;border-bottom:1px solid var(--border);color:var(--text-3);font-family:Consolas,ui-monospace,monospace;font-size:11px;cursor:{{ u.cursor }};white-space:nowrap">{{ u.when }}</td>
                      <td onclick="{{ u.openReadonly }}" style="padding:5px 12px;border-bottom:1px solid var(--border);color:var(--text-1);font-family:Consolas,ui-monospace,monospace;font-size:11px;cursor:{{ u.cursor }};white-space:nowrap;font-weight:600">{{ u.drive }}</td>
                      <td onclick="{{ u.openReadonly }}" style="padding:5px 12px;border-bottom:1px solid var(--border);color:var(--text-2);cursor:{{ u.cursor }};white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:0">{{ u.label }}</td>
                      <td onclick="{{ u.openReadonly }}" style="padding:5px 12px;border-bottom:1px solid var(--border);color:var(--text-1);text-align:right;cursor:{{ u.cursor }};font-variant-numeric:tabular-nums">{{ u.files }}</td>
                      <td onclick="{{ u.openReadonly }}" style="padding:5px 12px;border-bottom:1px solid var(--border);text-align:right;cursor:{{ u.cursor }}"><span style="display:inline-block;padding:2px 10px;border-radius:99px;font-size:11px;font-weight:600;color:{{ u.threatsColor }};background:{{ u.threatsBg }};font-variant-numeric:tabular-nums">{{ u.threats }}</span></td>
                      <td onclick="{{ u.openReadonly }}" style="padding:5px 12px;border-bottom:1px solid var(--border);cursor:{{ u.cursor }};white-space:nowrap"><span style="display:inline-flex;align-items:center;gap:5px;padding:2px 9px;border-radius:99px;font-size:11px;font-weight:600;color:{{ u.statusFg }};background:{{ u.statusBg }}"><svg style="width:11px;height:11px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;flex:none"><use href="{{ u.statusIcon }}"></use></svg>{{ u.statusLabel }}</span></td>
                      <td onclick="{{ u.openReadonly }}" style="padding:5px 12px;border-bottom:1px solid var(--border);color:var(--text-2);cursor:{{ u.cursor }};white-space:nowrap;font-size:11px">{{ u.action }}</td>
                    </tr>
                  </sc-for>
                </tbody>
              </table>
              <sc-if value="{{ usbEmpty }}" hint-placeholder-val="{{ false }}">
                <div style="text-align:center;padding:32px;color:var(--text-3);font-size:12px">No USB events recorded yet. Insert a USB drive and events will appear here.</div>
              </sc-if>
            </div>
          </sc-if>
        </section>
        </sc-if>

        <!-- ============ TOOLS (Phase 1 — tile page) ============
             Consolidates Logs / Quarantine / Whitelist under a single sidebar item. Each tile is
             a per-tool card with its own SVG icon, live count badge, and Open button. In Phases
             2-4 the Open button will spawn a new WebView2 process so each tool's memory is
             isolated from the main app — matches ESET / Kaspersky / Bitdefender's Tools page. -->
        <sc-if value="{{ pTools }}" hint-placeholder-val="{{ false }}">
        <section data-screen-label="Tools" style="animation:tqFade .25s ease">
          <div style="display:flex;align-items:center;gap:14px;padding:14px 18px;border-radius:12px;background:var(--brand-tint);border:1px solid var(--brand-border);margin-bottom:14px">
            <div style="width:48px;height:48px;border-radius:10px;background:var(--brand);color:#fff;display:flex;align-items:center;justify-content:center;flex:none"><svg style="width:24px;height:24px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"><use href="#i-box"></use></svg></div>
            <div style="flex:1;line-height:1.3">
              <div style="font-size:15px;font-weight:600;color:var(--text-1)">Tools</div>
              <div style="font-size:11.5px;color:var(--text-2);margin-top:1px">Each tool opens in its own window — no impact on the main app.</div>
            </div>
          </div>

          <div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:12px">
            <sc-for list="{{ toolTiles }}" as="t" hint-placeholder-count="3">
              <div style="background:var(--card);border:1px solid var(--border);border-radius:11px;padding:16px 18px;box-shadow:var(--shadow-sm);display:flex;flex-direction:column;gap:10px;transition:transform .15s ease,box-shadow .15s ease" style-hover="transform:translateY(-2px);box-shadow:var(--shadow-md)">
                <div style="display:flex;align-items:center;gap:10px">
                  <div style="width:38px;height:38px;border-radius:9px;background:{{ t.accentBg }};color:{{ t.accent }};display:flex;align-items:center;justify-content:center;flex:none"><svg style="width:20px;height:20px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"><use href="{{ t.icon }}"></use></svg></div>
                  <div style="flex:1;line-height:1.3">
                    <div style="font-size:13.5px;font-weight:700;color:var(--text-1);display:flex;align-items:center;gap:8px">{{ t.name }} <sc-if value="{{ t.badge }}" hint-placeholder-val="{{ false }}"><span style="font-size:10.5px;font-weight:700;padding:1px 8px;border-radius:99px;background:{{ t.accentBg }};color:{{ t.accent }}">{{ t.badge }}</span></sc-if></div>
                    <div style="font-size:11px;color:var(--text-3);margin-top:2px">{{ t.desc }}</div>
                  </div>
                </div>
                <div style="display:flex;align-items:center;justify-content:space-between;margin-top:2px">
                  <div style="font-size:10.5px;color:var(--text-3)">Opens in a new window</div>
                  <button onclick="{{ t.open }}" style="display:inline-flex;align-items:center;gap:6px;padding:6px 13px;border-radius:6px;font-weight:600;font-size:11.5px;cursor:pointer;border:none;background:var(--brand);color:var(--on-brand);font-family:inherit" style-hover="background:var(--brand-hover)">Open <svg style="width:11px;height:11px;stroke:currentColor;fill:none;stroke-width:2.4;stroke-linecap:round;stroke-linejoin:round"><use href="#i-chev"></use></svg></button>
                </div>
              </div>
            </sc-for>
          </div>
        </section>
        </sc-if>

        <!-- ============ SETTINGS ============ -->
        <sc-if value="{{ pSettings }}" hint-placeholder-val="{{ false }}">
        <section data-screen-label="Settings" style="animation:tqFade .25s ease">

          <!-- ============ 01 GENERAL ============ -->
          <div class="tqset-block" style="--sc:#6B7280">
            <div class="tqset-hd">
              <div class="tqset-ic"><svg viewBox="0 0 24 24"><line x1="4" y1="21" x2="4" y2="14"/><line x1="4" y1="10" x2="4" y2="3"/><line x1="12" y1="21" x2="12" y2="12"/><line x1="12" y1="8" x2="12" y2="3"/><line x1="20" y1="21" x2="20" y2="16"/><line x1="20" y1="12" x2="20" y2="3"/><line x1="1" y1="14" x2="7" y2="14"/><line x1="9" y1="8" x2="15" y2="8"/><line x1="17" y1="16" x2="23" y2="16"/></svg></div>
              <div><div class="tqset-t">General</div><div class="tqset-st">Global preferences and startup</div></div>
            </div>
            <div class="tqset-grp">
              <div class="tqset-row">
                <div class="tqset-rb"><div class="tqset-nm">Show notifications</div></div>
                <div onclick="{{ notifTgl.toggle }}" style="width:38px;height:21px;border-radius:99px;background:{{ notifTgl.bg }};cursor:pointer;position:relative;transition:background .2s;flex:none"><div style="position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;transform:translateX({{ notifTgl.knob }});box-shadow:0 1px 2px rgba(0,0,0,.25)"></div></div>
              </div>
            </div>
          </div>

          <!-- ============ 02 REAL-TIME PROTECTION ============ -->
          <div class="tqset-block" style="--sc:#3EBB6A">
            <div class="tqset-hd">
              <div class="tqset-ic"><svg viewBox="0 0 24 24"><path d="M12 22s7-3.5 7-9V5.5L12 3 5 5.5V13c0 5.5 7 9 7 9z"/><polyline points="9 12 11 14 15 10"/></svg></div>
              <div><div class="tqset-t">File Guard</div><div class="tqset-st">What TreeIQ does while you work</div></div>
            </div>
            <div class="tqset-grp">
              <div class="tqset-row">
                <div class="tqset-rb"><div class="tqset-nm">File Guard</div></div>
                <div onclick="{{ rtMasterTgl.toggle }}" style="width:38px;height:21px;border-radius:99px;background:{{ rtMasterTgl.bg }};cursor:pointer;position:relative;transition:background .2s;flex:none"><div style="position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;transform:translateX({{ rtMasterTgl.knob }});box-shadow:0 1px 2px rgba(0,0,0,.25)"></div></div>
              </div>
              <div class="tqset-row">
                <div class="tqset-rb"><div class="tqset-nm">On detection &mdash; real-time</div></div>
                <select onchange="{{ onRtActionChange }}" style="min-width:190px;background:var(--surface);border:1px solid var(--border-strong);border-radius:6px;padding:6px 10px;color:var(--text-1);font-family:inherit;font-size:12px;outline:none;cursor:pointer">
                  <sc-for list="{{ rtActionOpts }}" as="o" hint-placeholder-count="4">
                    <sc-if value="{{ o.isSelected }}" hint-placeholder-val="{{ false }}"><option value="{{ o.value }}" selected>{{ o.label }}</option></sc-if>
                    <sc-if value="{{ o.isNotSelected }}" hint-placeholder-val="{{ true }}"><option value="{{ o.value }}">{{ o.label }}</option></sc-if>
                  </sc-for>
                </select>
              </div>
              <div class="tqset-row">
                <div class="tqset-rb"><div class="tqset-nm">What to scan</div></div>
                <div style="display:inline-flex;background:var(--surface);border:1px solid var(--border);border-radius:6px;padding:2px">
                  <sc-for list="{{ scanScopeChoices }}" as="x" hint-placeholder-count="2">
                    <button onclick="{{ x.select }}" style="border:none;padding:4px 11px;border-radius:5px;cursor:pointer;font-size:11.5px;font-weight:600;background:{{ x.bg }};color:{{ x.color }};font-family:inherit">{{ x.label }}</button>
                  </sc-for>
                </div>
              </div>
              <div style="padding:11px 0;border-bottom:1px solid var(--border)">
                <div class="tqset-nm" style="margin-bottom:8px">Real-time extensions</div>
                <div style="display:flex;gap:8px;margin-bottom:10px">
                  <input type="text" value="{{ extValue }}" onchange="{{ onExtInput }}" placeholder="e.g. exe, dll, vbs, js" style="flex:1;background:var(--surface);border:1px solid var(--border);border-radius:6px;padding:7px 10px;color:var(--text-1);font-family:inherit;font-size:12px;outline:none">
                  <button onclick="{{ addExt }}" style="padding:6px 15px;border-radius:6px;font-weight:600;font-size:11.5px;cursor:pointer;border:1px solid var(--border-strong);background:transparent;color:var(--text-2);font-family:inherit">Add</button>
                </div>
                <div style="display:flex;flex-wrap:wrap;gap:6px">
                  <sc-for list="{{ extChips }}" as="e" hint-placeholder-count="6">
                    <span style="display:inline-flex;align-items:center;gap:6px;padding:3px 6px 3px 10px;background:var(--surface);border:1px solid var(--border);border-radius:99px;font-size:11.5px;color:var(--text-2);font-family:Consolas,ui-monospace,monospace">.{{ e.label }}<button onclick="{{ e.remove }}" style="border:none;background:none;color:var(--text-3);cursor:pointer;display:flex;padding:0" style-hover="color:var(--danger)"><svg style="width:12px;height:12px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round"><use href="#i-trash"></use></svg></button></span>
                  </sc-for>
                </div>
              </div>
              <div class="tqset-row">
                <div class="tqset-rb"><div class="tqset-nm">Skip files larger than</div></div>
                <input type="number" value="{{ rtMaxMB }}" onchange="{{ onRtMax }}" style="width:90px;background:var(--surface);border:1px solid var(--border);border-radius:6px;padding:6px 10px;color:var(--text-1);font-family:inherit;font-size:12px;outline:none;text-align:right">
              </div>
              <div class="tqset-row">
                <div class="tqset-rb"><div class="tqset-nm">Scan inside archives</div></div>
                <div onclick="{{ rtArchivesTgl.toggle }}" style="width:38px;height:21px;border-radius:99px;background:{{ rtArchivesTgl.bg }};cursor:pointer;position:relative;transition:background .2s;flex:none"><div style="position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;transform:translateX({{ rtArchivesTgl.knob }});box-shadow:0 1px 2px rgba(0,0,0,.25)"></div></div>
              </div>
              <div class="tqset-row">
                <div class="tqset-rb"><div class="tqset-nm">Scan network files</div></div>
                <div onclick="{{ rtNetworkTgl.toggle }}" style="width:38px;height:21px;border-radius:99px;background:{{ rtNetworkTgl.bg }};cursor:pointer;position:relative;transition:background .2s;flex:none"><div style="position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;transform:translateX({{ rtNetworkTgl.knob }});box-shadow:0 1px 2px rgba(0,0,0,.25)"></div></div>
              </div>
              <div class="tqset-row">
                <div class="tqset-rb"><div class="tqset-nm">Kill running malware</div></div>
                <div onclick="{{ killTgl.toggle }}" style="width:38px;height:21px;border-radius:99px;background:{{ killTgl.bg }};cursor:pointer;position:relative;transition:background .2s;flex:none"><div style="position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;transform:translateX({{ killTgl.knob }});box-shadow:0 1px 2px rgba(0,0,0,.25)"></div></div>
              </div>
            </div>
          </div>

          <!-- ============ SCRIPT GUARD (v22 UI) ============
               First-class module — real-time script-host blocking. Was buried inside Advanced as
               "Script & macro protection (AMSI)". Now surfaced as its own named module. -->
          <div class="tqset-block" style="--sc:#D97706">
            <div class="tqset-hd">
              <div class="tqset-ic"><svg viewBox="0 0 24 24"><path d="M12 2l8 4v6c0 5-3.5 9-8 10-4.5-1-8-5-8-10V6l8-4z"/><polyline points="8 12 10 14 16 10"/></svg></div>
              <div><div class="tqset-t">Script Guard <span class="tqset-pill">Win10+</span></div></div>
              <sc-if value="{{ amsiAvailable }}" hint-placeholder-val="{{ true }}"><div onclick="{{ amsiEnabledTgl.toggle }}" style="width:38px;height:21px;border-radius:99px;background:{{ amsiEnabledTgl.bg }};cursor:pointer;position:relative;transition:background .2s;flex:none;margin-left:auto"><div style="position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;transform:translateX({{ amsiEnabledTgl.knob }});box-shadow:0 1px 2px rgba(0,0,0,.25)"></div></div></sc-if>
            </div>
            <sc-if value="{{ amsiUnavailable }}" hint-placeholder-val="{{ false }}">
              <div class="tqset-grp" style="padding:14px 16px"><div style="font-size:11.5px;color:var(--text-3);display:flex;align-items:center;gap:8px"><svg style="width:15px;height:15px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round;flex:none"><use href="#i-alert"></use></svg> {{ amsiStatusText }}</div></div>
            </sc-if>
            <sc-if value="{{ amsiAvailable }}" hint-placeholder-val="{{ true }}">
              <div class="tqset-grp">
                <div class="tqset-row">
                  <div class="tqset-rb"><div class="tqset-nm">Detection mode</div></div>
                  <div style="display:inline-flex;background:var(--surface);border:1px solid var(--border);border-radius:6px;padding:2px">
                    <sc-for list="{{ amsiModeSeg }}" as="m" hint-placeholder-count="2"><button onclick="{{ m.select }}" style="border:none;padding:5px 14px;border-radius:5px;cursor:pointer;font-size:11.5px;font-weight:600;background:{{ m.bg }};color:{{ m.color }};font-family:inherit">{{ m.label }}</button></sc-for>
                  </div>
                </div>
                <div class="tqset-row" style="flex-direction:column;align-items:stretch;gap:0">
                  <div class="tqset-nm" style="margin-bottom:6px">Script types monitored</div>
                  <sc-for list="{{ amsiHostRows }}" as="h" hint-placeholder-count="4">
                    <div style="display:flex;align-items:center;justify-content:space-between;padding:7px 0;border-bottom:1px solid var(--border)">
                      <span style="font-size:12px;color:var(--text-2)">{{ h.title }}</span>
                      <div onclick="{{ h.toggle }}" style="width:38px;height:21px;border-radius:99px;background:{{ h.bg }};cursor:pointer;position:relative;transition:background .2s;flex:none"><div style="position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;transform:translateX({{ h.knob }});box-shadow:0 1px 2px rgba(0,0,0,.25)"></div></div>
                    </div>
                  </sc-for>
                </div>
                <div class="tqset-row">
                  <div class="tqset-rb"><div class="tqset-nm">Skip scripts larger than (MB)</div></div>
                  <input type="number" value="{{ amsiMaxMB }}" onchange="{{ onAmsiMax }}" style="width:90px;background:var(--surface);border:1px solid var(--border);border-radius:6px;padding:6px 10px;color:var(--text-1);font-family:inherit;font-size:12px;outline:none;text-align:right">
                </div>
                <div class="tqset-row">
                  <div class="tqset-rb"><div class="tqset-nm">Extra rule-based checks</div></div>
                  <div onclick="{{ amsiHeurTgl.toggle }}" style="width:38px;height:21px;border-radius:99px;background:{{ amsiHeurTgl.bg }};cursor:pointer;position:relative;transition:background .2s;flex:none"><div style="position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;transform:translateX({{ amsiHeurTgl.knob }});box-shadow:0 1px 2px rgba(0,0,0,.25)"></div></div>
                </div>
                <div class="tqset-row">
                  <div style="display:flex;align-items:center;gap:8px;font-size:11.5px;color:var(--text-2);flex:1"><svg style="width:15px;height:15px;stroke:var(--success);fill:none;stroke-width:1.9;stroke-linecap:round;flex:none"><use href="#i-check"></use></svg> {{ amsiStatusText }}</div>
                  <button onclick="{{ amsiReRegister }}" style="padding:5px 11px;border-radius:6px;font-weight:600;font-size:11px;cursor:pointer;border:1px solid var(--border-strong);background:transparent;color:var(--text-2);font-family:inherit">Re-register</button>
                </div>
              </div>
            </sc-if>
          </div>

          <!-- ============ CONTENT TYPES (v22 UI) ============
               Script / Macro / .NET protection surfaced up front instead of buried under Advanced.
               Each toggle flips BOTH real-time and on-demand engine flags at once. Advanced still
               holds the per-mode variants for power users. -->
          <div class="tqset-block" style="--sc:#0E7490">
            <div class="tqset-hd">
              <div class="tqset-ic"><svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="10" y1="13" x2="8" y2="15"/><line x1="14" y1="13" x2="16" y2="15"/><line x1="12" y1="12" x2="12" y2="17"/></svg></div>
              <div><div class="tqset-t">Content types</div></div>
            </div>
            <div class="tqset-grp">
              <sc-for list="{{ contentTypes }}" as="c">
                <div class="tqset-row">
                  <div class="tqset-rb"><div class="tqset-nm">{{ c.title }}</div></div>
                  <div onclick="{{ c.toggle }}" style="width:38px;height:21px;border-radius:99px;background:{{ c.bg }};cursor:pointer;position:relative;transition:background .2s;flex:none"><div style="position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;transform:translateX({{ c.knob }});box-shadow:0 1px 2px rgba(0,0,0,.25)"></div></div>
                </div>
              </sc-for>
            </div>
          </div>

          <!-- ============ RANSOM GUARD (v1.1.0) ============ -->
          <sc-if value="{{ rgSection.visible }}" hint-placeholder-val="{{ true }}">
          <div class="tqset-block" style="--sc:#DC2626">
            <div class="tqset-hd">
              <div class="tqset-ic"><svg viewBox="0 0 24 24"><rect x="5" y="10" width="14" height="10" rx="2"/><path d="M8 10V7a4 4 0 0 1 8 0v3"/></svg></div>
              <div><div class="tqset-t">Ransomware protection</div><div class="tqset-st">Behavioral safety net for unknown ransomware</div></div>
            </div>
            <div class="tqset-grp">
              <!-- Master toggle -->
              <div class="tqset-row">
                <div class="tqset-rb"><div class="tqset-nm">Ransomware protection</div><div class="tqset-hint">Master switch for the whole detector + rollback stack</div></div>
                <div onclick="{{ rgSection.masterToggle }}" style="width:38px;height:21px;border-radius:99px;background:{{ rgSection.masterBg }};cursor:pointer;position:relative;transition:background .2s;flex:none"><div style="position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;transform:translateX({{ rgSection.masterKnob }});box-shadow:0 1px 2px rgba(0,0,0,.25)"></div></div>
              </div>
              <!-- Behavior row (always on, dim toggle) -->
              <sc-if value="{{ rgSection.enabled }}" hint-placeholder-val="{{ true }}">
              <div class="tqset-row" style="background:var(--success-bg);border-radius:6px;padding:10px 12px;margin-top:6px">
                <div class="tqset-rb">
                  <div class="tqset-nm" style="color:var(--success);display:flex;align-items:center;gap:8px">Behavior signals<span style="font-size:10px;color:var(--success);background:rgba(0,110,86,0.15);padding:1px 6px;border-radius:6px">Always on</span></div>
                  <div class="tqset-hint" style="color:var(--success)">4 detectors &middot; canary, rapid-write, extension-flip, entropy &middot; 0 ms latency</div>
                </div>
                <div style="width:32px;height:18px;background:var(--success);border-radius:10px;position:relative;opacity:.55;flex:none"><div style="position:absolute;top:2px;left:16px;width:14px;height:14px;border-radius:50%;background:#fff"></div></div>
              </div>
              <!-- ProHunt add-on row -->
              <sc-if value="{{ rgSection.proHuntFeatureVisible }}" hint-placeholder-val="{{ true }}">
              <div class="tqset-row" style="background:var(--surface);border-radius:6px;padding:10px 12px;margin-top:6px">
                <div class="tqset-rb">
                  <div class="tqset-nm" style="display:flex;align-items:center;gap:8px">ProHunt &mdash; deep confirmation<span style="font-size:10px;color:var(--accent);background:var(--accent-dim);padding:1px 6px;border-radius:6px">Add-on</span></div>
                  <div class="tqset-hint">Adds scan engine confirmation on kill (family name) + cache eligibility gate &middot; +20-100 ms on trigger</div>
                </div>
                <div onclick="{{ rgSection.proHuntToggle }}" style="width:38px;height:21px;border-radius:99px;background:{{ rgSection.proHuntBg }};cursor:pointer;position:relative;transition:background .2s;flex:none"><div style="position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;transform:translateX({{ rgSection.proHuntKnob }});box-shadow:0 1px 2px rgba(0,0,0,.25)"></div></div>
              </div>
              </sc-if>
              <!-- Sensitivity -->
              <div class="tqset-row" style="margin-top:6px">
                <div class="tqset-rb"><div class="tqset-nm">Sensitivity</div><div class="tqset-hint">{{ rgSection.sensHint }}</div></div>
                <div style="display:inline-flex;background:var(--surface);border:1px solid var(--border);border-radius:6px;padding:2px">
                  <sc-for list="{{ rgSection.sensChoices }}" as="s" hint-placeholder-count="3">
                    <button onclick="{{ s.select }}" style="border:none;padding:4px 12px;border-radius:5px;cursor:pointer;font-size:11.5px;font-weight:600;background:{{ s.bg }};color:{{ s.color }};font-family:inherit">{{ s.label }}</button>
                  </sc-for>
                </div>
              </div>
              <!-- Info strip: canary + snapshot + last event -->
              <div style="padding:11px 0 3px;border-top:1px solid var(--border);margin-top:6px;display:grid;grid-template-columns:repeat(3,1fr);gap:10px">
                <div><div style="font-size:11px;color:var(--text-3)">Canary honeypots</div><div style="font-size:15px;font-weight:600;color:var(--text-1);margin-top:2px">{{ rgSection.canaryCount }} files</div></div>
                <div><div style="font-size:11px;color:var(--text-3)">Rollback snapshots</div><div style="font-size:15px;font-weight:600;color:var(--text-1);margin-top:2px">{{ rgSection.snapCount }} files</div><div style="font-size:11px;color:var(--text-3)">{{ rgSection.snapMB }} MB used</div></div>
                <div><div style="font-size:11px;color:var(--text-3)">Last activity</div><div style="font-size:15px;font-weight:600;color:var(--text-1);margin-top:2px">{{ rgSection.lastActivity }}</div></div>
              </div>
              </sc-if>
            </div>
          </div>
          </sc-if>

          <!-- ============ 03 ON-DEMAND SCANS ============ -->
          <div class="tqset-block" style="--sc:#1257D6">
            <div class="tqset-hd">
              <div class="tqset-ic"><svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg></div>
              <div><div class="tqset-t">On-demand scans</div><div class="tqset-st">How Quick / Full / Custom scans behave</div></div>
            </div>
            <div class="tqset-grp">
              <div class="tqset-row">
                <div class="tqset-rb"><div class="tqset-nm">On detection &mdash; on-demand</div></div>
                <select onchange="{{ onOdActionChange }}" style="min-width:190px;background:var(--surface);border:1px solid var(--border-strong);border-radius:6px;padding:6px 10px;color:var(--text-1);font-family:inherit;font-size:12px;outline:none;cursor:pointer">
                  <sc-for list="{{ odActionOpts }}" as="o" hint-placeholder-count="4">
                    <sc-if value="{{ o.isSelected }}" hint-placeholder-val="{{ false }}"><option value="{{ o.value }}" selected>{{ o.label }}</option></sc-if>
                    <sc-if value="{{ o.isNotSelected }}" hint-placeholder-val="{{ true }}"><option value="{{ o.value }}">{{ o.label }}</option></sc-if>
                  </sc-for>
                </select>
              </div>
              <div class="tqset-row">
                <div class="tqset-rb"><div class="tqset-nm">Scan removable drives (USB)</div></div>
                <div onclick="{{ usbTgl.toggle }}" style="width:38px;height:21px;border-radius:99px;background:{{ usbTgl.bg }};cursor:pointer;position:relative;transition:background .2s;flex:none"><div style="position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;transform:translateX({{ usbTgl.knob }});box-shadow:0 1px 2px rgba(0,0,0,.25)"></div></div>
              </div>
              <div class="tqset-row">
                <div class="tqset-rb"><div class="tqset-nm">Scan inside archives</div></div>
                <div onclick="{{ odArchivesTgl.toggle }}" style="width:38px;height:21px;border-radius:99px;background:{{ odArchivesTgl.bg }};cursor:pointer;position:relative;transition:background .2s;flex:none"><div style="position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;transform:translateX({{ odArchivesTgl.knob }});box-shadow:0 1px 2px rgba(0,0,0,.25)"></div></div>
              </div>
              <div class="tqset-row">
                <div class="tqset-rb"><div class="tqset-nm">Skip files larger than</div></div>
                <input type="number" value="{{ odMaxMB }}" onchange="{{ onOdMax }}" style="width:90px;background:var(--surface);border:1px solid var(--border);border-radius:6px;padding:6px 10px;color:var(--text-1);font-family:inherit;font-size:12px;outline:none;text-align:right">
              </div>

              <!-- Scan intensity (v26) — 3-mode radio. Smart is the new default, ~5× faster than
                   Thorough with the same threat coverage. Custom exposes the extension list. -->
              <div style="padding:10px 0 4px 0;border-top:1px solid var(--border)">
                <div class="tqset-nm" style="margin-bottom:8px">Scan intensity</div>
                <sc-for list="{{ odScanModes }}" as="m">
                  <label style="display:flex;align-items:flex-start;gap:10px;padding:8px 0;cursor:pointer">
                    <input type="radio" checked="{{ m.checked }}" onchange="{{ m.pick }}" style="accent-color:var(--accent);margin-top:2px">
                    <div style="flex:1;line-height:1.35">
                      <div style="font-size:12px;font-weight:600;color:var(--text-1)">{{ m.label }} <span style="font-size:10.5px;font-weight:700;padding:1px 7px;border-radius:99px;color:{{ m.badgeFg }};background:{{ m.badgeBg }}">{{ m.badge }}</span></div>
                      <div style="font-size:11px;color:var(--text-3);margin-top:2px">{{ m.desc }}</div>
                    </div>
                  </label>
                </sc-for>
                <sc-if value="{{ odScanCustomOpen }}" hint-placeholder-val="{{ false }}">
                  <div style="padding:8px 0 4px 26px">
                    <div style="font-size:11px;color:var(--text-3);margin-bottom:5px">Extensions to scan (comma-separated, no dots):</div>
                    <textarea rows="4" oninput="{{ onOdIncludeExts }}" style="width:100%;background:var(--surface);border:1px solid var(--border);border-radius:6px;padding:8px 10px;color:var(--text-1);font-family:Consolas,ui-monospace,monospace;font-size:11.5px;outline:none;resize:vertical">{{ odIncludeExtsText }}</textarea>
                    <button onclick="{{ resetToSmartDefaults }}" style="margin-top:6px;padding:5px 11px;border-radius:5px;font-weight:600;font-size:10.5px;cursor:pointer;border:1px solid var(--border-strong);background:transparent;color:var(--text-2);font-family:inherit">Reset to Smart defaults ({{ odSmartCount }} extensions)</button>
                  </div>
                </sc-if>
              </div>

              <div class="tqset-row">
                <div class="tqset-rb"><div class="tqset-nm">Scan-cache</div></div>
                <div onclick="{{ scanCacheTgl.toggle }}" style="width:38px;height:21px;border-radius:99px;background:{{ scanCacheTgl.bg }};cursor:pointer;position:relative;transition:background .2s;flex:none"><div style="position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;transform:translateX({{ scanCacheTgl.knob }});box-shadow:0 1px 2px rgba(0,0,0,.25)"></div></div>
              </div>
              <div class="tqset-row">
                <div class="tqset-rb"><div class="tqset-nm">Weekly root sweep</div></div>
                <div onclick="{{ weeklySweepTgl.toggle }}" style="width:38px;height:21px;border-radius:99px;background:{{ weeklySweepTgl.bg }};cursor:pointer;position:relative;transition:background .2s;flex:none"><div style="position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;transform:translateX({{ weeklySweepTgl.knob }});box-shadow:0 1px 2px rgba(0,0,0,.25)"></div></div>
              </div>
            </div>
          </div>

          <!-- ============ USB PROTECTION (2026-07-07) ============
               Master + 4 modules + auto-scan on insertion. When Master is OFF, sub-toggles are
               visually dimmed. Each toggle is independent — user can disable e.g. LNK analyzer if
               they legit have shortcut-based USB workflows and get false positives.
          -->
          <div class="tqset-block" style="--sc:#0D9488">
            <div class="tqset-hd">
              <div class="tqset-ic"><svg viewBox="0 0 24 24"><rect x="8" y="2" width="8" height="8" rx="1"/><rect x="9" y="10" width="6" height="12" rx="1"/><line x1="12" y1="6" x2="12" y2="6"/></svg></div>
              <div><div class="tqset-t">USB / Removable Media Protection</div></div>
            </div>
            <div class="tqset-grp">
              <div class="tqset-row">
                <div class="tqset-rb"><div class="tqset-nm">USB protection</div></div>
                <div onclick="{{ usbMasterTgl.toggle }}" style="width:38px;height:21px;border-radius:99px;background:{{ usbMasterTgl.bg }};cursor:pointer;position:relative;transition:background .2s;flex:none"><div style="position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;transform:translateX({{ usbMasterTgl.knob }});box-shadow:0 1px 2px rgba(0,0,0,.25)"></div></div>
              </div>
              <div class="tqset-row">
                <div class="tqset-rb"><div class="tqset-nm">Auto-scan on USB insertion</div></div>
                <div onclick="{{ usbAutoTgl.toggle }}" style="width:38px;height:21px;border-radius:99px;background:{{ usbAutoTgl.bg }};cursor:pointer;position:relative;transition:background .2s;flex:none"><div style="position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;transform:translateX({{ usbAutoTgl.knob }});box-shadow:0 1px 2px rgba(0,0,0,.25)"></div></div>
              </div>
              <div class="tqset-row">
                <div class="tqset-rb"><div class="tqset-nm">Vaccine mode</div></div>
                <div onclick="{{ usbVaccineTgl.toggle }}" style="width:38px;height:21px;border-radius:99px;background:{{ usbVaccineTgl.bg }};cursor:pointer;position:relative;transition:background .2s;flex:none"><div style="position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;transform:translateX({{ usbVaccineTgl.knob }});box-shadow:0 1px 2px rgba(0,0,0,.25)"></div></div>
              </div>
              <div class="tqset-row">
                <div class="tqset-rb"><div class="tqset-nm">Unhide &amp; repair</div></div>
                <div onclick="{{ usbUnhideTgl.toggle }}" style="width:38px;height:21px;border-radius:99px;background:{{ usbUnhideTgl.bg }};cursor:pointer;position:relative;transition:background .2s;flex:none"><div style="position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;transform:translateX({{ usbUnhideTgl.knob }});box-shadow:0 1px 2px rgba(0,0,0,.25)"></div></div>
              </div>
              <div class="tqset-row">
                <div class="tqset-rb"><div class="tqset-nm">Root-launcher flagger</div></div>
                <div onclick="{{ usbLauncherTgl.toggle }}" style="width:38px;height:21px;border-radius:99px;background:{{ usbLauncherTgl.bg }};cursor:pointer;position:relative;transition:background .2s;flex:none"><div style="position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;transform:translateX({{ usbLauncherTgl.knob }});box-shadow:0 1px 2px rgba(0,0,0,.25)"></div></div>
              </div>
              <div class="tqset-row">
                <div class="tqset-rb"><div class="tqset-nm">LNK analyzer</div></div>
                <div onclick="{{ usbLnkTgl.toggle }}" style="width:38px;height:21px;border-radius:99px;background:{{ usbLnkTgl.bg }};cursor:pointer;position:relative;transition:background .2s;flex:none"><div style="position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;transform:translateX({{ usbLnkTgl.knob }});box-shadow:0 1px 2px rgba(0,0,0,.25)"></div></div>
              </div>
              <div class="tqset-row">
                <div class="tqset-rb"><div class="tqset-nm">USB remediation</div></div>
                <div onclick="{{ usbRemediationTgl.toggle }}" style="width:38px;height:21px;border-radius:99px;background:{{ usbRemediationTgl.bg }};cursor:pointer;position:relative;transition:background .2s;flex:none"><div style="position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;transform:translateX({{ usbRemediationTgl.knob }});box-shadow:0 1px 2px rgba(0,0,0,.25)"></div></div>
              </div>

              <!-- Currently-connected USB drives + Vaccinate-all button (2026-07-07) -->
              <div class="tqset-row" style="flex-direction:column;align-items:stretch;gap:10px">
                <div style="display:flex;align-items:center;gap:10px">
                  <div class="tqset-rb" style="flex:1"><div class="tqset-nm">Currently connected drives</div></div>
                  <button onclick="{{ refreshUsbDrives }}" style="padding:6px 12px;border-radius:6px;font-size:11.5px;font-weight:600;color:var(--text-2);background:var(--card);border:1px solid var(--border-strong);cursor:pointer;font-family:inherit">Refresh</button>
                  <button onclick="{{ vaccinateAllUsb }}" style="padding:6px 12px;border-radius:6px;font-size:11.5px;font-weight:600;color:#fff;background:#0D9488;border:1px solid #0D9488;cursor:pointer;font-family:inherit">Vaccinate all now</button>
                </div>
                <sc-if test="{{ usbDrivesEmpty }}">
                  <div style="padding:16px;color:var(--text-3);font-size:12px;text-align:center;background:var(--surface);border-radius:8px;border:1px dashed var(--border-strong)">
                    <svg style="width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;opacity:.6;vertical-align:-4px;margin-right:6px"><use href="#i-usb"></use></svg>
                    No removable drives connected right now. Plug in a USB to see protection status.
                  </div>
                </sc-if>
                <sc-for list="{{ usbDrives }}" as="u">
                  <div style="display:flex;align-items:center;gap:10px;padding:12px 14px;background:var(--card);border-radius:8px;border:1px solid var(--border)">
                    <div style="width:32px;height:32px;border-radius:7px;background:{{ u.iconBg }};color:{{ u.iconFg }};display:flex;align-items:center;justify-content:center;flex:none"><svg style="width:15px;height:15px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"><use href="#i-usb"></use></svg></div>
                    <div style="flex:1;line-height:1.3;min-width:0"><div style="font-size:12.5px;font-weight:600;color:var(--text-1)">{{ u.drive }} <span style="font-size:11px;font-weight:400;color:var(--text-3)">· {{ u.label }} · {{ u.sizeGb }} GB</span></div><div style="font-size:11px;color:{{ u.statusColor }};font-weight:500">{{ u.statusText }}</div></div>
                    <button onclick="{{ u.scanNow }}" title="Scan this drive now" style="padding:5px 10px;border-radius:6px;font-size:11px;font-weight:600;color:var(--text-2);background:var(--card);border:1px solid var(--border-strong);cursor:pointer;font-family:inherit">Scan</button>
                    <sc-if test="{{ u.showVaccinate }}">
                      <button onclick="{{ u.vaccinate }}" title="Vaccinate this drive" style="padding:5px 10px;border-radius:6px;font-size:11px;font-weight:600;color:#fff;background:#0D9488;border:1px solid #0D9488;cursor:pointer;font-family:inherit">Vaccinate</button>
                    </sc-if>
                    <sc-if test="{{ u.showUnvaccinate }}">
                      <button onclick="{{ u.unvaccinate }}" title="Remove vaccine (use before reformat)" style="padding:5px 10px;border-radius:6px;font-size:11px;font-weight:600;color:var(--text-2);background:var(--card);border:1px solid var(--border-strong);cursor:pointer;font-family:inherit">Un-vaccinate</button>
                    </sc-if>
                  </div>
                </sc-for>
              </div>
            </div>
          </div>

          <!-- ============ SYSTEM REMEDIATION (v22, per-mode RT/OD split) ============
               Master + 6 sub-modules × 2 modes. RT / OD tab switch uses the same pattern
               as the Advanced section. -->
          <div class="tqset-block" style="--sc:#EA580C">
            <div class="tqset-hd">
              <div class="tqset-ic"><svg viewBox="0 0 24 24"><path d="M12 2l3 4h5v5l4 3-4 3v5h-5l-3 4-3-4H4v-5l-4-3 4-3V6h5z"/><path d="M9 12l2 2 4-4"/></svg></div>
              <div><div class="tqset-t">System Remediation</div></div>
            </div>
            <div class="tqset-grp">
              <div class="tqset-row">
                <div class="tqset-rb"><div class="tqset-nm">System remediation</div></div>
                <div onclick="{{ remedMasterTgl.toggle }}" style="width:38px;height:21px;border-radius:99px;background:{{ remedMasterTgl.bg }};cursor:pointer;position:relative;transition:background .2s;flex:none"><div style="position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;transform:translateX({{ remedMasterTgl.knob }});box-shadow:0 1px 2px rgba(0,0,0,.25)"></div></div>
              </div>
              <div class="tqset-row" style="padding-bottom:6px">
                <div style="display:flex;align-items:center;gap:8px;font-size:11.5px;color:var(--text-2);font-weight:600">Applies to:</div>
                <div style="display:inline-flex;align-items:center;background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:3px;gap:2px;margin-left:4px">
                  <div onclick="{{ remedProfileRt }}" style="cursor:pointer;padding:5px 12px;font-size:11.5px;font-weight:600;color:{{ remedRtBtnColor }};background:{{ remedRtBtnBg }};border-radius:6px">Real-time</div>
                  <div onclick="{{ remedProfileOd }}" style="cursor:pointer;padding:5px 12px;font-size:11.5px;font-weight:600;color:{{ remedOdBtnColor }};background:{{ remedOdBtnBg }};border-radius:6px">On-demand</div>
                </div>
              </div>
              <div class="tqset-row">
                <div class="tqset-rb"><div class="tqset-nm">Kill running malware</div></div>
                <div onclick="{{ remedKillTgl.toggle }}" style="width:38px;height:21px;border-radius:99px;background:{{ remedKillTgl.bg }};cursor:pointer;position:relative;transition:background .2s;flex:none"><div style="position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;transform:translateX({{ remedKillTgl.knob }});box-shadow:0 1px 2px rgba(0,0,0,.25)"></div></div>
              </div>
              <div class="tqset-row">
                <div class="tqset-rb"><div class="tqset-nm">Persistence cleanup</div></div>
                <div onclick="{{ remedPersTgl.toggle }}" style="width:38px;height:21px;border-radius:99px;background:{{ remedPersTgl.bg }};cursor:pointer;position:relative;transition:background .2s;flex:none"><div style="position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;transform:translateX({{ remedPersTgl.knob }});box-shadow:0 1px 2px rgba(0,0,0,.25)"></div></div>
              </div>
              <div class="tqset-row">
                <div class="tqset-rb"><div class="tqset-nm">Hosts file cleanup</div></div>
                <div onclick="{{ remedHostsTgl.toggle }}" style="width:38px;height:21px;border-radius:99px;background:{{ remedHostsTgl.bg }};cursor:pointer;position:relative;transition:background .2s;flex:none"><div style="position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;transform:translateX({{ remedHostsTgl.knob }});box-shadow:0 1px 2px rgba(0,0,0,.25)"></div></div>
              </div>
              <div class="tqset-row">
                <div class="tqset-rb"><div class="tqset-nm">Browser hijack cleanup</div></div>
                <div onclick="{{ remedBrowserTgl.toggle }}" style="width:38px;height:21px;border-radius:99px;background:{{ remedBrowserTgl.bg }};cursor:pointer;position:relative;transition:background .2s;flex:none"><div style="position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;transform:translateX({{ remedBrowserTgl.knob }});box-shadow:0 1px 2px rgba(0,0,0,.25)"></div></div>
              </div>
              <div class="tqset-row">
                <div class="tqset-rb"><div class="tqset-nm">Bad-autostart cleanup</div></div>
                <div onclick="{{ remedBadTgl.toggle }}" style="width:38px;height:21px;border-radius:99px;background:{{ remedBadTgl.bg }};cursor:pointer;position:relative;transition:background .2s;flex:none"><div style="position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;transform:translateX({{ remedBadTgl.knob }});box-shadow:0 1px 2px rgba(0,0,0,.25)"></div></div>
              </div>
              <div class="tqset-row">
                <div class="tqset-rb"><div class="tqset-nm">Reboot-delete locked files</div></div>
                <div onclick="{{ remedRebootTgl.toggle }}" style="width:38px;height:21px;border-radius:99px;background:{{ remedRebootTgl.bg }};cursor:pointer;position:relative;transition:background .2s;flex:none"><div style="position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;transform:translateX({{ remedRebootTgl.knob }});box-shadow:0 1px 2px rgba(0,0,0,.25)"></div></div>
              </div>
              <div class="tqset-row">
                <div class="tqset-rb"><div class="tqset-nm">Exclusion sanitizer</div></div>
                <div onclick="{{ remedExclTgl.toggle }}" style="width:38px;height:21px;border-radius:99px;background:{{ remedExclTgl.bg }};cursor:pointer;position:relative;transition:background .2s;flex:none"><div style="position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;transform:translateX({{ remedExclTgl.knob }});box-shadow:0 1px 2px rgba(0,0,0,.25)"></div></div>
              </div>
              <!-- Run cleanup now — user-triggered SanitizeSystem sweep + last-cleanup summary -->
              <div class="tqset-row" style="flex-direction:column;align-items:stretch;gap:8px">
                <div style="display:flex;align-items:center;gap:10px">
                  <div class="tqset-rb" style="flex:1"><div class="tqset-nm">Run cleanup now</div></div>
                  <button onclick="{{ runCleanupNow }}" style="padding:6px 12px;border-radius:6px;font-size:11.5px;font-weight:600;color:#fff;background:#EA580C;border:1px solid #EA580C;cursor:pointer;font-family:inherit">Run cleanup now</button>
                </div>
                <sc-if test="{{ lastRemediation.show }}">
                  <div style="display:flex;align-items:center;gap:9px;padding:9px 12px;background:var(--surface);border:1px solid var(--border);border-radius:8px">
                    <div style="width:20px;height:20px;border-radius:5px;background:rgba(234,88,12,.14);color:#EA580C;display:flex;align-items:center;justify-content:center;flex:none"><svg style="width:11px;height:11px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"><use href="#i-refresh"></use></svg></div>
                    <div style="flex:1;line-height:1.3;font-size:11.5px"><span style="color:var(--text-1);font-weight:600">Last cleanup · {{ lastRemediation.summary }}</span><br><span style="color:var(--text-3);font-size:10.5px">{{ lastRemediation.detail }}</span></div>
                  </div>
                </sc-if>
              </div>
            </div>
          </div>

          <!-- ============ 04 ADVANCED ============ -->
          <div class="tqset-block" style="--sc:#334155">
            <div class="tqset-hd">
              <div class="tqset-ic"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg></div>
              <div><div class="tqset-t">Advanced</div><div class="tqset-st">Fine-tune protection engines</div></div>
            </div>

            <div style="display:flex;gap:10px;align-items:center;padding:10px 13px;border-radius:8px;background:var(--warning-bg);color:var(--warning);font-size:11.5px;font-weight:500;margin-bottom:10px;border-top:2px solid #334155"><svg style="width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;flex:none"><use href="#i-alert"></use></svg> Turning modules off reduces protection. Change only if you know what you are doing.</div>

            <div style="display:flex;align-items:center;gap:11px;padding:11px 14px;background:var(--card);border:1px solid var(--border);border-radius:8px;margin-bottom:10px;flex-wrap:wrap">
              <span style="font-size:11.5px;color:var(--text-3);font-weight:600">Applies to</span>
              <div style="display:inline-flex;background:var(--surface);border:1px solid var(--border);border-radius:6px;padding:2px">
                <sc-for list="{{ advProfiles }}" as="p" hint-placeholder-count="2">
                  <button onclick="{{ p.select }}" style="border:none;padding:5px 14px;border-radius:5px;cursor:pointer;font-size:11.5px;font-weight:600;background:{{ p.bg }};color:{{ p.color }};font-family:inherit">{{ p.label }}</button>
                </sc-for>
              </div>
              <div style="font-size:11.5px;color:var(--text-3);flex:1;min-width:120px">{{ advProfileHint }}</div>
            </div>

            <div class="tqset-gl">Per-mode scan rules</div>

            <sc-for list="{{ accSections }}" as="sec" hint-placeholder-count="2">
              <div class="tqset-acc">
                <div class="tqset-achd" onclick="{{ sec.toggleOpen }}">
                  <div class="tqset-accic"><svg viewBox="0 0 24 24"><use href="{{ sec.icon }}"></use></svg></div>
                  <div class="tqset-acnm">{{ sec.title }}</div>
                  <svg style="width:14px;height:14px;stroke:var(--text-3);fill:none;stroke-width:2;stroke-linecap:round;transition:transform .18s;transform:{{ sec.rot }}"><use href="#i-chev"></use></svg>
                </div>
                <div style="max-height:{{ sec.mh }};overflow:hidden;transition:max-height .25s ease">
                  <div style="padding:0 14px 8px">
                    <sc-for list="{{ sec.rows }}" as="row" hint-placeholder-count="4">
                      <div style="display:flex;align-items:center;justify-content:space-between;padding:9px 0;border-bottom:1px solid var(--border)">
                        <div style="line-height:1.35"><div style="font-weight:600;color:var(--text-1);font-size:12.5px">{{ row.title }}</div><sc-if value="{{ row.sub }}" hint-placeholder-val="{{ false }}"></sc-if></div>
                        <div onclick="{{ row.toggle }}" style="width:38px;height:21px;border-radius:99px;background:{{ row.bg }};cursor:pointer;position:relative;transition:background .2s;flex:none"><div style="position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;transform:translateX({{ row.knob }});box-shadow:0 1px 2px rgba(0,0,0,.25)"></div></div>
                      </div>
                    </sc-for>
                  </div>
                </div>
              </div>
            </sc-for>

            <div class="tqset-acc">
              <div class="tqset-achd" onclick="{{ perfToggleOpen }}">
                <div class="tqset-accic"><svg viewBox="0 0 24 24"><line x1="6" y1="20" x2="6" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="18" y1="20" x2="18" y2="14"/></svg></div>
                <div class="tqset-acnm">Performance</div>
                <svg style="width:14px;height:14px;stroke:var(--text-3);fill:none;stroke-width:2;stroke-linecap:round;transition:transform .18s;transform:{{ perfRot }}"><use href="#i-chev"></use></svg>
              </div>
              <div style="max-height:{{ perfMh }};overflow:hidden;transition:max-height .25s ease">
                <div style="padding:0 14px 14px">
                  <div style="padding:8px 0 12px">
                    <div style="font-weight:600;color:var(--text-1);font-size:12.5px;margin-bottom:8px">Preset</div>
                    <div style="display:inline-flex;background:var(--surface);border:1px solid var(--border);border-radius:7px;padding:2px">
                      <sc-for list="{{ perfPresets }}" as="p" hint-placeholder-count="4">
                        <button onclick="{{ p.select }}" style="border:none;padding:5px 14px;border-radius:5px;cursor:pointer;font-size:11.5px;font-weight:600;background:{{ p.bg }};color:{{ p.color }};font-family:inherit">{{ p.label }}</button>
                      </sc-for>
                    </div>
                  </div>
                  <div style="opacity:{{ perfCustomOpacity }};pointer-events:{{ perfCustomEvents }}">
                    <sc-for list="{{ perfFields }}" as="f" hint-placeholder-count="4">
                      <div style="display:flex;align-items:center;justify-content:space-between;padding:8px 0;border-bottom:1px solid var(--border)">
                        <div style="font-weight:600;color:var(--text-1);font-size:12.5px">{{ f.label }}</div>
                        <input type="number" value="{{ f.value }}" onchange="{{ f.onChange }}" style="width:96px;background:var(--surface);border:1px solid var(--border);border-radius:6px;padding:6px 10px;color:var(--text-1);font-family:inherit;font-size:12px;outline:none">
                      </div>
                    </sc-for>
                    <div style="display:flex;align-items:center;justify-content:space-between;padding:9px 0">
                      <div style="line-height:1.35"><div style="font-weight:600;color:var(--text-1);font-size:12.5px">Aggressive memory control</div></div>
                      <div onclick="{{ memToggle.toggle }}" style="width:38px;height:21px;border-radius:99px;background:{{ memToggle.bg }};cursor:pointer;position:relative;transition:background .2s;flex:none"><div style="position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;transform:translateX({{ memToggle.knob }});box-shadow:0 1px 2px rgba(0,0,0,.25)"></div></div>
                    </div>
                  </div>
                </div>
              </div>
            </div>

            <div class="tqset-gl">Global protection</div>

            <div class="tqset-acc">
              <div class="tqset-achd" style="cursor:default">
                <div class="tqset-accic"><svg viewBox="0 0 24 24"><rect x="4" y="4" width="16" height="16" rx="2"/><rect x="9" y="9" width="6" height="6"/></svg></div>
                <div class="tqset-acnm">Process guard</div>
              </div>
              <div style="padding:0 14px 8px">
                <div style="display:flex;align-items:center;justify-content:space-between;padding:9px 0;border-bottom:1px solid var(--border)">
                  <div style="line-height:1.35"><div style="font-weight:600;color:var(--text-1);font-size:12.5px">Watch running processes</div></div>
                  <div onclick="{{ procguardTgl.toggle }}" style="width:38px;height:21px;border-radius:99px;background:{{ procguardTgl.bg }};cursor:pointer;position:relative;transition:background .2s;flex:none"><div style="position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;transform:translateX({{ procguardTgl.knob }});box-shadow:0 1px 2px rgba(0,0,0,.25)"></div></div>
                </div>
                <div style="display:flex;align-items:center;justify-content:space-between;padding:9px 0;border-bottom:1px solid var(--border)">
                  <div style="line-height:1.35"><div style="font-weight:600;color:var(--text-1);font-size:12.5px">Scan process memory (fileless)</div></div>
                  <div onclick="{{ procmemTgl.toggle }}" style="width:38px;height:21px;border-radius:99px;background:{{ procmemTgl.bg }};cursor:pointer;position:relative;transition:background .2s;flex:none"><div style="position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;transform:translateX({{ procmemTgl.knob }});box-shadow:0 1px 2px rgba(0,0,0,.25)"></div></div>
                </div>
                <div style="display:flex;align-items:center;justify-content:space-between;padding:9px 0">
                  <div style="line-height:1.35"><div style="font-weight:600;color:var(--text-1);font-size:12.5px">Scan loaded DLLs</div></div>
                  <div onclick="{{ procmodTgl.toggle }}" style="width:38px;height:21px;border-radius:99px;background:{{ procmodTgl.bg }};cursor:pointer;position:relative;transition:background .2s;flex:none"><div style="position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;transform:translateX({{ procmodTgl.knob }});box-shadow:0 1px 2px rgba(0,0,0,.25)"></div></div>
                </div>
              </div>
            </div>

            <!-- AMSI accordion moved out — now lives as top-level "Script Guard" section above. -->


            <div class="tqset-acc">
              <div class="tqset-achd" style="cursor:default">
                <div class="tqset-accic"><svg viewBox="0 0 24 24"><path d="M12 22s7-3.5 7-9V5.5L12 3 5 5.5V13c0 5.5 7 9 7 9z"/><polyline points="9 12 11 14 15 10"/></svg></div>
                <div class="tqset-acnm">Trusted publishers</div>
                <div onclick="{{ trustToggle.toggle }}" style="width:38px;height:21px;border-radius:99px;background:{{ trustToggle.bg }};cursor:pointer;position:relative;transition:background .2s;flex:none"><div style="position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;transform:translateX({{ trustToggle.knob }});box-shadow:0 1px 2px rgba(0,0,0,.25)"></div></div>
              </div>
              <div style="padding:12px 14px;border-top:1px solid var(--border)">
                <div style="font-size:11.5px;color:var(--text-3);margin-bottom:10px">47,000+ built-in publishers &middot; encrypted, managed by TreeIQ. Your added publishers are also trusted.</div>
                <div style="font-weight:600;color:var(--text-1);font-size:12.5px;margin-bottom:8px">Your trusted publishers</div>
                <div style="display:flex;gap:8px;margin-bottom:9px">
                  <input type="text" value="{{ pubValue }}" onchange="{{ onPubInput }}" placeholder="e.g. Kuber Accounting Software" style="flex:1;background:var(--surface);border:1px solid var(--border);border-radius:6px;padding:7px 10px;color:var(--text-1);font-family:inherit;font-size:12px;outline:none">
                  <button onclick="{{ addPublisher }}" style="padding:6px 13px;border-radius:6px;font-weight:600;font-size:11.5px;cursor:pointer;border:1px solid var(--border-strong);background:transparent;color:var(--text-2);font-family:inherit">Add</button>
                </div>
                <div style="display:flex;flex-wrap:wrap;gap:6px">
                  <sc-for list="{{ addedPubs }}" as="p" hint-placeholder-count="2">
                    <span style="display:inline-flex;align-items:center;gap:6px;padding:3px 7px 3px 11px;background:var(--surface);border:1px solid var(--border);border-radius:99px;font-size:11.5px;color:var(--text-2)">{{ p.label }}<button onclick="{{ p.remove }}" style="border:none;background:none;color:var(--text-3);cursor:pointer;display:flex;padding:0"><svg style="width:12px;height:12px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round"><use href="#i-trash"></use></svg></button></span>
                  </sc-for>
                </div>
              </div>
            </div>

            <div class="tqset-acc">
              <div class="tqset-achd" onclick="{{ pwToggleOpen }}">
                <div class="tqset-accic"><svg viewBox="0 0 24 24"><circle cx="7" cy="15" r="4"/><line x1="10" y1="12" x2="18" y2="4"/><line x1="15" y1="7" x2="18" y2="10"/><line x1="20" y1="4" x2="22" y2="6"/></svg></div>
                <div class="tqset-acnm">Password-protected files</div>
                <svg style="width:14px;height:14px;stroke:var(--text-3);fill:none;stroke-width:2;stroke-linecap:round;transition:transform .18s;transform:{{ pwRot }}"><use href="#i-chev"></use></svg>
              </div>
              <div style="max-height:{{ pwMh }};overflow:hidden;transition:max-height .25s ease">
                <div style="padding:0 14px 14px;border-top:1px solid var(--border)">
                  <div style="display:flex;align-items:center;justify-content:space-between;padding:11px 0;border-bottom:1px solid var(--border)">
                    <div style="line-height:1.35"><div style="font-weight:600;color:var(--text-1);font-size:12.5px">Treat unopenable encrypted files as suspicious</div></div>
                    <div onclick="{{ pwSusp.toggle }}" style="width:38px;height:21px;border-radius:99px;background:{{ pwSusp.bg }};cursor:pointer;position:relative;transition:background .2s;flex:none"><div style="position:absolute;top:2.5px;left:2.5px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;transform:translateX({{ pwSusp.knob }});box-shadow:0 1px 2px rgba(0,0,0,.25)"></div></div>
                  </div>
                  <div style="padding:12px 0 0">
                    <div style="font-weight:600;color:var(--text-1);font-size:12.5px;margin-bottom:8px">Passwords to try</div>
                    <div style="display:flex;gap:8px;margin-bottom:8px">
                      <input type="text" value="{{ pwValue }}" onchange="{{ onPwInput }}" placeholder="password" style="flex:1;background:var(--surface);border:1px solid var(--border);border-radius:6px;padding:7px 10px;color:var(--text-1);font-family:inherit;font-size:12px;outline:none">
                      <button onclick="{{ addPw }}" style="padding:6px 13px;border-radius:6px;font-weight:600;font-size:11.5px;cursor:pointer;border:1px solid var(--border-strong);background:transparent;color:var(--text-2);font-family:inherit">Add</button>
                    </div>
                    <div style="display:flex;flex-wrap:wrap;gap:6px">
                      <sc-for list="{{ passwords }}" as="p" hint-placeholder-count="3">
                        <span style="display:inline-flex;align-items:center;gap:6px;padding:3px 7px 3px 10px;background:var(--surface);border:1px solid var(--border);border-radius:99px;font-size:11.5px;color:var(--text-2);font-family:Consolas,ui-monospace,monospace">{{ p.label }}<button onclick="{{ p.remove }}" style="border:none;background:none;color:var(--text-3);cursor:pointer;display:flex;padding:0"><svg style="width:12px;height:12px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round"><use href="#i-trash"></use></svg></button></span>
                      </sc-for>
                    </div>
                  </div>
                </div>
              </div>
            </div>

          </div>

          <!-- ============ 05 ABOUT AND MAINTENANCE ============ -->
          <div class="tqset-block" style="--sc:#0891B2">
            <div class="tqset-hd">
              <div class="tqset-ic"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg></div>
              <div><div class="tqset-t">About and maintenance</div><div class="tqset-st">Version and signature database</div></div>
            </div>
            <div class="tqset-grp">
              <div class="tqset-row">
                <div class="tqset-rb"><div class="tqset-nm">{{ productVerLine }}</div></div>
              </div>
              <div class="tqset-row">
                <div class="tqset-rb"><div class="tqset-nm">{{ productSigLine }}</div></div>
              </div>
            </div>
          </div>

        </section>
        </sc-if>

        <!-- ============ ABOUT ============ -->
        <sc-if value="{{ pAbout }}" hint-placeholder-val="{{ false }}">
        <section data-screen-label="About" style="animation:tqFade .25s ease">
          <div style="background:var(--card);border:1px solid var(--border);border-radius:8px;box-shadow:var(--shadow-sm);text-align:center;padding:44px 20px">
            <div style="width:60px;height:60px;border-radius:14px;background:var(--accent);display:inline-flex;align-items:center;justify-content:center;color:var(--on-accent);margin-bottom:14px"><svg style="width:34px;height:34px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"><use href="#i-shield"></use></svg></div>
            <h2 style="color:var(--text-1);font-size:17px;font-weight:700">TreeIQ Anti-Malware</h2>
            <div style="font-size:12px;color:var(--text-3)">Version {{ productVersion }} · Scan engine {{ engineVersion }}</div>
            <div style="display:flex;gap:6px;justify-content:center;margin-top:14px;flex-wrap:wrap">
              <span style="display:inline-flex;padding:2px 10px;border-radius:99px;font-size:11px;font-weight:600;color:var(--success);background:var(--success-bg)">Real-time protection</span>
              <span style="display:inline-flex;padding:2px 10px;border-radius:99px;font-size:11px;font-weight:600;color:var(--info);background:var(--info-bg)">Offline · no data shared</span>
              <span style="display:inline-flex;padding:2px 10px;border-radius:99px;font-size:11px;font-weight:600;color:var(--info);background:var(--info-bg)">32-bit &amp; 64-bit</span>
            </div>
            <div style="font-size:11.5px;color:var(--text-3);margin-top:18px;line-height:1.7">Database V: {{ dbVersion }} · Released {{ dbDateStr }}<br>© 2026 TreeIQ Labs. All rights reserved.</div>
          </div>
          <!-- License card — status + days remaining + paste-to-activate -->
          <div style="margin-top:14px;background:var(--card);border:1px solid var(--border);border-radius:8px;box-shadow:var(--shadow-sm);padding:18px 20px">
            <div style="display:flex;align-items:center;gap:12px;margin-bottom:10px">
              <div style="font-size:13px;font-weight:600;color:var(--text-1)">License</div>
              <span style="display:inline-flex;padding:2px 10px;border-radius:99px;font-size:11px;font-weight:600;color:{{ licPillFg }};background:{{ licPillBg }}">{{ licPillLabel }}</span>
              <div style="flex:1"></div>
              <div style="font-size:11.5px;color:var(--text-3)">{{ licSummary }}</div>
            </div>
            <div style="font-size:11.5px;color:var(--text-3);margin-bottom:12px;line-height:1.5">Machine fingerprint · <span style="font-family:Consolas,ui-monospace,monospace;color:var(--text-2)">{{ licFingerprint }}</span></div>
            <div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">
              <input type="text" placeholder="Paste license key (XXXX-XXXX-XXXX-XXXX)" value="{{ licKeyInput }}" oninput="{{ setLicKeyInput }}" style="flex:1;min-width:220px;padding:7px 10px;border-radius:6px;border:1px solid var(--border-strong);background:var(--surface);color:var(--text-1);font-size:13px;font-family:Consolas,ui-monospace,monospace;letter-spacing:1px;text-align:center;text-transform:uppercase;outline:none" />
              <button onclick="{{ applyLicense }}" style="padding:7px 14px;border-radius:6px;font-weight:600;font-size:12px;cursor:pointer;border:1px solid var(--accent);background:var(--accent);color:var(--on-accent);font-family:inherit">Activate</button>
            </div>
            <sc-if value="{{ licError }}" hint-placeholder-val="{{ false }}">
              <div style="margin-top:10px;padding:8px 12px;border-radius:6px;background:var(--danger-bg);color:var(--danger);font-size:11.5px">{{ licErrorText }}</div>
            </sc-if>
          </div>
        </section>
        </sc-if>

      </div>
    </div>
  </div>
</div>
</div>

<!-- Expired-license lockdown overlay. Full-screen, opaque, high z-index — nothing behind it is
     clickable. Only the license input + Activate button work. Rendered whenever the license is
     Expired; disappears the moment Bridge.ApplyLicense returns success and syncFromBridge polls
     the fresh status. -->
<sc-if value="{{ licLockdown }}" hint-placeholder-val="{{ false }}">
<div style="position:fixed;inset:0;background:rgba(15,20,30,.72);backdrop-filter:blur(4px);z-index:100;display:flex;align-items:center;justify-content:center;padding:20px">
  <div style="background:var(--card);border:1px solid var(--border);border-radius:12px;box-shadow:0 12px 40px rgba(0,0,0,.35);padding:36px 40px;max-width:520px;width:100%;text-align:center">
    <div style="width:56px;height:56px;border-radius:14px;background:var(--danger);color:#fff;display:inline-flex;align-items:center;justify-content:center;margin-bottom:16px"><svg style="width:30px;height:30px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"><use href="#i-shield"></use></svg></div>
    <div style="font-size:17px;font-weight:700;color:var(--text-1);margin-bottom:6px">License expired</div>
    <div style="font-size:12.5px;color:var(--text-2);line-height:1.55;margin-bottom:20px">Real-time protection, scans and updates are paused.<br>Enter a license key to reactivate.</div>
    <div style="display:flex;flex-direction:column;gap:10px">
      <input type="text" placeholder="XXXX-XXXX-XXXX-XXXX" value="{{ licKeyInput }}" oninput="{{ setLicKeyInput }}" style="padding:10px 14px;border-radius:8px;border:1px solid var(--border-strong);background:var(--surface);color:var(--text-1);font-size:14px;font-family:Consolas,ui-monospace,monospace;letter-spacing:1.5px;text-align:center;text-transform:uppercase;outline:none" />
      <button onclick="{{ applyLicense }}" style="padding:10px 14px;border-radius:8px;font-weight:700;font-size:13px;cursor:pointer;border:none;background:var(--accent);color:var(--on-accent);font-family:inherit">Activate</button>
    </div>
    <sc-if value="{{ licError }}" hint-placeholder-val="{{ false }}">
      <div style="margin-top:14px;padding:8px 12px;border-radius:6px;background:var(--danger-bg);color:var(--danger);font-size:11.5px">{{ licErrorText }}</div>
    </sc-if>
    <div style="margin-top:18px;font-size:10.5px;color:var(--text-3);font-family:Consolas,ui-monospace,monospace">Machine · {{ licFingerprint }}</div>
  </div>
</div>
</sc-if>

<!-- Toasts -->
<div style="position:fixed;bottom:26px;right:26px;display:flex;flex-direction:column;gap:10px;z-index:50">
  <sc-for list="{{ toasts }}" as="t" hint-placeholder-count="0">
    <div style="display:flex;gap:12px;align-items:flex-start;min-width:280px;max-width:340px;padding:12px 14px;border-radius:9px;background:var(--card);border:1px solid var(--border);box-shadow:var(--shadow-lg);animation:tqToast .25s ease">
      <div style="width:30px;height:30px;border-radius:7px;display:flex;align-items:center;justify-content:center;flex:none;background:{{ t.bg }};color:{{ t.fg }}"><svg style="width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round"><use href="{{ t.icon }}"></use></svg></div>
      <div style="line-height:1.35"><div style="font-weight:600;color:var(--text-1);font-size:12.5px">{{ t.title }}</div><div style="font-size:11.5px;color:var(--text-3)">{{ t.desc }}</div></div>
    </div>
  </sc-for>
</div>

</x-dc>
<script type="text/x-dc" data-dc-script="" data-props="{&quot;accent&quot;:{&quot;editor&quot;:&quot;color&quot;,&quot;default&quot;:&quot;#2563EB&quot;,&quot;tsType&quot;:&quot;string&quot;,&quot;options&quot;:[&quot;#2563EB&quot;,&quot;#0F766E&quot;,&quot;#4F46E5&quot;,&quot;#B45309&quot;],&quot;section&quot;:&quot;Branding&quot;},&quot;dashboardVariant&quot;:{&quot;editor&quot;:&quot;enum&quot;,&quot;default&quot;:&quot;overview&quot;,&quot;options&quot;:[&quot;overview&quot;,&quot;compact&quot;,&quot;focus&quot;],&quot;tsType&quot;:&quot;string&quot;,&quot;section&quot;:&quot;Layout&quot;}}">
class Component extends DCLogic {
  constructor(props) {
    super(props);
    this.rootRef = React.createRef();
    this.scanTimer = null;
    this.liveFound = [];
    this.livePct = 0;
    const pad = (n) => (n < 10 ? '0' + n : '' + n);
    const base = new Date();
    const seed = [
      ['event', 'Real-time protection enabled'],
      ['update', 'Signature database updated to 2026-07-04'],
      ['scan', 'Quick scan completed — 18,204 files, 0 threats'],
      ['realtime', 'Blocked Trojan.Win32.Agent.gen in Downloads\\invoice_scan.exe'],
      ['event', 'Watched drive added: E:\\'],
      ['scan', 'Full scan completed — 1,204,882 files, 2 threats'],
      ['realtime', 'Blocked AdWare.InstallCore in Downloads\\setup_crack.exe'],
      ['update', 'Auto-update check — already up to date'],
      ['event', 'Exclusion added: C:\\Dev\\build']
    ];
    const logs = seed.map((s, i) => {
      const d = new Date(base - i * 3600000);
      return { t: `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`, type: s[0], msg: s[1] };
    });
    // ── Tool-window mode (Phase 2-4 Tools consolidation) ──────────────────
    // When TQAM.exe is spawned as `--tool=logs|quarantine|whitelist`, LogsWindow/
    // QuarantineWindow/WhitelistWindow navigate to `index.html?tool=X`. In that mode we hide
    // the sidebar, force the correct page, and stay locked there — this gives the user the
    // exact same polished rendering the main app has always shipped, running in its own
    // isolated process. Detected here at boot so initial render is correct.
    let _toolMode = '';
    try {
      const _u = new URL(window.location.href);
      const _t = (_u.searchParams.get('tool') || '').toLowerCase();
      if (_t === 'logs' || _t === 'quarantine' || _t === 'whitelist' || _t === 'restore') _toolMode = _t;
    } catch (e) {}
    // Stamp <html data-tool="..."> before first paint so the CSS rule that hides the sidebar
    // fires immediately (no flash of the full main-app chrome before we jump to the tool page).
    if (_toolMode) { try { document.documentElement.setAttribute('data-tool', _toolMode); } catch (e) {} }
    // tool=restore maps to the rollback/restore page (its page flag is 'rollback').
    const _bootPage = _toolMode === 'restore' ? 'rollback' : (_toolMode || 'dashboard');
    this.state = {
      page: _bootPage,
      toolMode: _toolMode,
      theme: 'light',
      uiScale: 'normal',
      sidebarRail: false,           // false = full sidebar with labels · true = icon rail (hamburger toggles)
      // Protection page sparkline history — 60 samples each (2-min window @ 2s poll). Populated from
      // real state deltas each tick (see sparkTick()). Starts empty and fills over time.
      sparkHistory: { files: [], threats: [], amsi: [] },
      sparkPrev: { scanned: 0, threats: 0, amsi: 0 },
      gwRange: '12h',
      gwDetail: null,
      gwCatFilter: null,
      dashVariant: this.props.dashboardVariant || 'overview',
      protection: true, rt: true, rtWarn: false,
      threats: 0, scanned: 0,
      drives: [
        { n: 'C:\\', t: 'System', g: 366, on: false },
        { n: 'D:\\', t: 'Data', g: 391, on: false },
        { n: 'E:\\', t: 'Data', g: 195, on: true },
        { n: 'G:\\', t: 'Data', g: 200, on: false }
      ],
      exclusions: ['C:\\Program Files\\TreeIQ', 'C:\\Dev\\build'], exclValue: '',
      whitelist: [], wlModal: null, wlProcs: [], wlSel: [],
      passwords: ['infected', 'test', 'virus'], pwValue: '', pwSusp: true,
      // Quarantine filters: hide the legacy vault-duplicate records by default so a fresh view isn't a
      // wall of "[Vault duplicate]" rows. User can uncheck "Hide vault duplicates" to see them.
      quarSearch: '', quarOnlyReview: false, quarHideDups: true,
      // Restore modal — null when closed, else { ids:[..], titleTag, subtitleTag, origPath, folder, wlFile, wlProc, reason }
      restoreModal: null,
      // Upgrade confirmation modal — null when closed, else { version, notes }. Styled in-app dialog
      // that replaces the native confirm() "app.treeiq says" box (unprofessional).
      upgradeConfirm: null,
      // System Guard (EDR cluster) module state — hydrated from Bridge.GetSystemGuard on load.
      sysGuard: { licensed: true, enabled: true, deepFile: true, forceUnlock: true, behaviorRules: true, persistence: true, network: true, rootkit: true, driverGuard: true, simulation: false },
      quarantine: [
        { id: 'q1', file: 'report.pdf', cat: 'Virus', det: 'Virus.Win32.Sality', path: 'C:\\Users\\ascoo\\Downloads\\report.pdf', date: '2026-07-04 01:08', sel: false },
        { id: 'q2', file: 'tmp8f2.tmp', cat: 'Backdoor', det: 'Backdoor.Win32.Bifrose', path: 'C:\\Users\\ascoo\\AppData\\Local\\Temp\\tmp8f2.tmp', date: '2026-07-04 01:08', sel: false },
        { id: 'q3', file: 'invoice_scan.exe', cat: 'Trojan', det: 'Trojan.Win32.Agent.gen', path: 'C:\\Users\\ascoo\\Downloads\\invoice_scan.exe', date: '2026-07-03 21:14', sel: false },
        { id: 'q4', file: 'setup_crack.exe', cat: 'Adware / PUA', det: 'not-a-virus:AdWare.InstallCore', path: 'C:\\Users\\ascoo\\Downloads\\setup_crack.exe', date: '2026-07-02 10:02', sel: false },
        { id: 'q5', file: 'macro_doc.xlsm', cat: 'Suspicious', det: 'PseudoSig.AutoOpenMacro', path: 'E:\\shared\\macro_doc.xlsm', date: '2026-07-01 16:41', sel: false }
      ],
      logs, logTab: 'all', logQuery: '', detections: [],
      lic: null, licKeyInput: '', licError: '',
      scan: { visible: false, running: false, mode: '', file: '', pct: 0, count: 0, threats: 0, results: null },
      scanBulk: 'quarantine',
      toasts: [],
      settingsMode: 'simple',
      scanPolicy: { mode: 'recommended', extensions: '', rtArchives: false, rtMaxMB: 150, rtNetwork: false, odArchives: true, odMaxMB: 500, scanCache: true, weeklySweep: true, odScanMode: 'smart', odIncludeExts: [], smartModeExts: [], usbProtection: true, usbVaccine: true, usbUnhide: true, usbRootLauncher: true, usbLnkAnalyzer: true, usbAutoScanInsertion: true, usbRemediation: true, remediation: true, remedKillRt: true, remedKillOd: true, remedPersRt: true, remedPersOd: true, remedHostsRt: true, remedHostsOd: true, remedBrowserRt: true, remedBrowserOd: true, remedBadRt: true, remedBadOd: true, remedRebootRt: true, remedRebootOd: true, remedExclRt: true, remedExclOd: true },
      usbDrives: [],
      remedProfile: 'rt',   // v22 System Remediation section — which mode's sub-toggles are shown.
      lastRemediationRaw: null,   // last remediation event; populated by settings loader.
      simpleToggles: { rt: true, usb: true, notif: true, kill: true, procguard: true, procmem: true, procmod: true },
      // Ransom Guard v1.1.0 UI state. Actual values come from Bridge.GetSettings + GetRollbackStatus
      // + GetCanaryPaths on boot. Defaults keep the section visible until the async load resolves.
      rgSettings: {
        enabled: true, mode: 'behavior', sensitivity: 'med',
        canaryCount: 8, snapCount: 0, snapMB: 0, lastActivity: 'Never'
      },
      // Rollback / Restore window (#7) state.
      rbFiles: [], rbSel: {}, rbFilter: 'enc', rbQuery: '', rbBusy: false, rbMsg: '', rbMsgOk: true, rbLoaded: false,
      // Which mode the Advanced settings edit: 'rt' (real-time) or 'od' (on-demand). Kept separate so
      // tuning one never affects the other. Each row carries rt+od bridge keys/setters and on-states.
      advProfile: 'rt',
      advActions: { rt: 'quarantine', od: 'quarantine' },
      // Detection engines — each maps to a real SDK simulator flag. [title, sub, rtKey,rtSet, odKey,odSet]
      engines: [
        ['Boot / MBR simulation', 'Detects boot-sector viruses', 'engBoot', 'SetEngineBoot', 'odEngBoot', 'SetOdEngineBoot'],
        ['32-bit executable emulation', 'Catches packed x86 malware', 'engExe32', 'SetEngineExe32', 'odEngExe32', 'SetOdEngineExe32'],
        ['64-bit executable emulation', 'Catches packed x64 malware', 'engExe64', 'SetEngineExe64', 'odEngExe64', 'SetOdEngineExe64'],
        ['JavaScript simulation', 'Malicious scripts', 'engJs', 'SetEngineJs', 'odEngJs', 'SetOdEngineJs'],
        ['P-Code simulation', 'VB / Office macro p-code', 'engPcode', 'SetEnginePcode', 'odEngPcode', 'SetOdEnginePcode'],
        ['.NET analysis', 'Managed-code malware', 'engDotNet', 'SetEngineDotNet', 'odEngDotNet', 'SetOdEngineDotNet'],
        ['.NET unpacking', 'Packed .NET malware', 'engDotNetUnpack', 'SetEngineDotNetUnpack', 'odEngDotNetUnpack', 'SetOdEngineDotNetUnpack'],
        ['Adware / PUA scanning', 'Unwanted programs (toolbars, bundlers)', 'advAdware', 'SetAdwareScan', 'odAdvAdware', 'SetOdAdwareScan']
      ].map((e, i) => ({ id: 'e' + i, title: e[0], sub: e[1], rtKey: e[2], rtSet: e[3], odKey: e[4], odSet: e[5], rtOn: true, odOn: true })),
      // Heuristic pseudo-signatures — map to the SDK "pseudosigs" object.
      heur: [
        ['Bare executable is suspicious', 'Any EXE with no other signature (can cause false alarms)', 'psBareExe', 'SetPsBareExe', 'odPsBareExe', 'SetOdPsBareExe'],
        ['Encrypted archive is bad', 'Locked archive that cannot be scanned', 'psEncArchive', 'SetPsEncArchive', 'odPsEncArchive', 'SetOdPsEncArchive'],
        ['Encrypted archive with bad name', 'Locked archive with a suspicious name', 'psEncArchiveName', 'SetPsEncArchiveName', 'odPsEncArchiveName', 'SetOdPsEncArchiveName'],
        ['Damaged executable', 'Corrupted MZ/PE file', 'psDamagedExe', 'SetPsDamagedExe', 'odPsDamagedExe', 'SetOdPsDamagedExe'],
        ['Auto-open macros', 'Office files that auto-run macros', 'psAutoMacros', 'SetPsAutoMacros', 'odPsAutoMacros', 'SetOdPsAutoMacros'],
        ['Encrypted document is bad', 'Locked Office/PDF', 'psEncDoc', 'SetPsEncDoc', 'odPsEncDoc', 'SetOdPsEncDoc'],
        ['Treat as mail item', 'Apply mail-specific checks', 'psMailItem', 'SetPsMailItem', 'odPsMailItem', 'SetOdPsMailItem']
      ].map((e, i) => ({ id: 'h' + i, title: e[0], sub: e[1], rtKey: e[2], rtSet: e[3], odKey: e[4], odSet: e[5], rtOn: false, odOn: false })),
      acc: { engines: true, heur: false },
      // trusted publishers (Phase 1) — the 47k list is backend/encrypted; only the toggle + user adds show
      trustPublishers: true, addedPublishers: [], pubValue: '', extValue: '',
      // AMSI script/macro protection (Phase 2)
      amsi: { available: true, registered: false, enabled: true, mode: 'enforce', hostPs: true, hostWsh: true, hostOffice: true, hostOther: false, maxMB: 4, heuristics: false },
      perfOpen: false, pwOpen: false,
      // per-mode performance: item=per-file timeout, global=whole-scan timeout, extract=max extracted MB, mem=low-memory
      perfRt: { preset: 'custom', item: 60, global: 0, extract: 2048, mem: false },
      perfOd: { preset: 'custom', item: 120, global: 0, extract: 4096, mem: false },
      updAuto: true, updChecking: false, updParts: [{},{},{},{}],
      // Placeholders left blank on purpose: refreshUpdateStatus() overwrites these from
      // Bridge.GetVdbInfo() on load. If the bridge call fails silently the UI shows "v—"
      // instead of a stale date, so bugs are visible instead of hidden (2026-07-09 fix 1).
      engineVersion: 'v6.5.5', dbVersion: '', signatures: 0, productVersion: '1.0.0'
    };
  }

  componentDidMount() {
    try {
      const t = localStorage.getItem('treeiq-theme');
      if (t === 'light' || t === 'carbon') this.setState({ theme: t });
      const sc = localStorage.getItem('treeiq-scale');
      if (sc === 'compact' || sc === 'normal' || sc === 'large') this.setState({ uiScale: sc });
      // Restore saved sidebar preference, or auto-collapse to icon rail on first load if the
      // window is narrow (< 1120 px). Manual hamburger toggle wins after that.
      const savedRail = localStorage.getItem('treeiq-rail');
      if (savedRail === '1' || savedRail === '0') this.setState({ sidebarRail: savedRail === '1' });
      else if (window.innerWidth && window.innerWidth < 1120) this.setState({ sidebarRail: true });
    } catch (e) {}
    this.applyAccent();
    this.applyScale();
    // Sparkline poll for the Protection page — 2 s cadence. The tick itself early-outs when the
    // user isn't on the Protection page, so this is essentially free while idle.
    this.sparkTimer = setInterval(() => { try { this.sparkTick(); } catch (e) {} }, 2000);
    if (this.bridge()) {
      this.onBridgeMsg = (e) => this.handleBridgeMsg(e);
      window.chrome.webview.addEventListener('message', this.onBridgeMsg);
      this.syncFromBridge();
      this.checkPendingScan();
      try { this.loadSystemGuard(); } catch (e) {}
      // Restore tool-window boot — load the rollback file list immediately so it's populated
      // when the standalone Restore window opens.
      if (this.state.toolMode === 'restore') { try { this.loadRollback(); } catch (e) {} }
      // keep the UI live: re-pull status / quarantine / logs, and pick up right-click scans
      this.bridgePoll = setInterval(() => { this.syncFromBridge(); this.checkPendingScan(); }, 5000);
      // Auto-updater status: initial fetch + refresh every 30 s so "checked N min ago" stays fresh
      // and the header reacts to background updates without the user reloading.
      try { this.refreshUpdateStatus(); } catch (e) {}
      this.updPoll = setInterval(() => { try { this.refreshUpdateStatus(); } catch (e) {} }, 30000);
    }
  }
  componentDidUpdate() { this.applyAccent(); this.applyScale(); }

  // UI size: zoom the whole shell, compensating width/height so it still fills the window exactly.
  applyScale() {
    const el = this.rootRef.current; if (!el) return;
    const z = ({ compact: 0.9, normal: 1, large: 1.12 })[this.state.uiScale] || 1;
    el.style.zoom = z;
    el.style.width = (100 / z) + 'vw';
    el.style.height = (100 / z) + 'vh';
  }
  setScale(k) { try { localStorage.setItem('treeiq-scale', k); } catch (e) {} this.setState({ uiScale: k }); }
  componentWillUnmount() {
    clearInterval(this.scanTimer);
    clearInterval(this.bridgePoll);
    if (this.onBridgeMsg) { try { window.chrome.webview.removeEventListener('message', this.onBridgeMsg); } catch (e) {} }
  }

  applyAccent() {
    const el = this.rootRef.current;
    if (!el) return;
    if (this.state.theme === 'light' && this.props.accent) {
      el.style.setProperty('--accent', this.props.accent);
      el.style.setProperty('--accent-hover', `color-mix(in oklab, ${this.props.accent}, black 14%)`);
      el.style.setProperty('--accent-dim', `color-mix(in srgb, ${this.props.accent} 9%, transparent)`);
      el.style.setProperty('--info', this.props.accent);
      el.style.setProperty('--info-bg', `color-mix(in srgb, ${this.props.accent} 9%, transparent)`);
    } else {
      ['--accent', '--accent-hover', '--accent-dim', '--info', '--info-bg'].forEach((v) => el.style.removeProperty(v));
    }
  }

  // ---------- native bridge (WebView2 host object) ----------
  bridge() {
    try { return (window.chrome && window.chrome.webview && window.chrome.webview.hostObjects && window.chrome.webview.hostObjects.treeiq) || null; } catch (e) { return null; }
  }

  // ---------- whitelist (File / Folder / Process / Drive) ----------
  async wlPick(type) {
    const b = this.bridge(); if (!b) return;
    try { const path = await (type === 'folder' ? b.PickFolder() : b.PickFile()); if (path) this.wlAdd(type, path); } catch (e) {}
  }
  wlAdd(type, path) {
    const b = this.bridge();
    if (b) { Promise.resolve(b.AddWhitelist(type, path)).catch(() => {}); }
    this.setState((st) => st.whitelist.some((w) => w.path === path)
      ? { wlModal: null }
      : ({ whitelist: [...st.whitelist, { type, path, added: 'now' }], wlModal: null }));
    this.addLog('event', 'Whitelist added (' + type + '): ' + path);
  }
  async wlOpenProcess() {
    const b = this.bridge();
    if (!b) { this.setState({ wlModal: 'process', wlProcs: [], wlSel: [] }); return; }
    try { const procs = JSON.parse(await b.GetRunningProcesses()); this.setState({ wlModal: 'process', wlProcs: procs, wlSel: [] }); }
    catch (e) { this.setState({ wlModal: 'process', wlProcs: [], wlSel: [] }); }
  }
  wlToggle(path) {
    this.setState((st) => ({ wlSel: st.wlSel.indexOf(path) >= 0 ? st.wlSel.filter((p) => p !== path) : [...st.wlSel, path] }));
  }
  wlModalRows() {
    const s = this.state, sel = s.wlSel || [];
    const row = (label, sub, path) => ({ label, sub, path, checked: sel.indexOf(path) >= 0, toggle: () => this.wlToggle(path) });
    if (s.wlModal === 'process') return (s.wlProcs || []).map((p) => row(p.name + '  ·  PID ' + p.pid, p.path, p.path));
    if (s.wlModal === 'drive') return (s.drives || []).map((d) => row(d.n, (d.t || 'Drive') + (d.g ? ('  ·  ' + d.g + ' GB') : ''), d.n));
    return [];
  }
  wlAddSelected() {
    const type = this.state.wlModal, sel = (this.state.wlSel || []).slice();
    if (!sel.length) { this.setState({ wlModal: null }); return; }
    if (type === 'drive' && !confirm('Whitelist ' + sel.length + ' drive(s)?\n\nEverything on them will be skipped by real-time and all scans.')) return;
    const b = this.bridge();
    sel.forEach((path) => { if (b) Promise.resolve(b.AddWhitelist(type, path)).catch(() => {}); });
    this.setState((st) => {
      const have = new Set(st.whitelist.map((w) => w.path));
      const add = sel.filter((p) => !have.has(p)).map((p) => ({ type, path: p, added: 'now' }));
      return { whitelist: [...st.whitelist, ...add], wlModal: null, wlSel: [] };
    });
    this.addLog('event', 'Whitelist added ' + sel.length + ' ' + type + '(s)');
  }
  dbg(msg) { try { const b = this.bridge(); if (b && b.WriteDebug) b.WriteDebug(msg); } catch (e) {} }
  async loadRollback() {
    const b = this.bridge();
    this.dbg('loadRollback: click received, bridge=' + (!!b) + ' hasFn=' + (!!(b && b.GetRollbackFiles)));
    if (!b || !b.GetRollbackFiles) { this.setState({ rbLoaded: true }); return; }
    const t0 = Date.now();
    try {
      const raw = await b.GetRollbackFiles();
      const dt = Date.now() - t0;
      const j = (typeof raw === 'string') ? JSON.parse(raw) : raw;
      const files = (j && Array.isArray(j.files)) ? j.files : [];
      this.dbg('loadRollback: returned in ' + dt + 'ms, files=' + files.length + (j && j.error ? (' error=' + j.error) : '') + ' rawLen=' + (typeof raw === 'string' ? raw.length : -1));
      const sel = {};
      files.forEach((f) => { if (f.st === 'modified' || f.st === 'deleted') sel[f.p] = true; });
      this.setState({ rbFiles: files, rbSel: sel, rbLoaded: true, rbMsg: '' });
    } catch (e) { this.dbg('loadRollback: EXCEPTION ' + (e && e.message)); this.setState({ rbLoaded: true, rbMsg: 'Could not read backups.', rbMsgOk: false }); }
  }
  async doRollbackRestore(paths) {
    const b = this.bridge();
    this.dbg('doRollbackRestore: click, paths=' + (paths ? paths.length : 0) + ' hasFn=' + (!!(b && b.RestoreRollbackSelected)));
    if (!b || !b.RestoreRollbackSelected || !paths || !paths.length) return;
    this.setState({ rbBusy: true, rbMsg: '' });
    const t0 = Date.now();
    try {
      const raw = await b.RestoreRollbackSelected(JSON.stringify(paths));
      this.dbg('doRollbackRestore: returned in ' + (Date.now() - t0) + 'ms');
      const j = (typeof raw === 'string') ? JSON.parse(raw) : raw;
      const n = (j && j.restored) || 0; const failed = (j && j.failed) || 0;
      const ok = failed === 0;
      this.setState({ rbBusy: false, rbMsg: n + ' file' + (n === 1 ? '' : 's') + ' restored' + (failed ? (' · ' + failed + ' could not be restored (still locked)') : '.'), rbMsgOk: ok });
      this.loadRollback();
    } catch (e) { this.setState({ rbBusy: false, rbMsg: 'Restore failed.', rbMsgOk: false }); }
  }
  async syncFromBridge() {
    const b = this.bridge(); if (!b) return;
    try {
      const [state, settings, drives, quar, logs, wl, ver, sysinfo, licInfo] = await Promise.all([
        b.GetState().then((r) => JSON.parse(r)),
        b.GetSettings().then((r) => JSON.parse(r)),
        b.GetDrives().then((r) => JSON.parse(r)),
        b.GetQuarantine().then((r) => JSON.parse(r)),
        b.GetLogs('all').then((r) => JSON.parse(r)),
        (b.GetWhitelist ? b.GetWhitelist().then((r) => JSON.parse(r)) : Promise.resolve(null)).catch(() => null),
        (b.GetProductVersion ? Promise.resolve(b.GetProductVersion()) : Promise.resolve(null)).catch(() => null),
        (b.GetSystemInfo ? Promise.resolve(b.GetSystemInfo()).then((r) => JSON.parse(r)) : Promise.resolve(null)).catch(() => null),
        (b.GetLicenseStatus ? Promise.resolve(b.GetLicenseStatus()).then((r) => JSON.parse(r)) : Promise.resolve(null)).catch(() => null)
      ]);
      const norm = (p) => String(p || '').trim().toUpperCase().replace(/\\+$/, '');
      const watch = (settings.watchFolders || []).map(norm);
      const loadPolicy = !this._policyLoaded;
      if (loadPolicy) this._policyLoaded = true;
      const loadAdv = !this._advLoaded;
      if (loadAdv) this._advLoaded = true;
      // Pre-fetch scan history BEFORE setState — the setState callback can't be async, so we
      // await the Promise here and hand the resolved array in via closure.
      let scanHistoryFetched = [];
      try {
        if (b && b.GetScanHistory) {
          const sj = await b.GetScanHistory();
          scanHistoryFetched = (typeof sj === 'string') ? JSON.parse(sj) : (sj || []);
        }
      } catch (e) { scanHistoryFetched = []; }
      // USB events — flat audit trail written by UsbInsertionWatcher.EmitUsbEvent. Format:
      // { ts, drive, label, action, scanned, threats, findings, vaccinated }
      let usbEventsFetched = [];
      try {
        if (b && b.GetUsbEvents) {
          const uj = await b.GetUsbEvents();
          usbEventsFetched = (typeof uj === 'string') ? JSON.parse(uj) : (uj || []);
        }
      } catch (e) { usbEventsFetched = []; }
      // Ransom Guard — hydrate the dashboard + settings section with REAL state (enabled/mode/
      // sensitivity + live canary/snapshot/attack counters). Without this the section always
      // showed static defaults (canary 8, snaps 0, mode behavior) regardless of reality.
      let rgFetched = null;
      try {
        if (b && b.GetRansomGuardStatus) {
          const rj = await b.GetRansomGuardStatus();
          const rg = (typeof rj === 'string') ? JSON.parse(rj) : rj;
          if (rg && !rg.error) rgFetched = rg;
        }
      } catch (e) { rgFetched = null; }
      this.setState((st) => {
        const p = {
          rt: !!state.realtimeEnabled,
          protection: !!state.realtimeEnabled,
          simpleToggles: { ...st.simpleToggles, rt: !!state.realtimeEnabled, notif: settings.showNotifications != null ? !!settings.showNotifications : st.simpleToggles.notif, kill: settings.killRunningMalware != null ? !!settings.killRunningMalware : st.simpleToggles.kill, procguard: settings.processGuard != null ? !!settings.processGuard : st.simpleToggles.procguard, procmem: settings.scanProcessMemory != null ? !!settings.scanProcessMemory : st.simpleToggles.procmem, procmod: settings.scanModules != null ? !!settings.scanModules : st.simpleToggles.procmod },
          // Triple-Shield disabled-at timestamps (empty string = currently ON). Used by the
          // dashboard guard cards to render "Disabled 12m ago". Refreshed every 5 s so the text
          // stays current without a page reload.
          fileGuardDisabledAt:    settings.fileGuardDisabledAt    != null ? settings.fileGuardDisabledAt    : (st.fileGuardDisabledAt    || ''),
          processGuardDisabledAt: settings.processGuardDisabledAt != null ? settings.processGuardDisabledAt : (st.processGuardDisabledAt || ''),
          scriptGuardDisabledAt:  settings.scriptGuardDisabledAt  != null ? settings.scriptGuardDisabledAt  : (st.scriptGuardDisabledAt  || ''),
          engineVersion: state.engineVersion || st.engineVersion,
          // Ransom Guard live state (falls back to prior state if the call failed this cycle).
          rgSettings: rgFetched ? { ...st.rgSettings,
            enabled: !!rgFetched.enabled, mode: rgFetched.mode || 'behavior', sensitivity: rgFetched.sensitivity || 'med',
            canaryCount: rgFetched.canaryCount || 0, snapCount: rgFetched.snapCount || 0, snapMB: rgFetched.snapMB || 0,
            attackCount: rgFetched.attackCount || 0, lastActivity: rgFetched.lastActivity || 'Never' } : st.rgSettings,
          // dbVersion + signatures are OWNED by refreshUpdateStatus() (reads Bridge.GetVdbInfo()
          // which reads vdb-info.json written by the scan engine). 2026-07-09 fix: previous
          // code was `st.dbVersion || state.dbVersion` which let ANY truthy value from GetStatus
          // (including the "2026-07-04" hardcoded stub) win over the real value from refreshUpdateStatus.
          // Now the state value ALWAYS wins — only fall back to st.dbVersion when state has none.
          dbVersion: state.dbVersion || st.dbVersion,
          // Bootstrap the two auto-updater toggles from settings.json ONCE per boot so they reflect
          // the persisted value. After that the user-driven toggles/dropdown own the state.
          updAuto:   (this._updSettingsLoaded ? st.updAuto  : (settings.vdbAutoUpdate  !== false)),
          updHours:  (this._updSettingsLoaded ? st.updHours : (Number(settings.vdbUpdateHours) || 6)),
          signatures: (st.signatures != null && st.signatures !== 8412556) ? st.signatures : (state.signatures != null ? state.signatures : st.signatures),
          productVersion: ver || st.productVersion,
          // License status refreshed every settings poll (5 s). See LicenseManager.cs.
          lic: licInfo || st.lic,
          blockedToday: state.blockedToday != null ? state.blockedToday : (st.blockedToday || 0),
          sysinfo: sysinfo || st.sysinfo || {},
          drives: (drives || []).map((d) => ({ n: d.root, t: d.label || 'Drive', g: d.sizeGb, on: watch.indexOf(norm(d.root)) !== -1 })),
          exclusions: settings.exclusions || [],
          whitelist: wl != null ? wl : st.whitelist,
          // Preserve sel across the 5-second auto-sync (was wiping user checkbox picks).
          quarantine: (quar || []).map((q) => ({ id: String(q.id), file: q.file, cat: q.category, det: q.detection, path: q.path, date: q.date, review: !!q.review, source: q.source || 'realtime', dup: !!q.dup, sel: !!((st.quarantine || []).find((x) => String(x.id) === String(q.id)) || {}).sel })),
          logs: (logs || []).map((l) => ({ t: l.time, type: l.type, msg: l.msg }))
        };
        // Use the scan history we pre-fetched above (setState callback can't be async).
        p.scanHistory = scanHistoryFetched;
        p.usbEvents = usbEventsFetched;
        if (loadPolicy) p.scanPolicy = {
          mode: settings.rtScanMode || 'recommended',
          extensions: (settings.rtExtensions || []).join(', '),
          rtArchives: !!settings.rtArchives,
          rtMaxMB: settings.rtMaxMB != null ? settings.rtMaxMB : 150,
          rtNetwork: !!settings.rtNetwork,
          odArchives: settings.odArchives != null ? !!settings.odArchives : true,
          odMaxMB: settings.odMaxMB != null ? settings.odMaxMB : 500,
          scanCache: settings.scanCache != null ? !!settings.scanCache : true,
          odScanMode: settings.odScanMode || 'smart',
          odIncludeExts: Array.isArray(settings.odIncludeExts) ? settings.odIncludeExts.slice() : [],
          smartModeExts: Array.isArray(settings.smartModeExts) ? settings.smartModeExts.slice() : [],
          weeklySweep: settings.weeklySweep != null ? !!settings.weeklySweep : true,
          usbProtection:      settings.usbProtection      != null ? !!settings.usbProtection      : true,
          usbVaccine:         settings.usbVaccine         != null ? !!settings.usbVaccine         : true,
          usbUnhide:          settings.usbUnhide          != null ? !!settings.usbUnhide          : true,
          usbRootLauncher:    settings.usbRootLauncher    != null ? !!settings.usbRootLauncher    : true,
          usbLnkAnalyzer:     settings.usbLnkAnalyzer     != null ? !!settings.usbLnkAnalyzer     : true,
          usbAutoScanInsertion: settings.usbAutoScanInsertion != null ? !!settings.usbAutoScanInsertion : true,
          usbRemediation:       settings.usbRemediation       != null ? !!settings.usbRemediation       : true,
          // v22 System Remediation — master + 12 per-mode sub-toggles.
          remediation:      settings.remediation      != null ? !!settings.remediation      : true,
          remedKillRt:      settings.remedKillRt      != null ? !!settings.remedKillRt      : true,
          remedKillOd:      settings.remedKillOd      != null ? !!settings.remedKillOd      : true,
          remedPersRt:      settings.remedPersRt      != null ? !!settings.remedPersRt      : true,
          remedPersOd:      settings.remedPersOd      != null ? !!settings.remedPersOd      : true,
          remedHostsRt:     settings.remedHostsRt     != null ? !!settings.remedHostsRt     : true,
          remedHostsOd:     settings.remedHostsOd     != null ? !!settings.remedHostsOd     : true,
          remedBrowserRt:   settings.remedBrowserRt   != null ? !!settings.remedBrowserRt   : true,
          remedBrowserOd:   settings.remedBrowserOd   != null ? !!settings.remedBrowserOd   : true,
          remedBadRt:       settings.remedBadRt       != null ? !!settings.remedBadRt       : true,
          remedBadOd:       settings.remedBadOd       != null ? !!settings.remedBadOd       : true,
          remedRebootRt:    settings.remedRebootRt    != null ? !!settings.remedRebootRt    : true,
          remedRebootOd:    settings.remedRebootOd    != null ? !!settings.remedRebootOd    : true,
          remedExclRt:      settings.remedExclRt      != null ? !!settings.remedExclRt      : true,
          remedExclOd:      settings.remedExclOd      != null ? !!settings.remedExclOd      : true
        };
        // One-shot flag — after the first successful sync we let the user-driven toggles own the
        // updater controls. Prevents the every-5-s poll from clobbering an in-flight setting change.
        this._updSettingsLoaded = true;
        // Fetch last-cleanup event for the chip. Synchronous single-line read.
        try {
          if (b && b.GetLastRemediation) {
            const lj = b.GetLastRemediation();
            p.lastRemediationRaw = (lj && lj !== 'null') ? JSON.parse(lj) : null;
          }
        } catch (e) { p.lastRemediationRaw = null; }
        // Also fetch the list of currently-connected removable drives + vaccination status.
        // Synchronous — Bridge.GetUsbDrivesStatus is a plain string return, no promise round-trip.
        try {
          if (b && b.GetUsbDrivesStatus) {
            const uj = b.GetUsbDrivesStatus();
            p.usbDrives = (typeof uj === 'string') ? JSON.parse(uj) : (uj || []);
          }
        } catch (e) { p.usbDrives = []; }
        if (loadAdv) {
          // fill each engine/heur row's rt + od on-states from their bridge keys
          const fill = (rows) => rows.map((r) => ({
            ...r,
            rtOn: settings[r.rtKey] != null ? !!settings[r.rtKey] : r.rtOn,
            odOn: settings[r.odKey] != null ? !!settings[r.odKey] : r.odOn
          }));
          p.engines = fill(st.engines);
          p.heur = fill(st.heur);
          p.perfRt = {
            preset: 'custom',
            item: settings.advStreamTimeout != null ? settings.advStreamTimeout : 60,
            global: settings.advGlobalTimeout != null ? settings.advGlobalTimeout : 0,
            extract: settings.advExtractMB != null ? settings.advExtractMB : 2048,
            mem: !!settings.advLowMemory
          };
          p.perfOd = {
            preset: 'custom',
            item: settings.odAdvStreamTimeout != null ? settings.odAdvStreamTimeout : 120,
            global: settings.odAdvGlobalTimeout != null ? settings.odAdvGlobalTimeout : 0,
            extract: settings.odAdvExtractMB != null ? settings.odAdvExtractMB : 4096,
            mem: !!settings.odAdvLowMemory
          };
          p.advActions = {
            rt: settings.rtAction || 'quarantine',
            od: settings.odAction || 'quarantine'
          };
          p.trustPublishers = settings.trustPublishers != null ? !!settings.trustPublishers : true;
          p.addedPublishers = settings.addedPublishers || [];
          p.amsi = {
            available: settings.amsiAvailable != null ? !!settings.amsiAvailable : true,
            registered: !!settings.amsiRegistered,
            enabled: settings.amsiEnabled != null ? !!settings.amsiEnabled : true,
            mode: settings.amsiMode || 'enforce',
            hostPs: settings.amsiHostPs != null ? !!settings.amsiHostPs : true,
            hostWsh: settings.amsiHostWsh != null ? !!settings.amsiHostWsh : true,
            hostOffice: settings.amsiHostOffice != null ? !!settings.amsiHostOffice : true,
            hostOther: !!settings.amsiHostOther,
            maxMB: settings.amsiMaxMB != null ? settings.amsiMaxMB : 4,
            heuristics: !!settings.amsiHeuristics
          };
        }
        return p;
      });
    } catch (e) { console.warn('TreeIQ bridge sync failed:', e); }
  }
  async refreshQuarantine() {
    const b = this.bridge(); if (!b) return;
    try {
      const q = JSON.parse(await b.GetQuarantine());
      // Preserve the user's checkbox selections across auto-refresh (runs every 5 s). Without this,
      // clicking "select all" would visibly deselect within seconds — the refresh rewrote every row's sel=false.
      const prevSel = new Map((this.state.quarantine || []).map((r) => [String(r.id), !!r.sel]));
      this.setState({ quarantine: (q || []).map((x) => ({ id: String(x.id), file: x.file, cat: x.category, det: x.detection, path: x.path, date: x.date, review: !!x.review, source: x.source || 'realtime', dup: !!x.dup, sel: !!prevSel.get(String(x.id)) })) });
    } catch (e) { console.warn('GetQuarantine failed:', e); }
  }
  async refreshLogs(type) {
    const b = this.bridge(); if (!b) return;
    try {
      const l = JSON.parse(await b.GetLogs(type || 'all'));
      this.setState({ logs: (l || []).map((x) => ({ t: x.time, type: x.type, msg: x.msg })) });
    } catch (e) { console.warn('GetLogs failed:', e); }
    // E1 (2026-07-16) — structured Protection Log: one row per detection from every guard.
    try {
      if (b.GetDetectionLog) {
        const d = JSON.parse(await b.GetDetectionLog());
        this.setState({ detections: Array.isArray(d) ? d : [] });
      }
    } catch (e) { console.warn('GetDetectionLog failed:', e); }
  }
  // ---------- scan-results actions (per-row dropdown + batch apply) ----------
  choiceVerb(c) { return c === 'allow' ? 'Allowed & whitelisted' : c === 'delete' ? 'Deleted' : 'Kept in quarantine'; }
  choiceStatus(c) { return c === 'allow' ? 'restored' : c === 'delete' ? 'deleted' : 'quarantined'; }
  shortPath(p) {
    p = String(p || '');
    if (p.length <= 46) return p;
    const parts = p.split('\\');
    const tail = parts.slice(-1)[0] || p.slice(-24);
    const head = parts[0] && parts[0].length <= 12 ? parts[0] + '\\' : p.slice(0, 10);
    return head + '…\\' + tail;
  }
  setScanChoice(id, choice) {
    this.setState((s) => ({ scan: { ...s.scan, results: (s.scan.results || []).map((r) => r.id === id ? { ...r, choice } : r) } }));
  }
  toggleScanSel(id) {
    this.setState((s) => ({ scan: { ...s.scan, results: (s.scan.results || []).map((r) => r.id === id ? { ...r, sel: !r.sel } : r) } }));
  }
  toggleScanSelAll(on) {
    this.setState((s) => ({ scan: { ...s.scan, results: (s.scan.results || []).map((r) => r.actionable && !r.done ? { ...r, sel: on } : r) } }));
  }
  applyScanToSelected() {
    const c = this.state.scanBulk || 'quarantine';
    this.setState((s) => ({ scan: { ...s.scan, results: (s.scan.results || []).map((r) => r.sel && r.actionable && !r.done ? { ...r, choice: c } : r) } }));
  }
  neutralizeThreats() {
    const rows = (this.state.scan.results || []).filter((r) => r.actionable && !r.done && r.sel);
    if (!rows.length) { this.toast('Nothing selected', 'Select at least one threat to neutralize.', 'amber'); return; }
    const dels = rows.filter((r) => r.choice === 'delete').length;
    if (dels && !window.confirm('Permanently delete ' + dels + ' file(s)? This cannot be undone.')) return;
    const b = this.bridge();
    if (!b) { this.toast('Not connected', 'Protection service unavailable.', 'red'); return; }
    const items = rows.map((r) => (r.choice || 'quarantine') + '|' + r.path).join('\n');
    Promise.resolve(b.StartNeutralize(items)).catch((e) => this.toast('Neutralize failed', String(e), 'red'));
  }
  async applyScanActions() {
    const rows = (this.state.scan.results || []).filter((r) => r.actionable && !r.done);
    if (!rows.length) { this.toast('Nothing to apply', 'No pending threats.', 'blue'); return; }
    const dels = rows.filter((r) => r.choice === 'delete').length;
    if (dels && !window.confirm('Permanently delete ' + dels + ' file(s)? This cannot be undone.')) return;
    const b = this.bridge();
    if (!b) { this.toast('Not connected', 'Protection service unavailable.', 'red'); return; }
    let ok = 0, err = 0;
    for (const r of rows) {
      try {
        const call = r.choice === 'allow' ? b.AllowQuarantine(r.id) : r.choice === 'delete' ? b.DeleteQuarantine(r.id) : b.KeepQuarantine(r.id);
        const res = await call;
        if (res) { err++; this.toast('Failed', r.file + ': ' + res, 'red'); }
        else {
          ok++;
          this.addLog('event', this.choiceVerb(r.choice) + ': ' + r.file);
          const st = this.choiceStatus(r.choice);
          this.setState((s) => ({ scan: { ...s.scan, results: (s.scan.results || []).map((x) => x.id === r.id ? { ...x, done: true, actionable: false, sel: false, action: st } : x) } }));
        }
      } catch (e) { err++; this.toast('Error', String(e), 'red'); }
    }
    await this.refreshQuarantine();
    await this.refreshLogs(this.state.logTab);
    if (ok) this.toast('Actions applied', ok + ' file(s) handled.', 'green');
  }

  handleBridgeMsg(e) {
    let m = e.data;
    try { if (typeof m === 'string') m = JSON.parse(m); } catch (err) { return; }
    if (!m || !m.type) return;
    if (m.type === 'progress') {
      this.livePct = Math.min(95, this.livePct + 1.5);
      const pct = this.livePct;
      this.setState((s) => s.scan.running ? ({ scan: { ...s.scan, file: m.file || s.scan.file, pct, count: m.count || 0, threats: m.threats != null ? m.threats : s.scan.threats } }) : null);
    } else if (m.type === 'threat') {
      const file = String(m.path || m.name || '').split('\\').pop();
      const pending = m.action === 'pending';
      this.liveFound.push({ file, det: m.name, cat: m.category || 'Threat', path: m.path || '', id: m.path || m.name || String(this.liveFound.length), action: m.action || 'pending', actionable: pending, choice: 'quarantine', sel: true, done: false });
      this.setState((s) => ({ scan: { ...s.scan, threats: this.liveFound.length } }));
    } else if (m.type === 'done') {
      const found = this.liveFound.slice(); this.liveFound = []; this.livePct = 0;
      this.addLog('scan', `${this.state.scan.mode} completed — ${this.fmt(m.count || 0)} files, ${m.threats != null ? m.threats : found.length} threat(s)`);
      this.setState((s) => ({
        scanned: s.scanned + (m.count || 0),
        threats: s.threats + found.length,
        scan: { ...s.scan, running: false, file: found.length ? (found.length + ' threat(s) found — review and neutralize') : 'No threats found.', pct: 100, count: m.count || 0, threats: m.threats != null ? m.threats : found.length, results: found }
      }));
      if (found.length) this.toast(`${found.length} threat(s) found`, 'Choose actions, then Neutralize.', 'amber');
      else this.toast('Scan complete', 'No threats found — your PC is clean.', 'green');
      this.refreshLogs(this.state.logTab);
    } else if (m.type === 'neutralized') {
      const st = m.status || 'quarantined';
      this.setState((s) => ({ scan: { ...s.scan, results: (s.scan.results || []).map((r) => r.path === m.path ? { ...r, done: true, actionable: false, sel: false, action: st } : r) } }));
    } else if (m.type === 'ndone') {
      const n = parseInt(m.neutralized || '0', 10);
      const reboot = m.reboot === true || m.reboot === 'true';
      this.addLog('scan', `Neutralized ${n} threat(s)`);
      this.refreshQuarantine(); this.refreshLogs(this.state.logTab);
      if (reboot) this.toast('Restart needed', 'Some items will be fully removed after a restart.', 'amber');
      this.toast(n > 0 ? 'Threats neutralized' : 'Done', n > 0 ? (n + ' threat(s) neutralized. Your PC is clean.') : 'No changes made.', 'green');
    } else if (m.type === 'error') {
      this.liveFound = []; this.livePct = 0;
      this.setState((s) => ({ scan: { ...s.scan, running: false, file: 'Error: ' + (m.message || 'unknown') } }));
      this.toast('Scan error', (m.message || '') + (m.hint ? ' — ' + m.hint : ''), 'red');
    }
  }

  // ---------- helpers ----------
  now() {
    const p = (n) => (n < 10 ? '0' + n : '' + n);
    const d = new Date();
    return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
  }
  fmt(n) { return n.toLocaleString('en-US'); }

  // Turn Bridge.GetSystemInfo() into the six Dashboard "This PC" tiles.
  buildSysTiles(si) {
    si = si || {};
    const gb = (mb) => mb ? (mb / 1024).toFixed(1) : '0';
    const memPct = si.memTotalMB ? Math.round(si.memUsedMB * 100 / si.memTotalMB) : 0;
    const drivePct = si.driveTotalGB ? Math.round((si.driveTotalGB - si.driveFreeGB) * 100 / si.driveTotalGB) : 0;
    const uptimeText = (() => {
      const s = si.uptimeSec || 0;
      const h = Math.floor(s / 3600), m = Math.floor((s % 3600) / 60);
      if (s >= 86400) { const d = Math.floor(s / 86400); return d + 'd ' + (h % 24) + 'h'; }
      return h + 'h ' + m + 'm';
    })();
    const memColor = memPct >= 90 ? '#B42318' : (memPct >= 80 ? '#BA7517' : 'var(--text-3)');
    const memWeight = memPct >= 80 ? '600' : '400';
    const driveColor = drivePct >= 90 ? '#B42318' : (drivePct >= 80 ? '#BA7517' : '#15803D');
    return [
      { icon: '#i-monitor', label: 'Operating system',
        value: si.os || 'Windows',
        valueSize: '13px', valueFont: 'inherit',
        sub: si.osVersion || '',
        subColor: 'var(--text-3)', subWeight: '400' },
      { icon: '#i-cpu', label: 'Device',
        value: si.deviceName || '',
        valueSize: '12.5px', valueFont: 'Consolas,ui-monospace,monospace',
        sub: (si.user || '') + (si.userRole ? ' · ' + si.userRole : ''),
        subColor: 'var(--text-3)', subWeight: '400' },
      { icon: '#i-cpu', label: 'Processor',
        value: si.cpuName || 'Unknown',
        valueSize: '12.5px', valueFont: 'inherit',
        sub: (si.cpuCores ? si.cpuCores + ' cores' : '') + (si.arch ? ' · ' + si.arch : ''),
        subColor: 'var(--text-3)', subWeight: '400' },
      { icon: '#i-disk', label: 'Memory',
        value: gb(si.memUsedMB) + ' / ' + gb(si.memTotalMB) + ' GB',
        valueSize: '18px', valueFont: 'inherit',
        sub: memPct + '% in use',
        subColor: memColor, subWeight: memWeight },
      { icon: '#i-disk', label: 'System drive (' + (si.driveLetter || 'C:') + ')',
        value: (si.driveTotalGB - si.driveFreeGB) + ' / ' + si.driveTotalGB + ' GB',
        valueSize: '18px', valueFont: 'inherit',
        sub: drivePct + '% used · ' + si.driveFreeGB + ' GB free',
        subColor: driveColor, subWeight: '600' },
      { icon: '#i-clock', label: 'Uptime',
        value: uptimeText,
        valueSize: '18px', valueFont: 'inherit',
        sub: 'Since last boot',
        subColor: 'var(--text-3)', subWeight: '400' }
    ];
  }
  catColors(c) {
    if (/ransom|trojan|virus|worm|backdoor/i.test(c)) return { fg: 'var(--danger)', bg: 'var(--danger-bg)' };
    if (/adware|pua|suspicious|exploit/i.test(c)) return { fg: 'var(--warning)', bg: 'var(--warning-bg)' };
    return { fg: 'var(--info)', bg: 'var(--info-bg)' };
  }
  // Maps an on-detection outcome to a coloured badge for the scan results table.
  actionBadge(a) {
    const m = {
      quarantined: ['Quarantined', 'var(--success)', 'var(--success-bg)'],
      deleted: ['Deleted', 'var(--success)', 'var(--success-bg)'],
      review: ['Needs review', 'var(--warning)', 'var(--warning-bg)'],
      logged: ['Logged', 'var(--info)', 'var(--info-bg)'],
      restored: ['Allowed', 'var(--info)', 'var(--info-bg)'],
      failed: ['Action failed', 'var(--danger)', 'var(--danger-bg)']
    };
    const x = m[a] || m.quarantined;
    return { actionLabel: x[0], actFg: x[1], actBg: x[2] };
  }
  toast(title, desc, color) {
    const id = Math.random().toString(36).slice(2);
    const map = {
      red: { icon: '#i-alert', fg: 'var(--danger)', bg: 'var(--danger-bg)' },
      green: { icon: '#i-check', fg: 'var(--success)', bg: 'var(--success-bg)' },
      blue: { icon: '#i-info', fg: 'var(--info)', bg: 'var(--info-bg)' }
    };
    const m = map[color] || map.blue;
    this.setState((s) => ({ toasts: [...s.toasts, { id, title, desc, ...m }] }));
    setTimeout(() => this.setState((s) => ({ toasts: s.toasts.filter((t) => t.id !== id) })), 4200);
  }
  addLog(type, msg) {
    this.setState((s) => ({ logs: [{ t: this.now(), type, msg }, ...s.logs].slice(0, 200) }));
  }
  setTheme(t) {
    this.setState({ theme: t });
    try { localStorage.setItem('treeiq-theme', t); } catch (e) {}
    // Persist to settings.json so the tray (separate WebView2 process) can follow via RtBridge.GetTheme.
    try { var b = (window.chrome && chrome.webview && chrome.webview.hostObjects) ? chrome.webview.hostObjects.treeiq : null; if (b && b.SetTheme) b.SetTheme(t); } catch (e) {}
  }
  mkToggle(on, fn) {
    return { on, bg: on ? 'var(--accent)' : 'var(--border-strong)', knob: on ? '17px' : '0px', toggle: fn };
  }

  // ---------- scan ----------
  startScanPath(path) {
    const b = this.bridge();
    if (!b || !path) return;
    // Right-click "Scan with TreeIQ" opens the separate scan window too.
    Promise.resolve(b.OpenScanWindow('custom', path)).catch((e) => this.toast('Scan failed to start', String(e), 'red'));
  }
  async checkPendingScan() {
    const b = this.bridge(); if (!b) return;
    try { const p = String((await b.GetPendingScan()) || '').trim(); if (p) this.startScanPath(p); } catch (e) {}
  }
  openCustomScan() {
    const b = this.bridge();
    if (!b) { this.toast('Custom scan', 'Not available in preview.', 'blue'); return; }
    this._cs = this._cs || {};
    const cs = this._cs;
    if (!cs.el) {
      const ov = document.createElement('div');
      ov.style.cssText = 'position:fixed;inset:0;z-index:9999;background:rgba(0,0,0,.45);display:flex;align-items:center;justify-content:center;';
      ov.innerHTML = '<div style="width:600px;max-width:94vw;background:var(--card);border:1px solid var(--border);border-radius:14px;overflow:hidden;box-shadow:0 20px 50px rgba(0,0,0,.3);">'
        + '<div style="display:flex;align-items:center;gap:12px;padding:14px 16px;border-bottom:1px solid var(--border);">'
        + '<div style="width:32px;height:32px;border-radius:8px;background:var(--accent);display:flex;align-items:center;justify-content:center;">'
        + '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg></div>'
        + '<div style="flex:1;"><div style="font-size:15px;font-weight:600;color:var(--text-1);">Custom scan</div><div style="font-size:12px;color:var(--text-3);">Choose exactly what to scan</div></div>'
        + '<button id="cs-x" style="border:none;background:transparent;color:var(--text-3);font-size:18px;cursor:pointer;">&times;</button></div>'
        + '<div style="display:flex;align-items:center;gap:12px;padding:9px 16px;border-bottom:1px solid var(--border);background:var(--surface);">'
        + '<label style="display:flex;align-items:center;gap:7px;font-size:13px;color:var(--text-2);cursor:pointer;"><input type="checkbox" id="cs-hid">Show hidden and system items</label>'
        + '<div style="flex:1;"></div>'
        + '<button id="cs-all" style="font-size:12.5px;padding:5px 11px;border:1px solid var(--border-strong);background:transparent;color:var(--text-2);border-radius:6px;cursor:pointer;">Select all drives</button></div>'
        + '<div id="cs-tree" style="height:300px;overflow:auto;padding:6px 4px;font-size:13.5px;"></div>'
        + '<div style="padding:12px 16px;border-top:1px solid var(--border);background:var(--surface);">'
        + '<input type="text" id="cs-path" placeholder="Or paste a path" style="width:100%;box-sizing:border-box;margin-bottom:10px;padding:8px 10px;border:1px solid var(--border-strong);border-radius:6px;background:var(--card);color:var(--text-1);font-family:inherit;">'
        + '<div style="display:flex;align-items:center;gap:10px;">'
        + '<div style="flex:1;font-size:12.5px;color:var(--text-3);"><span id="cs-cnt" style="font-weight:600;color:var(--text-1);">0</span> items selected</div>'
        + '<button id="cs-cancel" style="padding:8px 14px;border:1px solid var(--border-strong);background:transparent;color:var(--text-2);border-radius:6px;cursor:pointer;">Cancel</button>'
        + '<button id="cs-scan" style="padding:8px 16px;border:none;background:var(--accent);color:#fff;border-radius:6px;cursor:pointer;font-weight:600;">Scan selected</button></div></div></div>';
      document.body.appendChild(ov);
      cs.el = ov; cs.tree = ov.querySelector('#cs-tree'); cs.cnt = ov.querySelector('#cs-cnt'); cs.hid = ov.querySelector('#cs-hid');
      ov.querySelector('#cs-x').onclick = () => this.closeCustomScan();
      ov.querySelector('#cs-cancel').onclick = () => this.closeCustomScan();
      ov.addEventListener('mousedown', (e) => { if (e.target === ov) this.closeCustomScan(); });
      cs.hid.onchange = () => this.csRender();
      ov.querySelector('#cs-all').onclick = () => { cs.nodes.forEach((n) => { if (n.drive) this.csSet(n, true); }); this.csRender(); };
      ov.querySelector('#cs-scan').onclick = () => this.csScan();
    }
    cs.el.style.display = 'flex';
    cs.el.querySelector('#cs-path').value = '';
    cs.nodes = [{ name: 'Running processes and memory', path: '::memory::', special: true }, { name: 'Boot sectors', path: '::boot::', special: true }];
    cs.tree.innerHTML = '<div style="padding:16px;color:var(--text-3);font-size:12.5px;">Loading drives…</div>';
    Promise.resolve(b.ListDir('')).then((json) => {
      let items = []; try { items = JSON.parse(json).items || []; } catch (e) {}
      items.forEach((it) => cs.nodes.push({ name: it.name, path: it.path, folder: true, drive: true, hidden: it.hidden, hasKids: it.kids, open: false, loaded: false, children: [] }));
      this.csRender();
    }).catch(() => { cs.tree.innerHTML = '<div style="padding:16px;color:var(--danger);">Could not list drives.</div>'; });
  }
  closeCustomScan() { if (this._cs && this._cs.el) this._cs.el.style.display = 'none'; }
  csSet(n, v) { n.checked = v; n.ind = false; (n.children || []).forEach((c) => this.csSet(c, v)); }
  csRefresh(n) { if (!n.loaded || !n.children.length) return; const all = n.children.every((c) => c.checked && !c.ind); const none = n.children.every((c) => !c.checked && !c.ind); n.checked = all; n.ind = !all && !none; }
  csRefreshAll() { const rec = (n) => { (n.children || []).forEach(rec); this.csRefresh(n); }; this._cs.nodes.forEach(rec); }
  csCount() { let c = 0; const rec = (n) => { if (n.special) { if (n.checked) c++; return; } if (n.checked && !n.ind) { c++; return; } if (n.ind) (n.children || []).forEach(rec); }; this._cs.nodes.forEach(rec); return c; }
  csTargets() { const out = []; const rec = (n) => { if (n.special) { if (n.checked) out.push(n.path); return; } if (n.checked && !n.ind) { out.push(n.path); return; } if (n.ind) (n.children || []).forEach(rec); }; this._cs.nodes.forEach(rec); return out; }
  csRender() {
    const cs = this._cs; this.csRefreshAll(); cs.tree.innerHTML = '';
    const showHidden = cs.hid.checked;
    const row = (n, d) => {
      if (n.hidden && !showHidden) return;
      const el = document.createElement('div');
      el.style.cssText = 'display:flex;align-items:center;gap:7px;padding:4px 0;padding-left:' + (d * 20 + 10) + 'px;' + (n.hidden ? 'opacity:.6;' : '');
      const hasKids = n.special ? false : n.hasKids;
      const chev = document.createElement('span');
      chev.style.cssText = 'width:16px;text-align:center;color:var(--text-3);cursor:' + (hasKids ? 'pointer' : 'default') + ';font-size:11px;';
      chev.textContent = hasKids ? (n.open ? '▾' : '▸') : '';
      if (hasKids) chev.onclick = () => this.csToggle(n);
      const cb = document.createElement('input'); cb.type = 'checkbox'; cb.checked = !!n.checked; cb.indeterminate = !!n.ind;
      cb.onchange = () => { this.csSet(n, cb.checked); this.csRender(); };
      const nm = document.createElement('span'); nm.textContent = n.name; nm.style.color = n.special ? 'var(--accent)' : 'var(--text-1)';
      el.appendChild(chev); el.appendChild(cb); el.appendChild(nm);
      if (n.hidden) { const t = document.createElement('span'); t.textContent = 'hidden'; t.style.cssText = 'margin-left:6px;font-size:10px;padding:1px 6px;border-radius:5px;background:var(--surface);color:var(--text-3);border:1px solid var(--border);'; el.appendChild(t); }
      cs.tree.appendChild(el);
      if (hasKids && n.open) (n.children || []).forEach((c) => row(c, d + 1));
    };
    cs.nodes.forEach((n) => row(n, 0));
    cs.cnt.textContent = this.csCount();
  }
  csToggle(n) {
    if (!n.open && !n.loaded) {
      const b = this.bridge(); n.open = true; this.csRender();
      Promise.resolve(b.ListDir(n.path)).then((json) => {
        let items = []; try { items = JSON.parse(json).items || []; } catch (e) {}
        n.children = items.map((it) => ({ name: it.name, path: it.path, folder: true, hidden: it.hidden, hasKids: it.kids, open: false, loaded: false, children: [], checked: n.checked && !n.ind, ind: false }));
        n.loaded = true; this.csRender();
      }).catch(() => { n.loaded = true; this.csRender(); });
    } else { n.open = !n.open; this.csRender(); }
  }
  csScan() {
    const targets = this.csTargets();
    const typed = this._cs.el.querySelector('#cs-path').value.trim();
    if (typed) targets.push(typed);
    if (!targets.length) { this.toast('Nothing selected', 'Tick at least one item to scan.', 'amber'); return; }
    this.closeCustomScan();
    this.runScanTargets(targets.join('|'));
  }
  runScanTargets(joined) {
    const b = this.bridge();
    if (b) Promise.resolve(b.OpenScanWindow('custom', joined)).catch((e) => this.toast('Scan failed to start', String(e), 'red'));
  }

  // Removable Media Scan: shortcut to the Custom picker with guidance so the user
  // navigates to their USB / external drive. A dedicated removable-only backend mode
  // is on the roadmap; for now, Custom handles the flow cleanly.
  startRemovableScan() {
    this.toast('Removable media', 'Pick your USB or external drive in the folder picker.', 'blue');
    this.startScan('custom');
  }

  // 2026-07-09: Boot Scan tile handler. Must live at the COMPONENT level (not inside the
  // render return object) because the tile's `run: () => this.startBootScan()` looks up
  // startBootScan on `this` — the component. Defining it in the render object leaves it
  // undefined on the component and the button silently no-ops (2026-07-09 bug).
  startBootScan() {
    const msg = 'This will restart your PC and run a Boot Scan.\n\n' +
                'The scan takes about 3-5 minutes and runs before your desktop is fully loaded, ' +
                'catching malware that locks files during normal use.\n\n' +
                'Save your work first. Continue?';
    if (!window.confirm(msg)) return;
    const b = this.bridge();
    if (!b || !b.StartBootScan) {
      this.toast('Boot Scan unavailable', 'The main service (TQKrn) needs to be running.', 'red');
      return;
    }
    try {
      Promise.resolve(b.StartBootScan())
        .then(() => this.toast('Boot Scan queued', 'Your PC will restart in 15 seconds.', 'green'))
        .catch((e) => this.toast('Boot Scan failed to start', String(e), 'red'));
    } catch (e) { this.toast('Boot Scan failed to start', String(e), 'red'); }
  }

  // ---------- Last USB event chip ----------
  // Reads bridge.GetLastUsbEvent (compact JSON or "null") synchronously and returns a chip descriptor.
  // Auto-hides when there's no event OR when the last event is older than 24 h (chip is transient
  // reassurance, not a permanent status — 24 h is enough for "I just plugged in a USB").
  buildLastUsbEvent(s) {
    const hidden = { show: false, summary: '', detail: '', pillText: '', pillColor: '', bg: 'var(--card)', border: 'var(--border)' };
    try {
      const b = this.bridge();
      if (!b || !b.GetLastUsbEvent) return hidden;
      const raw = b.GetLastUsbEvent();
      if (!raw || raw === 'null' || raw === 'undefined') return hidden;
      const e = (typeof raw === 'string') ? JSON.parse(raw) : raw;
      if (!e || !e.drive) return hidden;
      const ago = this.usbRelativeTime(e.ts);
      if (ago === null) return hidden;   // > 24 h — chip auto-hides
      const threats = Number(e.threats || 0);
      const findings = Number(e.findings || 0);
      const label = e.label || '(no label)';
      const action = String(e.action || '');
      const clean = threats === 0 && findings === 0 && action !== 'arrival';
      const arrivalOnly = action === 'arrival';
      // Summary line (bolded left half).
      const summary = e.drive + ' · ' + label + ' · ' + ago;
      // Detail line (secondary).
      let detail;
      if (arrivalOnly) detail = 'Drive plugged in — scan may still be running.';
      else if (clean) detail = 'Scanned ' + (e.scanned || 0) + ' file(s) — clean.';
      else detail = 'Scanned ' + (e.scanned || 0) + ' file(s) · ' + threats + ' threat(s) · ' + findings + ' USB finding(s).';
      // Pill on the right.
      let pillText, pillColor, bg, border;
      if (arrivalOnly) { pillText = 'Scanning…'; pillColor = 'var(--accent-ink)'; bg = 'var(--accent-soft)'; border = 'var(--accent-line)'; }
      else if (threats > 0) { pillText = threats + ' threat' + (threats === 1 ? '' : 's'); pillColor = 'var(--danger-ink)'; bg = 'var(--danger-bg)'; border = 'var(--danger-line)'; }
      else if (findings > 0) { pillText = findings + ' warning' + (findings === 1 ? '' : 's'); pillColor = '#B45309'; bg = '#FEF3E2'; border = '#F3D9A8'; }
      else { pillText = 'Clean'; pillColor = 'var(--success-ink)'; bg = 'var(--success-bg)'; border = 'var(--success-line)'; }
      return { show: true, summary, detail, pillText, pillColor, bg, border };
    } catch (e) { return hidden; }
  }

  // Last cleanup chip descriptor — builds from a bridge.GetLastRemediation() JSON row.
  // Hides after 48 h (System Remediation chip is longer-lived than the USB one).
  formatLastRemediation(raw) {
    const hidden = { show: false, summary: '', detail: '' };
    try {
      if (!raw) return hidden;
      const e = (typeof raw === 'string') ? JSON.parse(raw) : raw;
      if (!e || !e.ts) return hidden;
      const t = new Date(e.ts).getTime();
      if (isNaN(t)) return hidden;
      const mins = Math.round((Date.now() - t) / 60000);
      if (mins > 60 * 48) return hidden;
      const ago = mins < 1 ? 'just now' : (mins < 60 ? mins + ' min ago' : Math.round(mins/60) + ' h ago');
      const total = e.total | 0;
      const src   = ({ manual: 'Manual', scan: 'During scan', realtime: 'Real-time' }[e.source] || 'System');
      const parts = [];
      if (e.hosts)       parts.push(e.hosts + ' hosts');
      if (e.browsers)    parts.push(e.browsers + ' browser');
      if (e.autostarts)  parts.push(e.autostarts + ' autostart');
      if (e.persistence) parts.push(e.persistence + ' persistence');
      if (e.exclusions)  parts.push(e.exclusions + ' exclusions');
      const summary = src + ' cleanup · ' + ago;
      const detail = total === 0
        ? 'Nothing to remove — system was already clean.'
        : 'Removed ' + (parts.length ? parts.join(' · ') : total + ' item(s)') + (e.reboot ? ' · reboot pending' : '');
      return { show: true, summary, detail };
    } catch (e) { return hidden; }
  }

  // Returns "just now" / "N min ago" / "N h ago" or null if > 24 h (chip hides then).
  usbRelativeTime(iso) {
    try {
      const t = new Date(iso).getTime();
      if (isNaN(t)) return null;
      const mins = Math.round((Date.now() - t) / 60000);
      if (mins < 0) return 'just now';
      if (mins < 1) return 'just now';
      if (mins < 60) return mins + ' min ago';
      const hrs = Math.round(mins / 60);
      if (hrs <= 24) return hrs + ' h ago';
      return null;   // hide after 24 h
    } catch (e) { return null; }
  }

  // ---------- Protection page sparklines ----------
  // Renders the 3 sparkline cards from state.sparkHistory buffers. Each buffer is a rolling
  // 60-sample window (updated every 2 s by sparkTick). Sparkline = SVG polyline in a 240×32 viewBox.
  // Sample is normalised to the local max so the shape is always visible even when the value is small.
  buildSparks(s) {
    const W = 240, H = 32, N = 60;
    const defs = [
      { key: 'files',   label: 'Files scanned', unit: ' /s',   fmtLast: (v) => Math.round(v) },
      { key: 'threats', label: 'Threats blocked', unit: ' today', fmtLast: (v) => Math.round(v) },
      { key: 'amsi',    label: 'AMSI events', unit: ' /min', fmtLast: (v) => Math.round(v) }
    ];
    return defs.map((d) => {
      let data = (s.sparkHistory && s.sparkHistory[d.key]) || [];
      // Pad-left with zeros so a fresh page still renders a line (rests on the bottom).
      if (data.length < N) data = new Array(N - data.length).fill(0).concat(data);
      const max = Math.max(1, ...data);
      const step = W / (N - 1);
      const pts = data.map((v, i) => i * step + ',' + (H - (v / max) * (H - 3) - 1.5).toFixed(1));
      const linePath = 'M ' + pts.join(' L ');
      const areaPath = 'M 0,' + H + ' L ' + pts.join(' L ') + ' L ' + W + ',' + H + ' Z';
      return { key: d.key, label: d.label, unit: d.unit, value: d.fmtLast(data[data.length - 1] || 0), linePath, areaPath };
    });
  }
  // Builds the GlassWire-style 12-hour activity chart from s.logs (real data auto-synced every 5s).
  // Returns SVG paths for the blue activity mountain + red threat pins + amber AMSI dots + time-axis labels.
  // 5-minute bucketing = 144 buckets over 12h. Log-derived so it works from first open — no waiting for buffer to fill.
  // v2 GlassWire chart — Catmull-Rom smoothed area + category-icon pins + count badges + range switcher + click details.
  // Category order = severity rank (higher wins on multi-category buckets). Detection via log msg keywords.
  buildGlassWire(s) {
    const CATS = [
      { key: 'ransomware', rank: 9, label: 'Ransomware', href: '#i-cat-ransomware', re: /ransom|crypt(?:o|er|ed|locker)|\.locky|\.encrypt/ },
      { key: 'backdoor',   rank: 8, label: 'Backdoor',   href: '#i-cat-backdoor',   re: /backdoor|rat\b|remote access/ },
      { key: 'trojan',     rank: 7, label: 'Trojan',     href: '#i-cat-trojan',     re: /trojan|downloader|dropper|injector|stealer/ },
      { key: 'worm',       rank: 6, label: 'Worm',       href: '#i-cat-worm',       re: /worm/ },
      { key: 'virus',      rank: 6, label: 'Virus',      href: '#i-cat-virus',      re: /virus|infector|w32\./ },
      { key: 'spyware',    rank: 5, label: 'Spyware',    href: '#i-cat-spyware',    re: /spy|keylog|snoop/ },
      { key: 'miner',      rank: 5, label: 'Miner',      href: '#i-cat-miner',      re: /miner|xmrig|coinhive|cryptomin/ },
      { key: 'script',     rank: 4, label: 'Script',     href: '#i-cat-script',     re: /amsi|powershell|vbscript|wscript|\.ps1|macro|script blocked/ },
      { key: 'adware',     rank: 3, label: 'Adware',     href: '#i-cat-adware',     re: /adware|pua\b|potentially unwanted|toolbar|bundler/ },
      { key: 'generic',    rank: 1, label: 'Threat',     href: '#i-cat-generic',    re: null }
    ];
    const catFromMsg = (msg) => {
      const m = String(msg || '').toLowerCase();
      for (let i = 0; i < CATS.length - 1; i++) if (CATS[i].re.test(m)) return CATS[i];
      return CATS[CATS.length - 1];
    };
    const RANGES = {
      '12h': { buckets: 48, hours: 12, axisLabels: 7 },
      '24h': { buckets: 48, hours: 24, axisLabels: 7 },
      '7d':  { buckets: 56, hours: 168, axisLabels: 8 }
    };
    const range = RANGES[s.gwRange] || RANGES['12h'];
    const BUCKETS = range.buckets;
    const W = 1200, H = 200, TOP = 30;
    const now = new Date();
    const endMs = now.getTime();
    const startMs = endMs - (range.hours * 3600 * 1000);
    const bucketMs = (range.hours * 3600 * 1000) / BUCKETS;
    const files = new Array(BUCKETS).fill(0);
    const threatsByBucket = {}; // idx -> [{name, path, act, cat, tMs}]
    const amsiBuckets = {};     // idx -> true
    let totalThreats = 0, totalAmsi = 0, totalEvents = 0;
    (s.logs || []).forEach((l) => {
      const tMs = new Date(String(l.t).replace(' ', 'T')).getTime();
      if (isNaN(tMs) || tMs < startMs || tMs > endMs) return;
      const idx = Math.floor((tMs - startMs) / bucketMs);
      if (idx < 0 || idx >= BUCKETS) return;
      files[idx]++;
      totalEvents++;
      const msg = String(l.msg || '');
      const msgL = msg.toLowerCase();
      const type = String(l.type || '').toLowerCase();
      // Bridge classifies real detections as type="realtime" AFTER stripping the "[THREAT] " level prefix.
      // Real threat msg format examples (post-strip): "Worm.Win32.VB | E:\path\file.exe -> C:\ProgramData\JR-AV\quarantine\<id>.quar"
      // Scan-summary noise (also type="realtime"): "scanned 298 process(es), 0 threat(s)." — filtered below.
      const isZeroCount = /\b0\s+threat|detected\s*=\s*0|scanned\s*=\s*0|no threat/.test(msgL);
      const isSummary = /scanned\s+\d+|scan(\s+complete|ning)|protection\s+(started|stopped)|service\s+(started|stopped)|remediation cleaned/.test(msgL);
      const isVaultLine = msg.indexOf(' -> ') !== -1 && (msgL.indexOf('quarantine') !== -1 || msgL.indexOf('.quar') !== -1);
      const isDetectName = /^[A-Z][A-Za-z0-9._-]+(?:\s*\|\s+|\s+->\s+)/.test(msg); // "Worm.Win32.VB | ..." or "EICAR-Test-File -> ..."
      const isPrefixDetect = /^(quarantined|detected|blocked|threat blocked|threat found):/i.test(msg);
      const isRealDetect = isVaultLine || isDetectName || isPrefixDetect;
      const isThreat = !isZeroCount && !isSummary && (type === 'threat' || type === 'detected' || type === 'realtime') && isRealDetect;
      const isAmsi = !isThreat && !isZeroCount && (type === 'amsi' || (msgL.indexOf('amsi') !== -1 && (msgL.indexOf('block') !== -1 || msgL.indexOf('found') !== -1)) || msgL.indexOf('script blocked') !== -1);
      if (isThreat) {
        // Parse "FAMILY | SRC_PATH -> DEST_PATH" (e.g. "Worm.Win32.VB | E:\...\Attendence.exe -> C:\ProgramData\JR-AV\quarantine\...quar")
        let family = '', srcPath = '', destPath = '';
        const pipeIdx = msg.indexOf(' | ');
        const arrowIdx = msg.indexOf(' -> ');
        if (pipeIdx > 0 && arrowIdx > pipeIdx) {
          family = msg.substring(0, pipeIdx).trim();
          srcPath = msg.substring(pipeIdx + 3, arrowIdx).trim();
          destPath = msg.substring(arrowIdx + 4).trim();
        } else if (arrowIdx > 0) {
          family = msg.substring(0, arrowIdx).trim();
          destPath = msg.substring(arrowIdx + 4).trim();
        } else {
          family = msg.trim();
        }
        // Extract LAST path segment as the actual filename (handles folders named ".exe" correctly)
        let fileName = '';
        if (srcPath) {
          const segs = srcPath.split(/[\\\/]/);
          fileName = (segs[segs.length - 1] || '').trim();
        }
        if (!fileName) fileName = family || 'Threat';
        // Category detection uses family name first (more reliable than path noise)
        const cat = catFromMsg(family + ' ' + fileName + ' ' + msg);
        const destL = destPath.toLowerCase();
        const isQuar = destL.indexOf('quarantine') !== -1 || destL.indexOf('.quar') !== -1;
        const actM = msg.match(/\b(quarantined|deleted|killed|blocked|removed)\b/i);
        const act = isQuar ? 'Quarantined' : (actM ? actM[1].charAt(0).toUpperCase() + actM[1].slice(1).toLowerCase() : 'Blocked');
        if (!threatsByBucket[idx]) threatsByBucket[idx] = [];
        threatsByBucket[idx].push({
          name: fileName,
          family: family || fileName,
          path: srcPath || msg,
          act: act,
          cat: cat.key,
          catLabel: cat.label,
          catHref: cat.href,
          tMs
        });
        totalThreats++;
      } else if (isAmsi) {
        amsiBuckets[idx] = true;
        totalAmsi++;
      }
    });
    const maxFiles = Math.max(1, ...files);
    const dx = W / (BUCKETS - 1);
    const yFor = (v) => TOP + (H - TOP) - ((v / maxFiles) * (H - TOP - 8));
    // Catmull-Rom cubic-bezier smoothing — produces GlassWire-like waves.
    let gwFilesLine = 'M 0,' + yFor(files[0]).toFixed(1);
    for (let i = 0; i < BUCKETS - 1; i++) {
      const p0 = files[Math.max(0, i - 1)], p1 = files[i], p2 = files[i + 1], p3 = files[Math.min(BUCKETS - 1, i + 2)];
      const x1 = i * dx, x2 = (i + 1) * dx;
      const cp1x = x1 + dx / 3, cp1y = yFor(p1 + (p2 - p0) / 6);
      const cp2x = x2 - dx / 3, cp2y = yFor(p2 - (p3 - p1) / 6);
      gwFilesLine += ' C ' + cp1x.toFixed(1) + ',' + cp1y.toFixed(1) + ' ' + cp2x.toFixed(1) + ',' + cp2y.toFixed(1) + ' ' + x2.toFixed(1) + ',' + yFor(p2).toFixed(1);
    }
    const gwFilesArea = gwFilesLine + ' L ' + W + ',' + H + ' L 0,' + H + ' Z';
    const fmtTime = (tMs) => {
      const d = new Date(tMs);
      const h = d.getHours(), m = d.getMinutes();
      return (h % 12 || 12) + ':' + (m < 10 ? '0' + m : m) + ' ' + (h >= 12 ? 'PM' : 'AM');
    };
    // Build pins — hover shows floating tooltip; click opens detail panel below chart.
    const gwThreatPins = Object.keys(threatsByBucket).map((k) => {
      const i = parseInt(k, 10);
      const items = threatsByBucket[k];
      const dominant = items.reduce((best, it) => {
        const bestCat = CATS.find((c) => c.key === best.cat);
        const itCat = CATS.find((c) => c.key === it.cat);
        return itCat.rank > bestCat.rank ? it : best;
      }, items[0]);
      const anyT = items[0].tMs;
      const rawX = i * dx;
      const detail = { pinIdx: i, x: rawX, time: fmtTime(anyT), count: items.length, items };
      return {
        x: rawX.toFixed(1),
        count: items.length,
        countStr: String(items.length),
        hasBadge: items.length > 1,
        badgeX: (rawX + 12).toFixed(1),
        catHref: dominant.catHref,
        openDetail: () => this.setState((st) => ({ gwDetail: (st.gwDetail && st.gwDetail.pinIdx === i) ? null : detail, gwCatFilter: null }))
      };
    });
    const gwAmsiDots = Object.keys(amsiBuckets).map((k) => {
      const i = parseInt(k, 10);
      const val = files[i] || 0;
      return { x: (i * dx).toFixed(1), y: (yFor(val) - 8).toFixed(1) };
    });
    // Axis labels adapt to range.
    const gwTimeLabels = [];
    const stepH = range.hours / (range.axisLabels - 1);
    for (let i = 0; i < range.axisLabels; i++) {
      const d = new Date(endMs - (range.hours - i * stepH) * 3600 * 1000);
      let label;
      if (s.gwRange === '7d') {
        const days = Math.round((endMs - d.getTime()) / (24 * 3600 * 1000));
        label = days === 0 ? 'now' : days + 'd';
      } else {
        const h = d.getHours();
        label = i === range.axisLabels - 1 ? 'now' : (h % 12 || 12) + (h >= 12 ? 'PM' : 'AM');
      }
      gwTimeLabels.push({ label });
    }
    const rangeText = s.gwRange === '12h' ? '12 hours' : s.gwRange === '24h' ? '24 hours' : '7 days';
    const gwHeadline = totalEvents === 0
      ? 'TreeIQ has been protecting your PC quietly.'
      : this.fmt(totalEvents) + ' events · ' + totalThreats + ' threats · ' + totalAmsi + ' script blocks';
    return {
      gwFilesLine, gwFilesArea, gwThreatPins, gwAmsiDots, gwTimeLabels, gwHeadline,
      gwIsEmpty: totalEvents === 0, gwNotEmpty: totalEvents !== 0,
      gwRangeText: rangeText,
      gwKpiThreats: String(totalThreats),
      gwKpiEvents: this.fmt(totalEvents),
      gwKpiAmsi: String(totalAmsi)
    };
  }
  // Poll every 2 s while on the Protection page: shift each buffer left, append a new sample based on
  // the delta of the underlying counter. Currently sourced from what state exposes (scanned / threats /
  // amsi). Real per-second service counters are on the roadmap for a future backend IPC.
  sparkTick() {
    if (this.state.page !== 'realtime') return;                     // only tick when the user is looking
    const N = 60;
    const s = this.state;
    const prev = s.sparkPrev || { scanned: 0, threats: 0, amsi: 0 };
    const nowScanned = Number(s.scanned || 0);
    const nowThreats = Number(s.blockedToday != null ? s.blockedToday : (s.threats || 0));
    const nowAmsi = Number(s.amsiCount || 0);
    const dFiles = Math.max(0, nowScanned - (prev.scanned || 0)) / 2;   // per-second
    const dAmsi = Math.max(0, nowAmsi - (prev.amsi || 0)) * 30;         // per-minute (scaled from 2s window)
    this.setState((st) => {
      const h = st.sparkHistory || { files: [], threats: [], amsi: [] };
      const push = (arr, v) => { const a = arr.slice(-(N - 1)); a.push(v); return a; };
      return {
        sparkHistory: {
          files: push(h.files || [], dFiles),
          threats: push(h.threats || [], nowThreats),
          amsi: push(h.amsi || [], dAmsi)
        },
        sparkPrev: { scanned: nowScanned, threats: nowThreats, amsi: nowAmsi }
      };
    });
  }

  // Build the "Recent scans" strip on the Scan page from the service log.
  // The real service writes two lines per scan:
  //   REQUEST:  "On-demand scan requested: mode=quick path= report=False"
  //   COMPLETE: "On-demand scan complete: 34981 files, 0 threats."
  // We pair them (walk backwards) to grab the mode + compute duration. "firstrun" is hidden
  // per user request (post-install auto-scan, not something they triggered themselves).
  buildRecentScans(logs) {
    // 2026-07-09: added "boot" (Boot Scan) — icon uses the reboot glyph (#i-refresh) so the row
    // is instantly distinguishable in Recent Scans from a normal Quick/Full run.
    const iconFor = (m) => ({ quick: '#i-scan', full: '#i-disk', custom: '#i-folder', autoruns: '#i-bolt', memory: '#i-cpu', boot: '#i-refresh' }[m] || '#i-scan');
    const titleFor = (m) => ({ quick: 'Quick Scan', full: 'Full Scan', custom: 'Custom Scan', autoruns: 'Autoruns Scan', memory: 'Memory Scan', boot: 'Boot Scan' }[m] || 'Scan');
    const whenText = (iso) => {
      try {
        const d = new Date(String(iso).replace(' ', 'T'));
        const now = new Date();
        const diff = (now - d) / 1000;
        if (diff < 90) return 'Just now';
        if (diff < 3600) return Math.round(diff / 60) + 'm ago';
        if (diff < 86400) return Math.round(diff / 3600) + 'h ago';
        return d.toLocaleDateString();
      } catch (e) { return iso || ''; }
    };
    const durText = (start, end) => {
      try {
        const s0 = new Date(String(start).replace(' ', 'T'));
        const s1 = new Date(String(end).replace(' ', 'T'));
        const s = Math.max(0, Math.round((s1 - s0) / 1000));
        if (s >= 3600) return Math.floor(s / 3600) + 'h ' + Math.floor((s % 3600) / 60) + 'm';
        if (s >= 60) return Math.floor(s / 60) + 'm ' + (s % 60) + 's';
        return s + 's';
      } catch (e) { return ''; }
    };
    const rows = [];
    for (let i = logs.length - 1; i >= 0 && rows.length < 3; i--) {
      const l = logs[i]; if (!l || l.type !== 'scan') continue;
      const msg = String(l.msg || '');
      // real service format
      let m = msg.match(/On-demand scan complete:\s+([\d,]+)\s+files?,\s+(\d+)\s+threat/i);
      if (m) {
        const files = m[1], threats = parseInt(m[2], 10);
        // Walk backward up to 30 entries to find the matching REQUEST line for mode + duration.
        let mode = 'scan', reqTime = null;
        for (let j = i - 1; j >= Math.max(0, i - 30); j--) {
          const rl = logs[j]; if (!rl || rl.type !== 'scan') continue;
          const rm = String(rl.msg || '').match(/On-demand scan requested:\s+mode=(\w+)/i);
          if (rm) { mode = rm[1].toLowerCase(); reqTime = rl.t; break; }
        }
        if (mode === 'firstrun') continue;                                // hide the post-install drop-zone scan
        const filesFmt = files.indexOf(',') >= 0 ? files : this.fmt(parseInt(files, 10) || 0);
        rows.push({
          icon: iconFor(mode), type: titleFor(mode), when: whenText(l.t),
          summary: filesFmt + ' files · ' + (threats === 0 ? '0 threats' : threats + ' threats'),
          duration: reqTime ? durText(reqTime, l.t) : '',
          // 2026-07-09 Step 7: double-click opens a read-only ScanWindow populated from
          // scan_history.jsonl. Empty runId asks Bridge.OpenScanWindowReadonly for the LATEST
          // entry — good enough for boot scans since they always end up as the newest row.
          // If we ever need historical-row precision here we can correlate by timestamp.
          open: () => { const b = this.bridge(); if (b && b.OpenScanWindowReadonly) { try { b.OpenScanWindowReadonly(''); } catch (e) { this.toast('Report unavailable', String(e), 'red'); } } }
        });
        continue;
      }
      // legacy mockup format
      m = msg.match(/^(\w+)\s+completed\s+—\s+([\d,]+)\s+files?,\s+(\d+)\s+threat/i);
      if (m) {
        const mode = m[1].toLowerCase();
        if (mode === 'firstrun') continue;
        rows.push({
          icon: iconFor(mode), type: titleFor(mode), when: whenText(l.t),
          summary: m[2] + ' files · ' + (parseInt(m[3], 10) === 0 ? '0 threats' : m[3] + ' threats'),
          duration: '',
          open: () => { const b = this.bridge(); if (b && b.OpenScanWindowReadonly) { try { b.OpenScanWindowReadonly(''); } catch (e) {} } }
        });
      }
    }
    return rows;
  }

  startScan(mode) {
    if (mode === 'custom' && this.bridge()) { this.openCustomScan(); return; }
    const names = { quick: 'Quick Scan', full: 'Full Scan', custom: 'Custom Scan', autoruns: 'Autoruns Scan', memory: 'Memory Scan' };
    const b = this.bridge();
    if (b) {
      // Scans run in their own separate window.
      Promise.resolve(b.OpenScanWindow(mode, '')).catch((e) => this.toast('Scan failed to start', String(e), 'red'));
      return;
    }
    if (mode === 'custom') this.toast('Custom scan', 'Folder picker would open here (mockup).', 'blue');
    clearInterval(this.scanTimer);
    this.setState({ page: 'scan', scan: { visible: true, running: true, mode: names[mode], file: 'Preparing…', pct: 0, count: 0, threats: 0, results: null } });
    const THREATS = [
      ['Trojan.Win32.Agent.gen', 'Trojan'], ['not-a-virus:AdWare.InstallCore', 'Adware / PUA'],
      ['Worm.Win32.Brontok', 'Worm'], ['Ransom.Win32.Filecoder', 'Ransomware'],
      ['Backdoor.Win32.Bifrose', 'Backdoor'], ['EICAR-Test-File', 'Test file'],
      ['Virus.Win32.Sality', 'Virus'], ['Exploit.PDF-JS.gen', 'Exploit']
    ];
    const FILES = ['C:\\Windows\\System32\\kernel32.dll', 'C:\\Users\\ascoo\\Downloads\\report.pdf', 'C:\\Program Files\\App\\app.exe', 'C:\\Users\\ascoo\\AppData\\Local\\Temp\\tmp8f2.tmp', 'E:\\media\\video.mp4', 'C:\\Users\\ascoo\\Documents\\sheet.xlsx', 'C:\\Windows\\explorer.exe', 'D:\\backup\\archive.zip', 'C:\\Users\\ascoo\\Desktop\\notes.txt', 'C:\\Games\\launcher.exe'];
    let pct = 0, count = 0;
    const found = [];
    this.scanTimer = setInterval(() => {
      pct += Math.random() * 4 + 2;
      count += Math.floor(Math.random() * 900 + 200);
      const f = FILES[Math.floor(Math.random() * FILES.length)];
      if (Math.random() < 0.06) {
        const th = THREATS[Math.floor(Math.random() * THREATS.length)];
        found.push({ file: f.split('\\').pop(), det: th[0], cat: th[1], path: f, id: 'mock' + found.length, action: 'review', actionable: true, choice: 'keep', sel: false, done: false });
      }
      if (pct >= 100) { this.finishScan(names[mode], count, found); return; }
      this.setState((s) => ({ scan: { ...s.scan, file: f, pct: Math.min(pct, 100), count, threats: found.length } }));
    }, 180);
  }
  finishScan(mode, count, found) {
    clearInterval(this.scanTimer);
    this.addLog('scan', `${mode} completed — ${this.fmt(count)} files, ${found.length} threat(s)`);
    this.setState((s) => {
      const quar = [...found.map((f) => ({ id: 's' + Math.random().toString(36).slice(2, 7), file: f.file, cat: f.cat, det: f.det, path: f.path, date: this.now().slice(0, 16), sel: false })), ...s.quarantine];
      return {
        scanned: s.scanned + count,
        threats: s.threats + found.length,
        quarantine: quar,
        scan: { visible: true, running: false, mode, file: 'Completed.', pct: 100, count, threats: found.length, results: found }
      };
    });
    if (found.length) this.toast(`${found.length} threat(s) found`, 'Moved to quarantine.', 'red');
    else this.toast('Scan complete', 'No threats found.', 'green');
  }
  stopScan() {
    const b = this.bridge();
    if (b) { Promise.resolve(b.AbortScan()).catch(() => {}); this.liveFound = []; this.livePct = 0; }
    clearInterval(this.scanTimer);
    this.setState((s) => ({ scan: { ...s.scan, running: false, mode: s.scan.mode.replace(' running…', ''), file: 'Stopped.', results: null } }));
    this.toast('Scan stopped', '', 'blue');
  }

  // ---------- realtime ----------
  toggleRt() {
    const on = !this.state.rt;
    if (!on) {
      this.setState({ rtWarn: true });
      if (!window.confirm('Turn OFF real-time protection?\n\n(Real app: this needs administrator/UAC approval.)')) return;
    } else {
      this.setState({ rtWarn: false });
    }
    const b = this.bridge();
    if (b) { Promise.resolve(b.SetRealtime(on)).catch((e) => this.toast('Failed to set File Guard', String(e), 'red')); }
    // Merged setState — toggle + log push in ONE call so the runtime doesn't coalesce them and
    // drop the toggle update. Same pattern as procguardTgl / amsiEnabledTgl.
    this.setState((st) => ({
      rt: on,
      protection: on,
      simpleToggles: { ...st.simpleToggles, rt: on },
      logs: [{ t: this.now(), type: 'realtime', msg: 'File Guard ' + (on ? 'ENABLED' : 'DISABLED') }, ...(st.logs || [])].slice(0, 200)
    }));
    this.toast('File Guard ' + (on ? 'ON' : 'OFF'), '', on ? 'green' : 'red');
  }

  // ---------- quarantine ----------
  quarAction(a) {
    const sel = this.state.quarantine.filter((q) => q.sel);
    if (!sel.length) { this.toast('Nothing selected', 'Pick items first.', 'blue'); return; }
    if (a === 'restore' && !window.confirm('Restore ' + sel.length + ' file(s)?\n\nThese were detected as threats. They will be added to exclusions so they are not re-quarantined.')) return;
    if (a === 'delete' && !window.confirm('Permanently delete ' + sel.length + ' file(s)? This cannot be undone.')) return;
    const b = this.bridge();
    if (b) {
      (async () => {
        // Delete uses the batch API (one log rewrite for the whole set, not one per item — was the
        // cause of the UI freeze + false-toast storm on bulk clear). Restore stays per-item because
        // each file has to be moved back to its unique original path.
        if (a === 'delete') {
          try {
            const ids = sel.map((q) => q.id).join('\n');
            const res = String(await b.DeleteQuarantineMany(ids) || '');
            this.toast('Deleted', res + ' file(s).', 'green');
            this.addLog('event', 'Bulk delete from quarantine (' + res + ')');
          } catch (e) { this.toast('Error', String(e), 'red'); }
          await this.refreshQuarantine();
          await this.refreshLogs(this.state.logTab);
          return;
        }
        let errs = 0;
        for (const q of sel) {
          try {
            const r = await b.RestoreQuarantine(q.id);
            if (r) { errs++; this.toast('Restore failed', q.file + ': ' + r, 'red'); }
          } catch (e) { errs++; this.toast('Error', String(e), 'red'); }
        }
        await this.refreshQuarantine();
        await this.refreshLogs(this.state.logTab);
        if (sel.length - errs > 0) this.toast('Restored', (sel.length - errs) + ' file(s).', 'blue');
      })();
      return;
    }
    sel.forEach((q) => this.addLog('event', (a === 'restore' ? 'Restored from quarantine: ' : 'Deleted from quarantine: ') + q.file));
    this.setState((s) => ({
      quarantine: s.quarantine.filter((q) => !q.sel),
      exclusions: a === 'restore' ? [...s.exclusions, ...sel.map((q) => q.path)] : s.exclusions
    }));
    this.toast(a === 'restore' ? 'Restored' : 'Deleted', sel.length + ' file(s).', a === 'restore' ? 'blue' : 'green');
  }

  // Single-item decision for an "Ask me" (needs-review) detection: allow | keep | delete.
  quarDecide(id, action, file) {
    const b = this.bridge();
    const labels = { allow: 'Allow & restore', keep: 'Keep quarantined', delete: 'Delete permanently' };
    if (action === 'delete' && !window.confirm('Permanently delete "' + file + '"? This cannot be undone.')) return;
    if (action === 'allow' && !window.confirm('Allow "' + file + '"?\n\nIt was detected as a threat. It will be restored and added to exclusions.')) return;
    if (!b) { this.setState((st) => ({ quarantine: st.quarantine.filter((q) => q.id !== id) })); return; }
    (async () => {
      try {
        const call = action === 'allow' ? b.AllowQuarantine(id) : action === 'keep' ? b.KeepQuarantine(id) : b.DeleteQuarantine(id);
        const r = await call;
        if (r) { this.toast('Action failed', file + ': ' + r, 'red'); return; }
        this.addLog('event', labels[action] + ': ' + file);
        this.toast(labels[action], file, action === 'delete' ? 'green' : 'blue');
        await this.refreshQuarantine();
        await this.refreshLogs(this.state.logTab);
      } catch (e) { this.toast('Error', String(e), 'red'); }
    })();
  }

  // Bulk-decide every "needs decision" item at once (the banner buttons). Kills the 5k+ decision backlog.
  quarDecideAll(action) {
    const n = this.state.quarantine.filter((q) => q.review).length;
    if (!n) return;
    const labels = { allow: 'Allow & restore', keep: 'Keep quarantined', delete: 'Delete permanently' };
    if (action === 'delete' && !window.confirm('Permanently delete ' + n + ' file(s)? This cannot be undone.')) return;
    if (action === 'allow' && !window.confirm('Allow ' + n + ' file(s)?\n\nEach was detected as a threat. They will be restored and added to exclusions.')) return;
    const b = this.bridge(); if (!b) return;
    (async () => {
      try {
        const res = String(await b.DecideAllReview(action) || '');
        this.addLog('event', labels[action] + ' (' + res + ')');
        this.toast(labels[action], res, action === 'delete' ? 'green' : 'blue');
        await this.refreshQuarantine();
        await this.refreshLogs(this.state.logTab);
      } catch (e) { this.toast('Error', String(e), 'red'); }
    })();
  }

  // Open the Restore-with-options modal for one row (rowRestore) or the selected set (bulkRestore).
  openRestoreModal(items) {
    if (!items || !items.length) return;
    const single = items.length === 1;
    this.setState({ restoreModal: {
      ids: items.map((x) => x.id),
      subtitleTag: single ? (items[0].file + ' · ' + items[0].det) : items.length + ' items selected',
      origPath: single ? (items[0].path || '(unknown)') : '(each item to its own original path)',
      destMode: 'orig', folder: '', wlFile: true, wlProc: false, reason: ''
    } });
  }
  async restoreModalBrowse() {
    const b = this.bridge(); if (!b || !b.PickFolder) return;
    try {
      const path = String(await b.PickFolder() || '');
      if (path) this.setState((st) => ({ restoreModal: st.restoreModal ? { ...st.restoreModal, destMode: 'folder', folder: path } : st.restoreModal }));
    } catch (e) { this.toast('Folder picker failed', String(e), 'red'); }
  }
  async restoreModalSubmit() {
    const m = this.state.restoreModal; if (!m) return;
    if (m.wlProc && !(m.reason || '').trim()) return;   // guard also enforced by button disabled state
    const destFolder = m.destMode === 'folder' ? (m.folder || '') : '';
    const single = m.ids.length === 1;
    const dels = 0; // no delete; kept for symmetry
    const b = this.bridge(); if (!b) { this.toast('Not connected', 'Protection service unavailable.', 'red'); return; }
    // USB / non-original destination — warn once, even though the amber note is already visible in the modal.
    if (destFolder && !window.confirm('Restore ' + m.ids.length + ' file(s) to:\n\n' + destFolder + '\n\nContinue?')) return;
    if (m.wlProc && !window.confirm('You are also whitelisting the PROCESS.\n\nReal-time will not scan what this program does when it runs. Continue?')) return;
    this.setState({ restoreModal: null });
    try {
      let res;
      if (single) res = String(await b.RestoreQuarantineAdvanced(m.ids[0], destFolder, !!m.wlFile, !!m.wlProc, m.reason || '') || '');
      else res = String(await b.RestoreQuarantineManyAdvanced(m.ids.join('\n'), destFolder, !!m.wlFile, !!m.wlProc, m.reason || '') || '');
      if (single) {
        if (res) this.toast('Restore failed', res, 'red');
        else this.toast('Restored', 'File restored' + (m.wlFile ? ' · whitelisted' : '') + (m.wlProc ? ' · process whitelisted' : ''), m.wlProc ? 'amber' : 'blue');
      } else this.toast('Restored', res, 'blue');
      this.addLog('event', single ? 'Restored: ' + (m.ids[0]) + (destFolder ? ' → ' + destFolder : '') + (m.wlProc ? ' · reason: ' + (m.reason || '') : '')
                                : 'Bulk restore (' + res + ')' + (m.wlProc ? ' · reason: ' + (m.reason || '') : ''));
    } catch (e) { this.toast('Error', String(e), 'red'); }
    await this.refreshQuarantine();
    await this.refreshLogs(this.state.logTab);
  }

  // One-click cleanup of the vault-duplicate leftovers (records whose OriginalPath is inside our vault).
  quarClearDups() {
    const n = this.state.quarantine.filter((q) => q.dup).length;
    if (!n) return;
    if (!window.confirm('Clear ' + n + ' vault-duplicate record(s)? These are safe leftovers from an older bug (fixed).')) return;
    const b = this.bridge(); if (!b) return;
    (async () => {
      try {
        const res = String(await b.CleanVaultDuplicates() || '');
        this.addLog('event', 'Cleared vault duplicates (' + res + ')');
        this.toast('Duplicates cleared', res, 'green');
        await this.refreshQuarantine();
      } catch (e) { this.toast('Error', String(e), 'red'); }
    })();
  }

  // ---------- updates ----------
  // v1.0.6.1 — "Analyze logs" button. Round-trips through the SYSTEM service (which can read the
  // diagnostic files), which bundles + uploads them and returns a step-by-step result we show inline.
  async analyzeLogs() {
    this.dbg('analyzeLogs: CLICK received, busy=' + this.state.analyzing);
    if (this.state.analyzing) return;
    const b = this.bridge();
    if (!b || !b.AnalyzeLogsNow) { this.dbg('analyzeLogs: AnalyzeLogsNow missing'); this.setState({ analyzeMsg: 'Analyze not supported by this build.', analyzeOk: false }); return; }
    this.setState({ analyzing: true, analyzeOk: null, analyzeMsg: 'Analyzing — collecting logs and uploading…' });
    // Trigger returns immediately; the upload runs in the service. Poll the result file so a slow
    // SFTP transfer can't cause a "closed pipe" (the old bug).
    const _t0 = Date.now();
    try { await b.AnalyzeLogsNow(); this.dbg('analyzeLogs: AnalyzeLogsNow returned in ' + (Date.now() - _t0) + 'ms'); } catch (e) { this.dbg('analyzeLogs: AnalyzeLogsNow threw ' + (e && e.message)); }
    const started = Date.now();
    const poll = async () => {
      if (!b.GetAnalyzeResult) { this.setState({ analyzing: false, analyzeOk: false, analyzeMsg: 'Analyze result not supported by this build.' }); return; }
      try {
        const raw = await b.GetAnalyzeResult();
        const r = (typeof raw === 'string') ? JSON.parse(raw) : (raw || {});
        if (r.running) {
          if (Date.now() - started < 180000) { setTimeout(poll, 1200); return; }
          this.setState({ analyzing: false, analyzeOk: false, analyzeMsg: (r.detail || '') + '\n(timed out waiting for result)' });
          return;
        }
        const ok = !!r.ok;
        // Simple, non-revealing message only (no server paths). Details live in upload-log.txt.
        const msg = ok ? 'All files analyzed.' : (r.summary || 'Could not complete analysis.');
        this.setState({ analyzing: false, analyzeOk: ok, analyzeMsg: msg });
        this.addLog('update', 'Analyze logs: ' + (ok ? 'OK' : 'FAILED'));
      } catch (e) {
        if (Date.now() - started < 180000) { setTimeout(poll, 1200); return; }
        this.setState({ analyzing: false, analyzeOk: false, analyzeMsg: 'Analyze failed: ' + (e && e.message ? e.message : e) });
      }
    };
    setTimeout(poll, 1200);
  }

  // v1.0.6.2 — self-upgrade (test builds). Checks the SFTP upgrade server; if a newer fix exists,
  // confirms, then downloads+verifies+applies via the SYSTEM service. The app restarts on apply.
  async upgradeNow() {
    this.dbg('upgradeNow: CLICK received, busy=' + this.state.upgrading);
    if (this.state.upgrading) return;
    const b = this.bridge();
    if (!b || !b.CheckUpgrade) { this.dbg('upgradeNow: CheckUpgrade missing'); this.setState({ upgradeMsg: 'Upgrade not supported by this build.', upgradeOk: false }); return; }
    this.setState({ upgrading: true, upgradeOk: null, upgradeMsg: 'Checking for upgrade…' });
    // Trigger the check (service does FTP on a background thread), then poll a result file.
    const _u0 = Date.now();
    try { await b.CheckUpgrade(); this.dbg('upgradeNow: CheckUpgrade returned in ' + (Date.now() - _u0) + 'ms'); } catch (e) { this.dbg('upgradeNow: CheckUpgrade threw ' + (e && e.message)); }
    const cStart = Date.now();
    const chk = await new Promise((resolve) => {
      const poll = async () => {
        try {
          const raw = await b.GetUpgradeCheckResult();
          const r = (typeof raw === 'string') ? JSON.parse(raw) : (raw || {});
          if (r.running) { if (Date.now() - cStart < 30000) { setTimeout(poll, 800); return; } resolve({ error: 'timeout' }); return; }
          resolve(r);
        } catch (e) { if (Date.now() - cStart < 30000) { setTimeout(poll, 800); return; } resolve({ error: (e && e.message ? e.message : String(e)) }); }
      };
      setTimeout(poll, 800);
    });
    if (chk.error) { this.setState({ upgrading: false, upgradeOk: false, upgradeMsg: 'Check failed: ' + chk.error }); return; }
    if (!chk.ok && !chk.available) { this.setState({ upgrading: false, upgradeOk: false, upgradeMsg: chk.notes || 'Upgrade channel not available.' }); return; }
    if (!chk.available) { this.setState({ upgrading: false, upgradeOk: true, upgradeMsg: 'You are on the latest build (' + (chk.current || '') + ').' }); return; }
    // Professional in-app confirmation (replaces the native confirm() "app.treeiq says" box).
    // We stop here and pop a styled modal; upgradeConfirmProceed() continues the download+apply.
    this.setState({ upgrading: false, upgradeMsg: '', upgradeConfirm: { version: chk.latest || '', notes: chk.notes || '' } });
  }

  // User clicked "Download & apply" in the styled upgrade modal → run the apply + status poll.
  async upgradeConfirmProceed() {
    const c = this.state.upgradeConfirm; if (!c) return;
    const b = this.bridge(); if (!b) { this.setState({ upgradeConfirm: null }); return; }
    this.setState({ upgradeConfirm: null, upgrading: true, upgradeOk: null, upgradeMsg: 'Downloading and applying v' + c.version + '…' });
    try { await b.ApplyUpgrade(); } catch (e) {}
    const started = Date.now();
    const poll = async () => {
      try {
        const raw = await b.GetUpgradeStatus();
        const r = (typeof raw === 'string') ? JSON.parse(raw) : (raw || {});
        if (r.running) {
          if (Date.now() - started < 180000) { this.setState({ upgradeMsg: r.msg || 'Working…' }); setTimeout(poll, 1500); return; }
          this.setState({ upgrading: false, upgradeOk: false, upgradeMsg: (r.msg || '') + '\n(timed out)' }); return;
        }
        this.setState({ upgrading: false, upgradeOk: !!r.ok, upgradeMsg: r.msg || (r.ok ? 'Done.' : 'Failed.') });
      } catch (e) {
        // The app may be getting restarted by the apply step — that's expected on success.
        if (Date.now() - started < 180000) { setTimeout(poll, 1500); return; }
        this.setState({ upgrading: false, upgradeOk: false, upgradeMsg: 'Upgrade status unavailable (app may be restarting).' });
      }
    };
    setTimeout(poll, 1500);
  }

  // ---- System Guard (EDR cluster) shell ----
  loadSystemGuard() {
    try {
      const b = this.bridge(); if (!b || !b.GetSystemGuard) return;
      Promise.resolve(b.GetSystemGuard()).then((j) => {
        try { const g = (typeof j === 'string') ? JSON.parse(j) : j; if (g) this.setState({ sysGuard: g }); } catch (e) {}
      }).catch(() => {});
    } catch (e) {}
  }
  // Flip a module (or 'enabled' master / 'simulation'); persist via the service. camelCase state key;
  // the Bridge maps a lower-cased key to the settings field.
  sgToggle(key) {
    const g = Object.assign({}, this.state.sysGuard || {});
    g[key] = !g[key];
    this.setState({ sysGuard: g });
    try { const b = this.bridge(); if (b && b.SetSystemGuardModule) Promise.resolve(b.SetSystemGuardModule(key.toLowerCase(), !!g[key])).catch(() => {}); } catch (e) {}
  }
  buildSgView() {
    const g = this.state.sysGuard || {};
    const on = !!g.enabled;
    const defs = [
      ['deepFile',      'Deep file inspection',   'Checks a program structure for packers and injection before it runs'],
      ['forceUnlock',   'Force unlock and remove','Removes locked malicious files without a reboot'],
      ['behaviorRules', 'Behavior rules',         'Auto-acts when a program behaves like a threat'],
      ['persistence',   'Persistence guard',      'Alerts when a program adds itself to Windows startup'],
      ['network',       'Network monitor',        'See and cut off program connections'],
      ['rootkit',       'Rootkit guard',          'Finds processes hiding from Windows'],
      ['driverGuard',   'Driver guard',           'Blocks malware that loads vulnerable drivers'],
    ];
    const bg = (v) => v ? 'var(--accent)' : 'var(--border-strong)';
    const active = on ? defs.filter((d) => g[d[0]]).length : 0;
    return {
      statusText: on ? 'Active' : 'Off',
      statusFg: on ? 'var(--success)' : 'var(--text-3)',
      subText: on ? (active + ' of 7 modules on') : 'Turn on to protect',
      masterBg: bg(on), masterKnob: on ? '20px' : '0px',
      toggleMaster: () => this.sgToggle('enabled'),
      showTierNote: g.licensed === false,
      activeCount: active,
      simDot: g.simulation ? 'var(--warning)' : 'var(--text-3)',
      simBg: bg(!!g.simulation), simKnob: g.simulation ? '17px' : '0px',
      toggleSim: () => this.sgToggle('simulation'),
      modules: defs.map((d) => {
        const mon = on && !!g[d[0]];
        return {
          name: d[1], desc: d[2], stage: 'Soon',
          dot: mon ? 'var(--success)' : 'var(--text-3)',
          bg: bg(mon), knob: mon ? '17px' : '0px',
          toggle: () => this.sgToggle(d[0]),
        };
      }),
    };
  }

  // Real auto-updater bridge. Bridge.CheckForUpdates() writes a flag file that the service polls
  // every 30 s → triggers a manual check. We start polling Bridge.GetUpdateStatus() every 2 s while
  // updChecking is true so the UI stays live.
  async checkUpdate() {
    if (this.state.updChecking) return;
    const b = this.bridge();
    if (!b || !b.CheckForUpdates) {
      this.toast('Unavailable', 'Update bridge not ready.', 'red');
      return;
    }
    this.setState({ updChecking: true });
    try { await b.CheckForUpdates(); } catch (e) { /* fire-and-forget */ }
    this.addLog('update', 'Manual update check requested');
    // Poll every 1 s. Exit when EITHER:
    //   • the log timestamp moves past our trigger point (a real check ran), OR
    //   • updPhase reached a terminal state (done/idle) after leaving idle, OR
    //   • 30 s hard timeout (was 90 s — too long for a "check nothing to do" case).
    const startedAt = Date.now();
    const startingLast = (this.state.updLastCheckIso || '');
    let sawActivePhase = false;
    const poll = async () => {
      try {
        const j = await b.GetUpdateStatus();
        const s = (typeof j === 'string') ? JSON.parse(j) : (j || {});
        const status = s.status || {};
        this.setState({
          updInstalledMd5: s.installedMd5 || '',
          updPendingMd5:   s.pendingMd5   || '',
          updPending:      !!s.pending,
          updLastCheckIso: s.lastCheckIso || this.state.updLastCheckIso || '',
          updResult:       s.lastResult   || 'unknown',
          updLogLine:      s.lastLogLine  || '',
          updPhase:        status.phase   || 'idle',
          updPct:          Number(status.pct) || 0,
          updBytesDown:    Number(status.bytesDownloaded) || 0,
          updBytesExp:     Number(status.bytesExpected) || 0,
          // Per-part download progress (added 2026-08-17). Array of 4 {d, t} entries from the
          // service's vdb-status.json. Falls back to 4 empty slots so the 4-row UI still renders
          // (as "Waiting…") when the file was captured before the download threads started.
          updParts:        Array.isArray(status.parts) ? status.parts : [{},{},{},{}]
        });
        // Note when the service moves out of idle so we know a check actually started.
        if (status.phase && status.phase !== 'idle') sawActivePhase = true;
        const moved = s.lastCheckIso && s.lastCheckIso !== startingLast;
        const finished = sawActivePhase && (status.phase === 'idle' || status.phase === 'done' || !status.phase);
        if (moved || finished || (Date.now() - startedAt) > 30000) {
          this.setState({ updChecking: false });
          const vTail = this.state.dbVersion ? ' — V: ' + this.state.dbVersion : '';
          if (s.lastResult === 'installed') this.toast('New Virus Database ready', 'Applying in next 3 mins.', 'green');
          else if (s.lastResult === 'up-to-date') this.toast('Virus Database up to date', 'You have the latest updates' + vTail, 'green');
          else if (s.lastResult === 'failed') this.toast('Update failed', s.lastLogLine || 'Check the log.', 'red');
          return;
        }
      } catch (e) { /* continue polling */ }
      setTimeout(poll, 1000);
    };
    setTimeout(poll, 1000);
  }

  // Read the current updater state without triggering a check — called once at page load + every
  // 30 s so the "checked N minutes ago" text stays fresh.
  async refreshUpdateStatus() {
    try {
      const b = this.bridge();
      if (!b || !b.GetUpdateStatus) return;
      const j = await b.GetUpdateStatus();
      const s = (typeof j === 'string') ? JSON.parse(j) : (j || {});
      const status = s.status || {};
      // Detect a fresh background install: MD5 changed since last poll AND we didn't trigger it.
      const prevMd5 = this.state.updInstalledMd5 || '';
      const newMd5  = s.installedMd5 || '';
      if (prevMd5 && newMd5 && prevMd5 !== newMd5 && !this.state.updChecking) {
        try { this.toast('New Virus Database ready', 'Applying in next 3 mins.', 'green'); } catch (e) {}
        try { this.addLog('update', 'Auto-update installed a fresh VDB (' + newMd5.slice(0, 8) + ')'); } catch (e) {}
      }
      this.setState({
        updInstalledMd5: newMd5,
        updPendingMd5:   s.pendingMd5   || '',
        updPending:      !!s.pending,
        updLastCheckIso: s.lastCheckIso || '',
        updResult:       s.lastResult   || 'unknown',
        updLogLine:      s.lastLogLine  || '',
        updPhase:        status.phase || 'idle',
        updPct:          Number(status.pct) || 0,
        updBytesDown:    Number(status.bytesDownloaded) || 0,
        updBytesExp:     Number(status.bytesExpected) || 0,
        updParts:        Array.isArray(status.parts) ? status.parts : [{},{},{},{}]
      });
      // Real DB version — file mtime of tiq.db, refreshed here so the sidebar footer + about page
      // update automatically after a promotion at boot.
      if (b.GetVdbInfo) {
        try {
          const vj = await b.GetVdbInfo();
          // 2026-07-09 fix 2 + follow-up: also mirror to ui-debug.log via Bridge.WriteDebug so
          // we can trace the flow without needing DevTools open. The old console.log alone gave
          // us nothing on shipping builds because DevTools were disabled.
          try { console.log('[GetVdbInfo]', vj); } catch(_){}
          const vi = (typeof vj === 'string') ? JSON.parse(vj) : (vj || {});
          if (vi && vi.version && vi.version !== 'unknown') {
            const patch = { dbVersion: vi.version, dbSizeBytes: Number(vi.sizeBytes) || 0 };
            if (vi.dateCreate) patch.dbDate = vi.dateCreate;
            // Only overwrite `signatures` if the engine gave us a real count (> 0). Otherwise keep
            // the previous value so the About page stays populated.
            if (Number(vi.numSigs) > 0) patch.signatures = Number(vi.numSigs);
            this.setState(patch);
          } else {
            try { console.warn('[GetVdbInfo] skipped patch — version missing or "unknown":', vi); } catch(_){}
          }
        } catch (e) {
          try { console.error('[GetVdbInfo] threw:', e && (e.message || e)); } catch(_){}
        }
      }
    } catch (e) { /* silent */ }
  }

  // ---------- renderVals ----------
  renderVals() {
    const s = this.state;
    const TITLES = { dashboard: 'Dashboard', scan: 'Scan', realtime: 'Protection', quarantine: 'Quarantine', whitelist: 'Whitelist', updates: 'Updates', logs: 'Logs', tools: 'Tools', settings: 'Settings', about: 'About' };
    // Sidebar tabs — Logs / Quarantine / Whitelist rolled into the Tools consolidator (Phase 1
    // ships the tile page only; Phase 2-4 will open each tile in its own out-of-process window
    // so each tool has an independent memory footprint like modern AVs).
    // v1.0.4 — 4th slot is the FeatureId gate (null = always visible). The renderer filters
    // any entry whose FeatureId is missing from window.__TQ_FEATURES__ (Bridge.GetEnabledFeatures
    // result cached at boot + re-fetched on tier change). Menu items for locked features are
    // NOT rendered — removed entirely, not grayed out.
    const NAVMAIN = [
      ['dashboard',    'Dashboard',    '#i-dash',   null],
      ['scan',         'Scan',         '#i-scan',   'OnDemandScan'],
      ['realtime',     'Protection',   '#i-shield', 'FileGuard'],
      ['ransomGuard',  'Ransom Guard', '#i-shield', 'RansomGuardBehavior'],
      ['systemGuard',  'System Guard', '#i-cpu',    'SystemGuard'],
      ['updates',      'Updates',      '#i-update', 'VdbAutoUpdate'],
      ['tools',        'Tools',        '#i-box',    null]
    ];
    const NAVSYS = [['settings', 'Settings', '#i-gear', null], ['about', 'About', '#i-info', null]];

    // Feature list is fetched once at boot into a global set. Falls back to "all visible" if
    // the Bridge method is missing (older tray) — grandfather semantics.
    if (typeof window.__TQ_FEATURES__ === 'undefined') {
      window.__TQ_FEATURES__ = null;   // null = no filter yet (show everything until we know)
    }
    function tqFeatureAllowed(fid) {
      // 2026-07-11: in-house test build — show every menu item, settings section, and
      // dashboard tile regardless of tier. Tier framework wiring stays intact behind the
      // scenes so we can flip it back on the day we split into AV / IS / TS SKUs.
      //
      // TO RE-ENABLE tier-based hiding, replace the body with:
      //   if (!fid) return true;
      //   if (!window.__TQ_FEATURES__) return true;
      //   return window.__TQ_FEATURES__.has(fid);
      return true;
    }
    async function tqLoadFeatures() {
      try {
        if (typeof Bridge === 'undefined' || !Bridge.GetEnabledFeatures) return;
        const raw = Bridge.GetEnabledFeatures();
        const arr = JSON.parse(raw);
        if (Array.isArray(arr)) window.__TQ_FEATURES__ = new Set(arr);
      } catch (e) { /* silent — grandfather to all-visible */ }
    }
    // Kick the initial load. Poll every 5 sec to pick up tier upgrades (Bridge.PeekTierChange
    // signals when it's worth re-rendering).
    tqLoadFeatures();

    // v1.0.6 TEST-BUILD banner — installer drops %ProgramData%\JR-AV\test-build.marker in
    // internal-beta builds. Bridge.IsTestBuild returns true when the marker exists, and we
    // paint a persistent red strip at the top of the app so the tester never forgets they're
    // on a build that uploads diagnostic bundles.
    (function () {
      try {
        if (typeof Bridge === 'undefined' || !Bridge.IsTestBuild) return;
        var isTest = Bridge.IsTestBuild();
        if (!isTest) return;
        var strip = document.createElement('div');
        strip.style.cssText = 'position:fixed;top:0;left:0;right:0;z-index:9999;height:22px;background:#7a1414;color:#fff;font-family:Segoe UI,system-ui,sans-serif;font-size:11.5px;font-weight:600;display:flex;align-items:center;justify-content:center;letter-spacing:.4px;';
        strip.textContent = 'TREEIQ TEST BUILD · diagnostic logs are being uploaded to internal QA · press F2 to view kill switch';
        document.body.appendChild(strip);
        // Shift the rest of the app down so nothing hides behind the strip.
        document.body.style.paddingTop = '22px';
        // F2 = quick kill-switch info (registry path shown for testers)
        document.addEventListener('keydown', function (e) {
          if (e.key === 'F2') {
            alert('To pause diagnostic-log uploads, set:\n\nHKLM\\SOFTWARE\\TreeIQ\\AntiMalware\\DisableTelemetry (DWORD) = 1\n\nRestart the service to apply.');
          }
        });
      } catch (e) { /* silent */ }
    })();
    setInterval(async () => {
      try {
        if (typeof Bridge === 'undefined' || !Bridge.PeekTierChange) return;
        const marker = Bridge.PeekTierChange();
        if (marker && marker !== 'null' && marker !== '') {
          await tqLoadFeatures();
          // The React-like renderer picks up window.__TQ_FEATURES__ via the navItem filter,
          // so we just force a state tick.
          if (window.__TQ_ROOT__ && window.__TQ_ROOT__.forceUpdate) window.__TQ_ROOT__.forceUpdate();
          if (Bridge.AckTierChange) Bridge.AckTierChange();
        }
      } catch (e) { /* silent */ }
    }, 5000);
    // Triple-Shield badge — count how many of File Guard / Process Guard / Script Guard are OFF.
    // The Protection nav item shows this as a red badge ("1 OFF", "AT RISK", etc.) so the user
    // sees an urgent visual cue from anywhere in the app.
    const _guardsOff = (function () {
      let n = 0;
      if (!s.rt) n++;
      if (s.simpleToggles && s.simpleToggles.procguard === false) n++;
      if (s.amsi && s.amsi.enabled === false) n++;
      return n;
    })();
    const navItem = ([key, label, icon, feature]) => ({
      key, label, icon, feature,
      fw: s.page === key ? '600' : '500',
      color: s.page === key ? 'var(--accent)' : 'var(--text-3)',
      bg: s.page === key ? 'var(--accent-dim)' : 'transparent',
      count: key === 'quarantine' && s.quarantine.length ? String(s.quarantine.length)
           : key === 'realtime' && _guardsOff > 0 ? (_guardsOff === 3 ? 'UNPROTECTED' : _guardsOff === 1 ? '1 OFF' : _guardsOff + ' OFF')
           : false,
      select: () => this.setState({ page: key })
    });
    const WLTYPE = {
      file: { label: 'File', icon: '#i-box', tint: 'var(--accent)', scope: 'This file only' },
      folder: { label: 'Folder', icon: '#i-folder', tint: 'var(--warning)', scope: 'Folder + subfolders' },
      process: { label: 'Process', icon: '#i-cpu', tint: '#7C3AED', scope: 'Process image (all PIDs)' },
      drive: { label: 'Drive', icon: '#i-disk', tint: '#0891B2', scope: 'Entire drive' }
    };
    // Triple-Shield truth: `ok` used to mean "File Guard is on" (single-guard mental model).
    // Now it means "all 3 guards are on" so the dashboard hero can't lie when 1 or 2 are off.
    // `_guardsOff` is computed further up (used by the sidebar badge) — reuse it here.
    const ok = (_guardsOff === 0);

    // Relative "Disabled 12m ago" helper — never lies, always short. Handles empty ISO (returns
    // empty string so callers can render a positive "on" line instead).
    const _rel = (iso) => {
      if (!iso) return '';
      try {
        const then = new Date(iso).getTime();
        if (isNaN(then)) return '';
        const secs = Math.max(0, Math.floor((Date.now() - then) / 1000));
        if (secs < 60) return 'just now';
        if (secs < 3600) return Math.floor(secs / 60) + 'm ago';
        if (secs < 86400) return Math.floor(secs / 3600) + 'h ago';
        return Math.floor(secs / 86400) + 'd ago';
      } catch (e) { return ''; }
    };

    // Cards for the 3 Triple-Shield guards. Each row on the dashboard maps to one guard, with a
    // colored left stripe (green=on, red=off), an icon tile, "Watching N locations" (on) or
    // "Disabled Xm ago" (off), and a one-click Enable button when off.
    const _mkGuardCard = (key, name, icon, onDetail, isOn, disabledIso, enableFn) => {
      const off = !isOn;
      const stripe = off ? '#DC2626' : '#15803D';
      const iconBg = off ? 'var(--danger-bg)' : 'var(--success-bg)';
      const iconFg = off ? 'var(--danger)'    : 'var(--success)';
      const pillFg = off ? 'var(--danger)'    : 'var(--success)';
      const pillBg = off ? 'var(--danger-bg)' : 'var(--success-bg)';
      const pillText = off ? 'OFF' : 'ON';
      const rel = _rel(disabledIso);
      const detail = off ? (rel ? 'Disabled ' + rel : 'Currently disabled') : onDetail;
      return { key, name, icon, stripe, iconBg, iconFg, pillFg, pillBg, pillText, detail, showEnable: off, enable: enableFn };
    };
    const _pgOn = !!(s.simpleToggles && s.simpleToggles.procguard);
    const _sgOn = !!(s.amsi && s.amsi.enabled);
    const _fgOn = !!s.rt;
    const guardCards = [
      _mkGuardCard('file',    'File Guard',    '#i-shield',
        'Files, downloads and USB drives',
        _fgOn, s.fileGuardDisabledAt,
        () => this.toggleRt()),
      _mkGuardCard('process', 'Process Guard', '#i-cpu',
        'Running processes and memory',
        _pgOn, s.processGuardDisabledAt,
        () => {
          this.setState((st) => ({ simpleToggles: { ...st.simpleToggles, procguard: true } }), () => {
            const b = this.bridge();
            if (b && b.SetProcessGuard) Promise.resolve(b.SetProcessGuard(true)).catch(() => {});
          });
        }),
      _mkGuardCard('script',  'Script Guard',  '#i-scan',
        'PowerShell, Office macros, VBS',
        _sgOn, s.scriptGuardDisabledAt,
        () => {
          this.setState((st) => ({ amsi: { ...st.amsi, enabled: true } }), () => {
            const b = this.bridge();
            if (b && b.SetAmsiEnabled) Promise.resolve(b.SetAmsiEnabled(true)).catch(() => {});
          });
        })
    ];
    // Dual palette for log-line type pills — same theme model the Scan tab uses so all Logs tabs
    // look consistent under both Lite and Carbon Matte themes.
    const _isDarkLog = (s.theme === 'carbon');
    const LOG_TYPE_META = {
      scan:     { label:'Scan',       icon:'#i-scan',    L:{fg:'#7C3AED',bg:'#EDE9FE'}, D:{fg:'#C4B5FD',bg:'#2B1E4A'} },
      realtime: { label:'Protection', icon:'#i-shield',  L:{fg:'#065F46',bg:'#D1FAE5'}, D:{fg:'#6EE7B7',bg:'#14372A'} },
      update:   { label:'Update',     icon:'#i-update',  L:{fg:'#1D4ED8',bg:'#DBEAFE'}, D:{fg:'#93C5FD',bg:'#1E3A5F'} },
      event:    { label:'Event',      icon:'#i-info',    L:{fg:'#374151',bg:'#F3F4F6'}, D:{fg:'#D1D5DB',bg:'#374151'} },
      usb:      { label:'USB',        icon:'#i-usb',     L:{fg:'#0D9488',bg:'#CCFBF1'}, D:{fg:'#5EEAD4',bg:'#0F3B36'} },
      danger:   { label:'Alert',      icon:'#i-alert',   L:{fg:'#B91C1C',bg:'#FEE2E2'}, D:{fg:'#FCA5A5',bg:'#4A1414'} },
      warn:     { label:'Neutralized',icon:'#i-lock',    L:{fg:'#065F46',bg:'#D1FAE5'}, D:{fg:'#6EE7B7',bg:'#14372A'} },
      good:     { label:'OK',         icon:'#i-check',   L:{fg:'#065F46',bg:'#D1FAE5'}, D:{fg:'#6EE7B7',bg:'#14372A'} }
    };
    const logBadge = (type) => {
      const m = LOG_TYPE_META[type] || LOG_TYPE_META.event;
      const p = _isDarkLog ? m.D : m.L;
      return { fg: p.fg, bg: p.bg, typeLabel: m.label, typeIcon: m.icon };
    };

    // Log translator — rewrites raw service diagnostics into user-facing English + drops noise.
    // Returns null to hide the row entirely (heartbeats, caches, internal flags — nothing users
    // benefit from seeing). Returns { title, detail, severity, typePillOverride } to render.
    //   severity: 'danger' (red pill) | 'warn' (amber) | 'good' (green) | null (keep original type color)
    //   typePillOverride: force a specific type name to display (e.g. "Alert", "Warning")
    function translateLogLine(rawMsg, rawType) {
      const m = String(rawMsg || '');
      const lo = m.toLowerCase();

      // ── NOISE — drop the row entirely ──
      if (lo.includes('heartbeat')) return null;
      if (lo.includes('[cache]')) return null;
      if (lo.startsWith('[verify] md5')) return null;
      if (lo.includes('flag detected')) return null;
      if (lo.includes('background init')) return null;
      if (lo.includes('engine ready')) return null;
      if (lo.includes('scan-cache loaded')) return null;
      if (lo.includes('rate-limited')) return null;
      if (lo.includes('deferred —')) return null;
      if (lo.includes('vdbupdater armed')) return null;
      if (lo.includes('rt-toast')) return null;
      if (lo.startsWith('[stamp]')) return null;
      if (lo.startsWith('[flag]')) return null;
      if (lo.includes('poll') && lo.includes('every')) return null;

      // ── CRITICAL — bright red + strong wording (Triple-Shield: 3 guards) ──
      if ((lo.includes('file guard') || lo.includes('real-time')) && (lo.includes('off') || lo.includes('disabled'))) {
        return { title: 'File Guard turned OFF — files, downloads, USB unprotected', severity: 'danger', typePillOverride: 'At risk' };
      }
      if (lo.includes('process guard') && (lo.includes('off') || lo.includes('disabled'))) {
        return { title: 'Process Guard turned OFF — running processes not monitored', severity: 'danger', typePillOverride: 'At risk' };
      }
      if ((lo.includes('script guard') || lo.includes('amsi')) && (lo.includes('off') || lo.includes('disabled'))) {
        return { title: 'Script Guard turned OFF — PowerShell/macros/VBS can run unblocked', severity: 'danger', typePillOverride: 'At risk' };
      }
      if (lo.includes('threat detected') || lo.startsWith('threat:') || (lo.includes('malware') && lo.includes('blocked'))) {
        return { title: 'Malware blocked', detail: extractAfter(m, ['detected:', 'blocked:', ':']), severity: 'danger', typePillOverride: 'Alert' };
      }
      if (lo.includes('update failed') || (lo.includes('failed') && rawType === 'update')) {
        return { title: 'Update failed — will retry automatically', severity: 'danger', typePillOverride: 'Warning' };
      }

      // ── WARNING / ATTENTION ──
      if (lo.includes('reboot') || lo.includes('restart') && lo.includes('remov')) {
        return { title: 'Restart needed to finish cleanup', severity: 'warn' };
      }
      if (lo.includes('quarantin')) {
        return { title: 'Moved to Quarantine', detail: extractPath(m), severity: 'warn' };
      }
      if (lo.includes('new signatures') || lo.includes('signatures ready') || lo.includes('[install] staged')) {
        return { title: 'New virus definitions downloaded', severity: null };
      }

      // ── GOOD / NORMAL — each guard has its own "turned on" translation ──
      if ((lo.includes('file guard') || lo.includes('real-time')) && (lo.includes('on') || lo.includes('enabled'))) {
        return { title: 'File Guard turned ON', detail: 'Files, downloads, USB drives are being scanned.', severity: 'good' };
      }
      if (lo.includes('process guard') && (lo.includes('on') || lo.includes('enabled'))) {
        return { title: 'Process Guard turned ON', detail: 'Running processes and memory are being monitored.', severity: 'good' };
      }
      if ((lo.includes('script guard') || lo.includes('amsi')) && (lo.includes('on') || lo.includes('enabled'))) {
        return { title: 'Script Guard turned ON', detail: 'PowerShell, Office macros and VBS are being blocked.', severity: 'good' };
      }
      if (lo.includes('[vdb-promote] promoted') || lo.includes('signatures activated') || lo.includes('engine reloaded')) {
        return { title: 'Signatures activated', detail: 'Fresh virus database now protecting.', severity: 'good' };
      }
      if (lo.includes('scan complete') || lo.includes('scan finished')) {
        return { title: 'Scan finished', detail: m.replace(/^\[.*?\]\s*/i, ''), severity: null };
      }
      if (lo.includes('scan started') || lo.includes('scan starting')) {
        return { title: 'Scan started', severity: null };
      }
      if (lo.includes('up-to-date')) {
        return { title: 'Signatures are up to date', severity: null };
      }
      if (lo.includes('[check] new vdb available')) {
        return { title: 'Downloading new virus definitions…', severity: null };
      }
      if (lo.includes('service') && (lo.includes('start') || lo.includes('stopp'))) {
        return { title: lo.includes('start') ? 'Protection service started' : 'Protection service stopped', severity: null };
      }
      if (rawType === 'usb') {
        // USB messages are already curated by the merge step above — pass through.
        return { title: m, severity: null };
      }

      // ── Unknown → drop it (safer default: only show what we understand). If we later find
      //    important messages we're accidentally hiding, we'll add rules.
      return null;
    }

    function extractAfter(s, seps) {
      for (const sep of seps) {
        const i = s.toLowerCase().indexOf(sep);
        if (i >= 0) return s.slice(i + sep.length).trim();
      }
      return '';
    }
    function extractPath(s) {
      const m = s.match(/[A-Za-z]:\\[^\s]+/);
      return m ? m[0] : '';
    }
    // Zebra tint for even rows — matches Scan tab visual density.
    const _zebraBg = _isDarkLog ? '#141416' : '#FAFAFB';
    const dotCol = (c) => c === 'green' ? 'var(--success)' : c === 'amber' ? 'var(--warning)' : c === 'red' ? 'var(--danger)' : 'var(--info)';

    const recent = s.logs.slice(0, 5).map((l) => ({ msg: l.msg, time: l.t.split(' ')[1], dot: dotCol(l.type === 'realtime' ? 'green' : l.type === 'event' ? 'amber' : 'blue') }));
    const rtRecent = s.logs.filter((l) => l.type === 'realtime').slice(0, 5).map((l) => ({ msg: l.msg, time: l.t.split(' ')[1] }));
    // Logs → Scan tab: rows that log a completed on-demand scan get an ondblclick handler that
    // opens the full readonly scan-details window (same UI as the live scan window in review state).
    // Matching a Logs row to a scan_history entry: parse the row timestamp, find the entry within
    // ±30 s. Rows without a match (older runs pruned from history) silently do nothing.
    // Merge USB events into the flat log stream when the user is on the "All" tab so they see the
    // complete timeline (scans + realtime + updates + events + USB) in chronological order. USB
    // events keep type='usb' so the teal pill lights up.
    const _usbFlat = (s.usbEvents || []).map((u) => {
      const t = String(u.action || 'event').toLowerCase();
      const threats = Number(u.threats) || 0;
      const scanned = Number(u.scanned) || 0;
      let msg = 'USB ' + (u.drive || '') + ' ' + (u.label ? '(' + u.label + ') ' : '') + '· ';
      if (t === 'inserted') msg += 'Detected on arrival';
      else if (t === 'scan-complete' && threats > 0) msg += 'Scan complete · ' + scanned.toLocaleString() + ' files · ' + threats + ' threats';
      else if (t === 'scan-complete') msg += 'Scan complete · ' + scanned.toLocaleString() + ' files · clean';
      else if (t === 'vaccinated') msg += 'Vaccinated (autorun-worm blocking applied)';
      else if (t === 'unvaccinated') msg += 'Vaccine removed';
      else if (t === 'remediated') msg += 'LNK/autorun worm cleaned';
      else if (t === 'ejected') msg += 'Removed';
      else msg += t;
      return { t: (u.ts || '').replace('T', ' ').slice(0, 19), type: 'usb', msg };
    });
    const _logsWithUsb = (s.logTab === 'all')
      ? [...s.logs, ..._usbFlat].sort((a, b) => (b.t || '').localeCompare(a.t || ''))
      : s.logs;
    // Translate every raw log entry to user-friendly English + drop noise. Rows where the translator
    // returns null are hidden completely — the user only sees curated events, not diagnostics.
    // The tab filter runs AFTER translation so we can surface danger-severity rows in Events tab
    // regardless of the underlying log type (a "Real-time OFF" row belongs in BOTH Protection AND
    // Events — it's a critical system event that no user should miss).
    // Search filter for the Logs page — case-insensitive substring over message, translated text,
    // time, and type. Applied after tab filter so the count already reflects the tab.
    const _logQ = (s.logQuery || '').trim().toLowerCase();
    const _translatedLogs = _logsWithUsb
      .map((l) => ({ ...l, _tr: translateLogLine(l.msg, l.type) }))
      .filter((l) => l._tr !== null)
      .filter((l) => {
        if (s.logTab === 'all') return true;
        if (l.type === s.logTab) return true;
        if (s.logTab === 'event' && l._tr && l._tr.severity === 'danger') return true;
        return false;
      })
      .filter((l) => {
        if (!_logQ) return true;
        const hay = ((l.msg || '') + ' ' + ((l._tr && l._tr.text) || '') + ' ' + (l.t || '') + ' ' + (l.type || '')).toLowerCase();
        return hay.indexOf(_logQ) !== -1;
      });
    const logRows = _translatedLogs.map((l, idx) => {
      // Severity override — a "Real-time protection OFF" line comes from type='realtime' but we
      // upgrade the pill to bright red so the user's eye lands on it instantly.
      const severity = l._tr && l._tr.severity;
      const effectiveType = severity || l.type;
      const badge = logBadge(effectiveType);
      const displayLabel = (l._tr && l._tr.typePillOverride) || badge.typeLabel;
      const humanTitle = l._tr && l._tr.title ? l._tr.title : l.msg;
      const humanDetail = l._tr && l._tr.detail ? l._tr.detail : '';
      const combinedMsg = humanDetail ? (humanTitle + ' · ' + humanDetail) : humanTitle;
      // Row highlight — for critical rows (danger severity) the ENTIRE row gets a red-tinted
      // background so the user's eye lands on it immediately, even in a long log. Overrides zebra.
      let rowBg = (idx % 2 === 1) ? _zebraBg : 'transparent';
      if (severity === 'danger') rowBg = _isDarkLog ? 'rgba(248,113,113,0.10)' : 'rgba(220,38,38,0.06)';
      else if (severity === 'warn') rowBg = _isDarkLog ? 'rgba(252,211,77,0.07)' : 'rgba(180,83,9,0.05)';
      const base = { ...l, fg: badge.fg, bg: badge.bg, typeIcon: badge.typeIcon, type: displayLabel, msg: combinedMsg, rowBg };
      const isScanComplete = typeof l.msg === 'string' && l.msg.toLowerCase().indexOf('scan complete') >= 0;
      if (!isScanComplete) return { ...base, canOpen: false, openReadonly: () => {}, cursor: 'default', rowTitle: '' };
      const openReadonly = () => {
        const b = this.bridge();
        if (!b || !b.GetScanRunNearTime || !b.OpenScanWindowReadonly) return;
        // WebView2 hostObject methods return Promises — must .then() to get the string result.
        const iso = l.t.replace(' ', 'T');
        Promise.resolve(b.GetScanRunNearTime(iso, 60))
          .then((raw) => {
            if (!raw || raw === 'null') {
              this.toast('Scan details', 'No matching scan record found (older than history retains).', 'amber');
              return;
            }
            let run = null;
            try { run = JSON.parse(raw); } catch (e) { return; }
            if (!run || !run.id) return;
            Promise.resolve(b.OpenScanWindowReadonly(run.id)).catch(() => {});
          })
          .catch(() => {});
      };
      return { ...base, canOpen: true, openReadonly, cursor: 'pointer', rowTitle: 'Click to open full scan details' };
    });

    // Scan history rows for the Logs → Scan tab (structured table like the tray Activity view).
    // Loaded from bridge.GetScanHistory into s.scanHistory (see settings loader). Each row is a
    // completed scan run — click any row to open the readonly scan window with full threat table.
    // Dual palette — same key, different bg/fg per theme. Dark values are dimmer + higher-contrast
    // ink so pills sit calmly against the near-black carbon canvas instead of glowing.
    const isDark = (s.theme === 'carbon');
    const modeMeta = {
      quick:       { label:'Quick',        icon:'#i-bolt',    L:{fg:'#1D4ED8',bg:'#DBEAFE'}, D:{fg:'#93C5FD',bg:'#1E3A5F'} },
      full:        { label:'Full',         icon:'#i-scan',    L:{fg:'#065F46',bg:'#D1FAE5'}, D:{fg:'#6EE7B7',bg:'#14372A'} },
      custom:      { label:'Custom',       icon:'#i-folder',  L:{fg:'#374151',bg:'#F3F4F6'}, D:{fg:'#D1D5DB',bg:'#374151'} },
      autoruns:    { label:'Autoruns',     icon:'#i-refresh', L:{fg:'#B45309',bg:'#FEF3C7'}, D:{fg:'#FCD34D',bg:'#3F2D0C'} },
      firstrun:    { label:'First-run',    icon:'#i-flask',   L:{fg:'#0E7490',bg:'#CFFAFE'}, D:{fg:'#67E8F9',bg:'#0F3138'} },
      'usb-auto':  { label:'USB auto',     icon:'#i-usb',     L:{fg:'#0D9488',bg:'#CCFBF1'}, D:{fg:'#5EEAD4',bg:'#0F3B36'} },
      usb:         { label:'USB scan',     icon:'#i-usb',     L:{fg:'#0D9488',bg:'#CCFBF1'}, D:{fg:'#5EEAD4',bg:'#0F3B36'} },
      contextmenu: { label:'Context-menu', icon:'#i-menu',    L:{fg:'#4338CA',bg:'#E0E7FF'}, D:{fg:'#A5B4FC',bg:'#1E1F4A'} },
      weekly:      { label:'Weekly sweep', icon:'#i-clock',   L:{fg:'#0369A1',bg:'#E0F2FE'}, D:{fg:'#7DD3FC',bg:'#0F2B3F'} },
      force:       { label:'Force full',   icon:'#i-alert',   L:{fg:'#B91C1C',bg:'#FEE2E2'}, D:{fg:'#FCA5A5',bg:'#4A1414'} },
      'force-full':{ label:'Force full',   icon:'#i-alert',   L:{fg:'#B91C1C',bg:'#FEE2E2'}, D:{fg:'#FCA5A5',bg:'#4A1414'} },
      rescan:      { label:'RT rescan',    icon:'#i-refresh', L:{fg:'#6D28D9',bg:'#EDE9FE'}, D:{fg:'#C4B5FD',bg:'#2B1E4A'} },
      memory:      { label:'Memory scan',  icon:'#i-cpu',     L:{fg:'#7C2D12',bg:'#FFEDD5'}, D:{fg:'#FED7AA',bg:'#3F1D0C'} },
      boot:        { label:'Boot scan',    icon:'#i-disk',    L:{fg:'#0F172A',bg:'#E2E8F0'}, D:{fg:'#CBD5E1',bg:'#1E293B'} }
    };
    const pick = (m) => { const p = isDark ? m.D : m.L; return { label:m.label, fg:p.fg, bg:p.bg, icon:m.icon || '#i-scan' }; };
    const modeInfo = (m) => {
      const k = String(m||'').toLowerCase();
      if (modeMeta[k]) return pick(modeMeta[k]);
      const label = m ? (m.charAt(0).toUpperCase()+m.slice(1)) : 'Scan';
      return { label, fg:'var(--text-2)', bg:'var(--accent-dim)' };
    };
    const statusMeta = {
      completed:         { label:'Completed',       icon:'#i-check', L:{fg:'#065F46',bg:'#D1FAE5'}, D:{fg:'#6EE7B7',bg:'#14372A'} },
      clean:             { label:'No threats found',icon:'#i-shield',L:{fg:'#065F46',bg:'#D1FAE5'}, D:{fg:'#6EE7B7',bg:'#14372A'} },
      'not-neutralized': { label:'No action taken', icon:'#i-pause', L:{fg:'#92400E',bg:'#FEF3C7'}, D:{fg:'#FCD34D',bg:'#3F2D0C'} },
      aborted:           { label:'Stopped',         icon:'#i-close', L:{fg:'#4B5563',bg:'#E5E7EB'}, D:{fg:'#9CA3AF',bg:'#374151'} }
    };
    const statusInfo = (r) => {
      let key = String(r.status || '').toLowerCase();
      if (!key) {
        const t = r.threats || 0, n = r.neutralized || 0;
        if (t === 0) key = 'clean';
        else if (n >= t) key = 'completed';
        else key = 'not-neutralized';
      }
      const m = statusMeta[key] || statusMeta.completed;
      const p = isDark ? m.D : m.L;
      return { label: m.label, fg: p.fg, bg: p.bg, icon: m.icon || '#i-check' };
    };
    // Threats-count pill — red for >0, muted grey for 0. Dark theme dims both.
    const threatsPill = (n) => {
      if (n > 0) return isDark ? { fg:'#FCA5A5', bg:'#4A1414' } : { fg:'var(--danger-ink)', bg:'var(--danger-bg)' };
      return isDark ? { fg:'#9CA3AF', bg:'#1F2937' } : { fg:'var(--text-3)', bg:'var(--success-bg)' };
    };
    const shortPath = (p) => !p ? '(all fixed drives)' : (p.length <= 60 ? p : p.slice(0,28)+' … '+p.slice(-28));
    const shortTs = (iso) => { try { const d = new Date(iso); if (isNaN(d.getTime())) return iso||''; const p = (n) => (n<10?'0':'')+n; return d.getFullYear()+'-'+p(d.getMonth()+1)+'-'+p(d.getDate())+' '+p(d.getHours())+':'+p(d.getMinutes()); } catch(e) { return iso||''; } };
    // E1 — Protection Log rows (Time · Guard · Threat · Category · File · Action), newest first.
    const guardColorMap = { 'File Guard':'#2563EB','Script Guard':'#7C3AED','Process Guard':'#0891B2','Ransom Guard':'#DC2626','USB Guard':'#D97706','Deep Inspection':'#4B5563','System Guard':'#059669' };
    const _dq = (s.logQuery || '').toLowerCase();
    const detectionRows = (s.detections || [])
      .filter((d) => !_dq || ((d.guard||'')+' '+(d.threat||'')+' '+(d.category||'')+' '+(d.path||'')+' '+(d.action||'')).toLowerCase().indexOf(_dq) >= 0)
      .map((d, i) => {
        const gc = guardColorMap[d.guard] || '#6B7280';
        let when = d.ts || '';
        try { const dt = new Date(d.ts); if (!isNaN(dt.getTime())) when = dt.toLocaleString(); } catch (e) {}
        return {
          rowBg: (i % 2 === 1) ? (isDark ? '#141416' : '#FAFAFB') : 'transparent',
          time: when, guard: d.guard || '', guardFg: gc, guardBg: gc + '22',
          threat: d.threat || '', category: d.category || '', path: d.path || '', action: d.action || ''
        };
      });
    const scanHistoryRows = (s.scanHistory || []).map((r, i) => {
      const mi = modeInfo(r.mode);
      const si = statusInfo(r);
      const tp = threatsPill(r.threats || 0);
      // Zebra rows — even indices get a subtle tint so the eye tracks across the wide table.
      const zebraBg = (i % 2 === 1)
        ? (isDark ? '#141416' : '#FAFAFB')
        : 'transparent';
      return {
        rowBg: zebraBg,
        modeLabel: mi.label, modeFg: mi.fg, modeBg: mi.bg, modeIcon: mi.icon,
        statusLabel: si.label, statusFg: si.fg, statusBg: si.bg, statusIcon: si.icon,
        target: shortPath(r.target),
        files: (r.scanned || 0).toLocaleString(),
        threats: r.threats || 0,
        threatsColor: tp.fg,
        threatsBg:    tp.bg,
        neutralized: r.neutralized || 0,
        elapsed: r.elapsed || '',
        when: shortTs(r.ts),
        openReadonly: () => {
          const b = this.bridge();
          if (b && b.OpenScanWindowReadonly && r.id) Promise.resolve(b.OpenScanWindowReadonly(r.id)).catch(() => {});
        }
      };
    });

    // USB event rows for the Logs → USB tab (Option A: one row per event).
    // Data: { ts, drive, label, action, scanned, threats, findings, vaccinated }
    // action ∈ { inserted, scan-complete, vaccinated, unvaccinated, remediated, ... }
    const usbStatusMeta = {
      inserted:      { label:'Inserted',      icon:'#i-plug',    L:{fg:'#374151',bg:'#F3F4F6'}, D:{fg:'#D1D5DB',bg:'#374151'} },
      'scan-complete':{label:'Scan complete', icon:'#i-check',   L:{fg:'#065F46',bg:'#D1FAE5'}, D:{fg:'#6EE7B7',bg:'#14372A'} },
      'scan-threats':{ label:'Threats found', icon:'#i-alert',   L:{fg:'#92400E',bg:'#FEF3C7'}, D:{fg:'#FCD34D',bg:'#3F2D0C'} },
      vaccinated:    { label:'Vaccinated',    icon:'#i-syringe', L:{fg:'#1D4ED8',bg:'#DBEAFE'}, D:{fg:'#93C5FD',bg:'#1E3A5F'} },
      unvaccinated:  { label:'Unvaccinated',  icon:'#i-close',   L:{fg:'#4B5563',bg:'#E5E7EB'}, D:{fg:'#9CA3AF',bg:'#374151'} },
      remediated:    { label:'Remediated',    icon:'#i-lock',    L:{fg:'#B45309',bg:'#FEF3C7'}, D:{fg:'#FCD34D',bg:'#3F2D0C'} },
      ejected:       { label:'Ejected',       icon:'#i-close',   L:{fg:'#4B5563',bg:'#E5E7EB'}, D:{fg:'#9CA3AF',bg:'#374151'} }
    };
    const usbActionText = {
      inserted:      'Detected',
      'scan-complete':'Auto-scanned',
      'scan-threats':'Auto-scanned',
      vaccinated:    'Applied',
      unvaccinated:  'Removed',
      remediated:    'LNK/worm cleaned',
      ejected:       'Removed safely'
    };
    const usbEventRows = (s.usbEvents || []).map((u, i) => {
      // Elevate action "scan-complete" to "scan-threats" when threats > 0 so status pill turns amber.
      let key = String(u.action || 'inserted').toLowerCase();
      const threats = Number(u.threats) || 0;
      if (key === 'scan-complete' && threats > 0) key = 'scan-threats';
      const meta = usbStatusMeta[key] || usbStatusMeta.inserted;
      const p = isDark ? meta.D : meta.L;
      const tp = threatsPill(threats);
      const zebraBg = (i % 2 === 1) ? (isDark ? '#141416' : '#FAFAFB') : 'transparent';
      return {
        rowBg: zebraBg,
        when: shortTs(u.ts),
        drive: u.drive || '',
        label: u.label || '—',
        files: (Number(u.scanned) || 0).toLocaleString(),
        threats,
        threatsColor: tp.fg,
        threatsBg:    tp.bg,
        statusLabel: meta.label, statusFg: p.fg, statusBg: p.bg, statusIcon: meta.icon || '#i-info',
        action: usbActionText[key] || u.action || '',
        cursor: 'default', rowTitle: '',
        openReadonly: () => {}
      };
    });

    const sp = s.scanPolicy;
    const setPolicy = (patch, save) => this.setState((st) => ({ scanPolicy: { ...st.scanPolicy, ...patch } }), save);

    const dv = s.dashVariant;
    const scanS = s.scan;
    const lastScanDays = '2d';

    const accSectionsDef = [
      { key: 'engines', icon: '#i-cpu', title: 'Detection Engines', list: 'engines' },
      { key: 'heur', icon: '#i-flask', title: 'Heuristic Rules', list: 'heur' }
    ];
    // --- Advanced settings are edited per-mode (real-time vs on-demand) ---
    const isRt = s.advProfile === 'rt';
    const modeLabel = isRt ? 'real-time' : 'on-demand';
    const perf = isRt ? s.perfRt : s.perfOd;
    const perfKey = isRt ? 'perfRt' : 'perfOd';
    const perfSetter = isRt
      ? { item: 'SetStreamTimeout', global: 'SetGlobalTimeout', extract: 'SetExtractMB', mem: 'SetLowMemory' }
      : { item: 'SetOdStreamTimeout', global: 'SetOdGlobalTimeout', extract: 'SetOdExtractMB', mem: 'SetOdLowMemory' };

    const mkRows = (listKey) => s[listKey].map((r) => {
      const on = isRt ? r.rtOn : r.odOn;
      const setter = isRt ? r.rtSet : r.odSet;
      const onField = isRt ? 'rtOn' : 'odOn';
      return {
        title: r.title, sub: r.sub || false,
        ...this.mkToggle(on, () => {
          const nv = !on;
          this.setState((st) => ({ [listKey]: st[listKey].map((x) => x.id === r.id ? { ...x, [onField]: nv } : x) }), () => {
            const b = this.bridge();
            if (b && setter) Promise.resolve(b[setter](nv)).catch((e) => this.toast('Failed to save', String(e), 'red'));
          });
          this.addLog('event', `${r.title} ${nv ? 'enabled' : 'disabled'} (${modeLabel})`);
        })
      };
    });

    const perfFieldsDef = [
      ['Per-file timeout (sec, 0 = none)', 'item'],
      ['Total scan timeout (sec, 0 = none)', 'global'],
      ['Max extracted data (MB)', 'extract']
    ];
    // preset values: [per-file timeout, global timeout, max extract MB]
    // 2026-07-07: tightened extract caps — Fast 1000→200, Balanced 2048→512, Thorough 4096→1024.
    // Keeps TQKrn peaks low; users can still bump this manually for deep custom scans.
    const PRESETS = { fast: [20, 0, 200], balanced: [60, 0, 512], thorough: [120, 300, 1024] };

    // on-detection action choices for the current mode
    const curAction = isRt ? s.advActions.rt : s.advActions.od;
    const setAction = (val) => {
      this.setState((st) => ({ advActions: { ...st.advActions, [isRt ? 'rt' : 'od']: val } }), () => {
        const b = this.bridge(); if (b) Promise.resolve(b[isRt ? 'SetRealtimeAction' : 'SetOnDemandAction'](val)).catch(() => {});
      });
      this.addLog('event', `On-detection action set to "${val}" (${modeLabel})`);
    };
    const ACTION_HINTS = {
      quarantine: 'Threats are moved to a locked vault automatically. Safe and reversible.',
      delete: 'Threats are permanently deleted immediately. Cannot be undone.',
      ask: 'Threats are held in quarantine and flagged for you to Allow, Delete or Keep.',
      log: 'Threats are only recorded — the file is left in place. For monitoring.'
    };
    const setAmsi = (patch, bridgeFn, val) => this.setState((st) => ({ amsi: { ...st.amsi, ...patch } }), () => {
      const b = this.bridge();
      if (b && bridgeFn) Promise.resolve(b[bridgeFn](val)).catch((e) => this.toast('Failed to save', String(e), 'red'));
    });

    return {
      rootRef: this.rootRef,
      theme: s.theme,
      engineVersion: s.engineVersion,
      dbVersion: s.dbVersion,
      productVersion: s.productVersion || '1.0.0',
      // ── License card (About page) ─────────────────────────────────────────
      // Status pill colour + summary + activation input. Poll is driven by syncFromBridge();
      // the "Activate" button calls Bridge.ApplyLicense(); success/failure surfaces here.
      ...(() => {
        const lic = s.lic || {};
        const status = lic.status || 'Trial';
        const days = Number(lic.daysRemaining || 0);
        // Human-friendly duration: "1 year 12 days", "8 months", "5 days". Auto-collapses when
        // one component is zero. Called for both Trial + Licensed labels.
        const humanDays = (n) => {
          n = Math.max(0, Number(n) || 0);
          if (n === 0) return '0 days';
          const y = Math.floor(n / 365), rem = n - y * 365;
          const mo = Math.floor(rem / 30), d = rem - mo * 30;
          const parts = [];
          if (y  > 0) parts.push(y  + ' year'  + (y  === 1 ? '' : 's'));
          if (mo > 0) parts.push(mo + ' month' + (mo === 1 ? '' : 's'));
          // Show days only when total is under a year (avoids "1 year 3 months 12 days" clutter)
          if (y === 0 && d > 0) parts.push(d + ' day' + (d === 1 ? '' : 's'));
          return parts.slice(0, 2).join(' ');
        };
        const palette = {
          Licensed: ['var(--success)', 'var(--success-bg)', days > 0 ? ('Licensed · ' + humanDays(days) + ' left') : 'Licensed'],
          Trial:    ['var(--info)',    'var(--info-bg)',    days > 0 ? ('Trial · ' + humanDays(days) + ' left') : 'Trial'],
          Grace:    ['var(--amber)',   'var(--amber-bg)',   'Grace period · expires soon'],
          Expired:  ['var(--danger)',  'var(--danger-bg)',  'Expired']
        };
        const [fg, bg, label] = palette[status] || palette.Trial;
        const summary = lic.expiresUtc
          ? ('Expires ' + (lic.expiresUtc || '').replace('T', ' ').slice(0, 16) + ' UTC · ' + (lic.kind || 'trial'))
          : 'No license file';
        return {
          licPillFg: fg, licPillBg: bg, licPillLabel: label,
          licSummary: summary,
          licFingerprint: lic.fingerprint || '—',
          licKeyInput: s.licKeyInput || '',
          setLicKeyInput: (ev) => this.setState({ licKeyInput: (ev && ev.target && ev.target.value) || '' }),
          applyLicense: async () => {
            const b = this.bridge(); if (!b || !b.ApplyLicense) return;
            try {
              const err = await b.ApplyLicense(this.state.licKeyInput || '');
              if (err) {
                this.setState({ licError: err });
                this.toast('License', err, 'red');
              } else {
                this.setState({ licError: '', licKeyInput: '' });
                this.toast('License activated', 'Thanks — your license is now active.', 'green');
                await this.syncFromBridge();
              }
            } catch (e) { this.setState({ licError: String(e) }); }
          },
          licError: !!s.licError,
          licErrorText: s.licError || '',
          // Full-screen lockdown when license is Expired — nothing behind the overlay is clickable.
          // Bridge scan methods also refuse (belt-and-braces backstop). See LicenseManager.IsExpired.
          licLockdown: status === 'Expired'
        };
      })(),
      signaturesFmt: typeof s.signatures === 'number' ? this.fmt(s.signatures) : String(s.signatures),
      pageTitle: TITLES[s.page],
      navMain: NAVMAIN.map(navItem).filter(n => tqFeatureAllowed(n.feature)),
      navSys: NAVSYS.map(navItem).filter(n => tqFeatureAllowed(n.feature)),
      scaleBtns: [['compact', 'A−', 'Compact'], ['normal', 'A', 'Normal'], ['large', 'A+', 'Larger']].map(([k, label, title]) => ({
        label, title, select: () => this.setScale(k),
        bg: s.uiScale === k ? 'var(--accent)' : 'transparent',
        color: s.uiScale === k ? 'var(--on-accent)' : 'var(--text-3)'
      })),
      // Sidebar rail toggle — hamburger flips it. JS auto-collapses on first load when the window is
      // narrow (see componentDidMount). Manual toggle always wins after that.
      sideCls: s.sidebarRail ? 'rail' : '',
      toggleRail: () => this.setState((st) => {
        const next = !st.sidebarRail;
        try { localStorage.setItem('treeiq-rail', next ? '1' : '0'); } catch (e) {}
        return { sidebarRail: next };
      }),
      themeBtns: [['light', 'Light'], ['carbon', 'Dark']].map(([k, label]) => ({
        label, select: () => this.setTheme(k),
        bg: s.theme === k ? 'var(--accent)' : 'transparent',
        color: s.theme === k ? 'var(--on-accent)' : 'var(--text-3)'
      })),
      statusText: ok ? 'Protected' : 'At risk',
      statusFg: ok ? 'var(--success)' : 'var(--danger)',
      statusBg: ok ? 'var(--success-bg)' : 'var(--danger-bg)',

      pDashboard: s.page === 'dashboard', pScan: s.page === 'scan', pRealtime: s.page === 'realtime',
      pQuarantine: s.page === 'quarantine', pWhitelist: s.page === 'whitelist', pUpdates: s.page === 'updates', pLogs: s.page === 'logs',
      pTools: s.page === 'tools', pRansomGuard: s.page === 'ransomGuard', pRollback: s.page === 'rollback',
      pSystemGuard: s.page === 'systemGuard',
      sgView: this.buildSgView(),
      pSettings: s.page === 'settings', pAbout: s.page === 'about',

      // ---- Ransom Guard dashboard hero + tiles ----
      rgHero: (() => {
        const rg = s.rgSettings || {};
        const on = rg.enabled;
        const modeText = rg.mode === 'prohunt' ? 'Behavior + ProHunt' : 'Behavior only';
        const sensText = rg.sensitivity === 'high' ? 'High' : rg.sensitivity === 'low' ? 'Low' : 'Medium';
        // P4 · derive the 30 chart bars from state. Async load fires the first time the page
        // renders and updates s.rgActivity; until then bars show empty (0 threats).
        const activity = Array.isArray(s.rgActivity) && s.rgActivity.length === 30 ? s.rgActivity : new Array(30).fill(0);
        const chartMax = Math.max(1, activity.reduce((m, v) => v > m ? v : m, 1));
        const totalEvents = activity.reduce((a, b) => a + b, 0);
        const chartBars = activity.map((v, i) => {
          const pct = Math.max(6, Math.round((v / chartMax) * 100));   // min 6% so empty bars are still visible
          const daysAgo = 29 - i;
          const label = daysAgo === 0 ? 'today' : (daysAgo + ' day' + (daysAgo === 1 ? '' : 's') + ' ago');
          return {
            h: pct + '%',
            color: v > 0 ? 'var(--danger)' : 'var(--border)',
            title: v + ' event' + (v === 1 ? '' : 's') + ' · ' + label
          };
        });
        // Kick a one-shot load if we haven't yet.
        if (typeof this.__rgActivityLoading === 'undefined') {
          this.__rgActivityLoading = true;
          try {
            const b = this.bridge();
            if (b && b.GetRansomActivity30d) {
              Promise.resolve(b.GetRansomActivity30d()).then((raw) => {
                try { const arr = JSON.parse(raw); if (Array.isArray(arr) && arr.length === 30) this.setState({ rgActivity: arr }); } catch (e) {}
              }).catch(() => {});
            }
          } catch (e) {}
        }
        return {
          statusFg: on ? 'var(--success)' : 'var(--danger)',
          statusText: on ? 'Protected' : 'Off',
          modeText, sensText,
          canaryCount: rg.canaryCount || 0,
          snapCount: rg.snapCount || 0,
          snapMB: rg.snapMB || 0,
          attackCount: rg.attackCount || 0,
          chartBars,
          activityText: totalEvents === 0 ? 'No ransomware events in the last 30 days' : (totalEvents + ' event' + (totalEvents === 1 ? '' : 's') + ' blocked in the last 30 days'),
          testRun: () => { const b = this.bridge(); if (b && b.SimulateRansomware) { Promise.resolve(b.SimulateRansomware()).catch(() => {}); } else { this.toast('Test run', 'Simulation hook not wired yet.', 'blue'); } },
          viewLog: () => this.setState({ page: 'logs' }),
          viewCanaries: () => { const b = this.bridge(); if (b && b.OpenCanaryPathsWindow) { try { b.OpenCanaryPathsWindow(); } catch (e) { /* silent */ } } },
          openRestore: () => { this.setState({ page: 'rollback' }); this.loadRollback(); }
        };
      })(),

      // ---- Rollback / Restore window (#7) ----
      rbView: (() => {
        const files = Array.isArray(s.rbFiles) ? s.rbFiles : [];
        const sel = s.rbSel || {};
        const filter = s.rbFilter || 'enc';
        const bucketOf = (d) => {
          const l = String(d || '').toLowerCase();
          if (l.indexOf('\\documents') >= 0) return 'Documents';
          if (l.indexOf('\\desktop') >= 0) return 'Desktop';
          if (l.indexOf('\\pictures') >= 0) return 'Pictures';
          if (l.indexOf('\\downloads') >= 0) return 'Downloads';
          if (l.indexOf('\\videos') >= 0) return 'Videos';
          if (l.indexOf('\\music') >= 0) return 'Music';
          if (l.indexOf('\\onedrive') >= 0) return 'OneDrive';
          return 'Other';
        };
        const isEnc = (f) => f.st === 'modified' || f.st === 'deleted';
        const fmtSize = (n) => n >= 1048576 ? (n / 1048576).toFixed(1) + ' MB' : n >= 1024 ? Math.round(n / 1024) + ' KB' : n + ' B';
        const ago = (iso) => { try { const ms = Date.now() - new Date(iso).getTime(); const m = Math.floor(ms / 60000); if (m < 1) return 'just now'; if (m < 60) return m + ' min ago'; const h = Math.floor(m / 60); if (h < 24) return h + 'h ago'; return Math.floor(h / 24) + 'd ago'; } catch (e) { return ''; } };
        const total = files.length;
        const encCount = files.filter(isEnc).length;
        const usedMB = Math.round(files.reduce((a, f) => a + (f.s || 0), 0) / 1048576);
        let shown = files.slice();
        if (filter === 'enc') shown = shown.filter(isEnc);
        else if (filter !== 'all') shown = shown.filter((f) => bucketOf(f.d) === filter);
        const _q = (s.rbQuery || '').trim().toLowerCase();
        if (_q) shown = shown.filter((f) => String(f.n || '').toLowerCase().indexOf(_q) >= 0);
        shown.sort((a, b) => (isEnc(b) ? 1 : 0) - (isEnc(a) ? 1 : 0));
        const rows = shown.slice(0, 500).map((f) => {
          const on = !!sel[f.p]; const enc = isEnc(f);
          return {
            name: f.n, folder: bucketOf(f.d), sizeStr: fmtSize(f.s || 0), timeStr: ago(f.t),
            stLabel: f.st === 'deleted' ? 'Deleted' : f.st === 'modified' ? 'Encrypted' : 'Protected',
            stFg: enc ? 'var(--danger)' : 'var(--success)',
            rowBg: enc ? 'var(--danger-bg)' : 'transparent',
            boxBd: on ? 'var(--success)' : 'var(--border-strong)',
            boxBg: on ? 'var(--success)' : 'transparent',
            checkDisp: on ? 'block' : 'none',
            toggle: () => { const ns = { ...(this.state.rbSel || {}) }; if (ns[f.p]) delete ns[f.p]; else ns[f.p] = true; this.setState({ rbSel: ns }); }
          };
        });
        const chipDef = [['enc', 'Needs restore'], ['all', 'All'], ['Documents', 'Documents'], ['Desktop', 'Desktop'], ['Pictures', 'Pictures'], ['Downloads', 'Downloads']];
        const chips = chipDef.map(([k, label]) => ({
          label, bg: filter === k ? 'var(--success)' : 'var(--card)', fg: filter === k ? '#fff' : 'var(--text-2)',
          bd: filter === k ? 'var(--success)' : 'var(--border)', select: () => this.setState({ rbFilter: k })
        }));
        const selPaths = Object.keys(sel);
        const selCount = selPaths.length;
        const selBytes = files.filter((f) => sel[f.p]).reduce((a, f) => a + (f.s || 0), 0);
        const _visForAll = shown.slice(0, 500);
        const _allOn = _visForAll.length > 0 && _visForAll.every((f) => sel[f.p]);
        return {
          queryVal: s.rbQuery || '',
          setQuery: (ev) => this.setState({ rbQuery: (ev && ev.target && ev.target.value) || '' }),
          allBd: _allOn ? 'var(--success)' : 'var(--border-strong)',
          allBg: _allOn ? 'var(--success)' : 'transparent',
          allChk: _allOn ? 'block' : 'none',
          total, encCount, usedMB, empty: rows.length === 0,
          emptyMsg: !s.rbLoaded ? 'Loading backups…' : (total === 0 ? 'No files are backed up yet — snapshots build up as you use your PC.' : 'No files match this filter.'),
          encFg: encCount > 0 ? 'var(--danger)' : 'var(--text-1)',
          chips, rows,
          selText: selCount === 0 ? 'Select files to restore' : (selCount + ' selected · ' + fmtSize(selBytes)),
          restoreLabel: s.rbBusy ? 'Restoring…' : 'Restore selected',
          restoreOpacity: (s.rbBusy || selCount === 0) ? '0.55' : '1',
          msgVisible: !!s.rbMsg, msg: s.rbMsg, msgFg: s.rbMsgOk ? 'var(--success)' : 'var(--danger)',
          reload: () => this.loadRollback(),
          toggleAll: () => { const vis = shown.slice(0, 500); const allOn = vis.length > 0 && vis.every((f) => sel[f.p]); const ns = { ...(this.state.rbSel || {}) }; vis.forEach((f) => { if (allOn) delete ns[f.p]; else ns[f.p] = true; }); this.setState({ rbSel: ns }); },
          restoreSel: () => { if (s.rbBusy || selCount === 0) return; this.doRollbackRestore(selPaths); },
          restoreAll: () => { if (s.rbBusy) return; this.doRollbackRestore(files.filter(isEnc).map((f) => f.p)); }
        };
      })(),

      // Tools tiles — each will open the target section in its own out-of-process window in Phases
      // 2-4. For Phase 1 (visual only) the Open button navigates in-place, matching the pre-Tools
      // behavior so nothing regresses while the extraction is landing.
      toolTiles: [
        { key:'logs',
          // Slate/steel — replaces the earlier purple. User has flagged purple twice, and this
          // slate hue also reads better in dark mode against the other tool tiles.
          icon:'#i-logs', accent:'#334155', accentBg:'#E2E8F0',
          name:'Logs',
          desc:'Activity across all guards · scan history · USB events',
          badge: (s.logs || []).length ? String((s.logs || []).length) : '',
          // Phase 2 — spawn TQAM.exe --tool=logs (LogsWindow.cs). Falls back to in-place navigation
          // only if the host object is unavailable (e.g. running outside WebView2).
          open: () => {
            try { window.chrome.webview.hostObjects.treeiq.OpenLogsWindow(); }
            catch (e) { this.setState({ page: 'logs' }); }
          }
        },
        { key:'quarantine',
          icon:'#i-lock', accent:'#B45309', accentBg:'#FEF3C7',
          name:'Quarantine',
          desc:'Threats moved to the encrypted vault · restore or delete',
          badge: (s.quarantine || []).length ? String((s.quarantine || []).length) : '',
          // Phase 3 — spawn TQAM.exe --tool=quarantine (QuarantineWindow.cs).
          open: () => {
            try { window.chrome.webview.hostObjects.treeiq.OpenQuarantineWindow(); }
            catch (e) { this.setState({ page: 'quarantine' }); }
          }
        },
        { key:'whitelist',
          icon:'#i-key', accent:'#065F46', accentBg:'#D1FAE5',
          name:'Whitelist',
          desc:'Trusted files, folders, processes and publishers',
          badge: (s.whitelist || []).length ? String((s.whitelist || []).length) : '',
          // Phase 4 — spawn TQAM.exe --tool=whitelist (WhitelistWindow.cs).
          open: () => {
            try { window.chrome.webview.hostObjects.treeiq.OpenWhitelistWindow(); }
            catch (e) { this.setState({ page: 'whitelist' }); }
          }
        },
        { key:'restore',
          icon:'#i-update', accent:'#0F766E', accentBg:'#CCFBF1',
          name:'Restore files',
          desc:'Ransom Guard backups · browse and roll back your files',
          badge: '',
          // Open in its own window (TQAM.exe --tool=restore), matching Logs/Quarantine/Whitelist.
          // Falls back to in-place navigation if the host object is unavailable.
          open: () => {
            try { window.chrome.webview.hostObjects.treeiq.OpenRestoreWindow(); }
            catch (e) { this.setState({ page: 'rollback' }); this.loadRollback(); }
          }
        }
      ],

      // ---- Dashboard v2 (ESET-style) ----
      // Solid state color for the icon square (was the left stripe). Everything visual on the
      // hero now takes its cue from this one hue.
      heroStripe: ok ? '#16A34A' : '#B42318',
      // Hero color follows the Triple-Shield state: green=all on, amber=1-2 off, red=all off.
      heroBg: _guardsOff === 0 ? 'var(--success-bg)' : _guardsOff === 3 ? 'var(--danger-bg)' : 'var(--warning-bg)',
      heroTintBg:     _guardsOff === 0 ? 'var(--success-bg)'   : _guardsOff === 3 ? 'var(--danger-bg)'   : 'var(--warning-bg)',
      heroTintBorder: _guardsOff === 0 ? 'var(--success-line)' : _guardsOff === 3 ? 'var(--danger-line)' : 'var(--warning-line)',
      heroTitle: _guardsOff === 0 ? 'You are protected'
               : _guardsOff === 3 ? "You're unprotected"
               : 'Not fully protected',
      heroSub: _guardsOff === 0
        ? 'All 3 guards active · signatures up to date' + (lastScanDays ? ' · last scan ' + lastScanDays : '')
        : _guardsOff === 3
          ? 'All 3 guards are OFF — enable them to stay protected.'
          : _guardsOff + ' of 3 guards OFF — see cards below to re-enable.',
      startQuick: () => this.startScan('quick'),
      startFull: () => this.startScan('full'),

      kpiThreats: String(s.blockedToday != null ? s.blockedToday : 0),
      kpiThreatsSub: (s.blockedToday === 1 ? '1 threat today' : (s.blockedToday || 0) + ' today') + ' · all quarantined',
      // Files scanned KPI — sum "scan complete: N files" + "[proc] scanned N process(es)" from actual log entries.
      // Bridge returns last 300 log lines → this reflects real scan activity (unlike s.scanned which only tracks
      // on-demand scan progress the UI directly witnessed).
      kpiScanned: this.fmt((function () {
        let files = 0;
        (s.logs || []).forEach(function (l) {
          const m = String(l.msg || '').match(/scan complete:\s+([\d,]+)\s+files?|scanned\s+([\d,]+)\s+file/i);
          if (m) files += parseInt(String(m[1] || m[2] || '0').replace(/,/g, ''), 10) || 0;
        });
        return files || (s.scanned || 0);
      })()),
      kpiScannedSub: (function () {
        let procs = 0, runs = 0;
        (s.logs || []).forEach(function (l) {
          const msg = String(l.msg || '');
          const pm = msg.match(/scanned\s+([\d,]+)\s+process/i);
          if (pm) { procs += parseInt(pm[1].replace(/,/g, ''), 10) || 0; runs++; }
          else if (/scan complete/i.test(msg)) runs++;
        });
        const bits = ['Session'];
        if (runs > 0) bits.push(runs + (runs === 1 ? ' scan' : ' scans'));
        if (procs > 0) bits.push(this.fmt(procs) + ' processes');
        return bits.join(' · ');
      }).call(this),

      // Component rows on the Protection page. OFF rows get a subtle red-tinted background + red
       // left strip + red icon + "Turned OFF" text + one-click Enable button — same visual
       // language as the guard cards up top and the log-row danger tint. Signature-database row
       // dropped (real info lives on the Updates page).
      compRows: (function () {
        const isDark = (s.theme === 'carbon');
        const OFF_ROW_BG = isDark ? 'rgba(248,113,113,0.10)' : 'rgba(220,38,38,0.06)';
        const mkRow = (icon, name, desc, isOn, enableFn) => {
          const off = !isOn;
          return {
            icon, name, desc,
            state: off ? 'Turned OFF' : 'On',
            stateColor: off ? '#B42318' : '#15803D',
            stateIcon:  off ? '#i-alert' : '#i-check',
            rowBg:      off ? OFF_ROW_BG : 'transparent',
            stripe:     off ? '#DC2626' : 'transparent',
            iconBg:     off ? 'var(--danger-bg)' : 'var(--icon-bg)',
            iconFg:     off ? 'var(--danger)'    : 'var(--text-2)',
            nameColor:  off ? 'var(--danger-ink)' : 'var(--text-1)',
            descColor:  off ? 'var(--danger-ink)' : 'var(--text-3)',
            showEnable: off,
            enable: enableFn
          };
        };
        return [
          mkRow('#i-shield', 'File Guard', 'Files, downloads and USB drives',
            !!s.simpleToggles.rt,
            () => this.toggleRt()),
          mkRow('#i-cpu', 'Process Guard', 'Watches running processes and memory',
            !!s.simpleToggles.procguard,
            () => {
              this.setState((st) => ({ simpleToggles: { ...st.simpleToggles, procguard: true } }), () => {
                const b = this.bridge();
                if (b && b.SetProcessGuard) Promise.resolve(b.SetProcessGuard(true)).catch(() => {});
              });
            }),
          mkRow('#i-scan', 'Script Guard', 'PowerShell, Office macros, VBS',
            !!(s.amsi && s.amsi.enabled),
            () => {
              this.setState((st) => ({ amsi: { ...st.amsi, enabled: true } }), () => {
                const b = this.bridge();
                if (b && b.SetAmsiEnabled) Promise.resolve(b.SetAmsiEnabled(true)).catch(() => {});
              });
            })
        ];
      })(),
      sysTiles: this.buildSysTiles(s.sysinfo || {}),

      // New Scan v2 tiles — 2x2 grid (Quick lives in the hero card above).
      scanTiles: [
        { icon: '#i-disk', title: 'Full Scan', sub: 'Every file on every fixed drive · ~30–60 min', cta: 'Start', run: () => this.startScan('full') },
        { icon: '#i-folder', title: 'Custom Scan', sub: 'Pick a specific folder, drive, or file', cta: 'Choose', run: () => this.startScan('custom') },
        { icon: '#i-bolt', title: 'Autoruns Scan', sub: 'Every program set to auto-start · Run keys, services, tasks', cta: 'Start', run: () => this.startScan('autoruns') },
        { icon: '#i-usb', title: 'Removable Media Scan', sub: 'USB drives, external HDDs, memory cards', cta: 'Scan', run: () => this.startRemovableScan() },
        // 2026-07-09 — Boot Scan tile. Restarts the PC and runs a scan at first-logon before the
        // user's session is fully loaded, so locked malware files can be caught + removed. Shows
        // a fullscreen "console" during the scan. Confirmation dialog before rebooting.
        { icon: '#i-refresh', title: 'Boot Scan', sub: 'Restart & scan startup + boot sectors · ~3-5 min', cta: 'Restart & scan', run: () => this.startBootScan() }
      ],
      startMemory: () => this.startScan('memory'),
      // Force Full Scan — bypasses the scan-cache. Sends the ignoreCache flag through Bridge → ScanServer.
      startForceFull: () => { const b = this.bridge(); if (b && b.OpenScanWindowForce) Promise.resolve(b.OpenScanWindowForce('full', '')).catch((e) => this.toast('Force scan failed to start', String(e), 'red')); else this.toast('Force scan unavailable', 'The service (TQKrn) needs to be running.', 'red'); },
      // (Boot Scan handler lives at the COMPONENT level as `startBootScan()` — see near
      // `startRemovableScan`. The tile array below uses `this.startBootScan()`.)
      // Recent scans — parsed from log entries with type 'scan'. Last 3, newest first.
      // Section is only shown when at least one parseable row exists (empty log = clean UI).
      recentScans: this.buildRecentScans(s.logs || []),
      hasRecentScans: this.buildRecentScans(s.logs || []).length > 0,
      scanVisible: scanS.visible,
      scanRunning: scanS.running,
      scanIconAnim: scanS.running ? 'tqPulse 1.2s ease infinite' : 'none',
      scanTitle: scanS.mode + (scanS.running ? ' running…' : ' complete'),
      scanFile: scanS.file,
      scanPctW: Math.min(Math.round(scanS.pct), 100) + '%',
      scanPctLabel: Math.min(Math.round(scanS.pct), 100) + '%',
      scanCount: this.fmt(scanS.count),
      scanThreats: String(scanS.threats),
      stopScan: () => this.stopScan(),
      resultsVisible: !!scanS.results,
      hasResults: !!(scanS.results && scanS.results.length),
      noResults: !!(scanS.results && !scanS.results.length),
      scanAllClear: !!(scanS.results && scanS.results.length && !scanS.results.some((r) => r.actionable && !r.done) && scanS.results.some((r) => r.done)),
      scanResults: (scanS.results || []).map((r) => {
        const choiceCol = r.choice === 'delete'
          ? { fg: 'var(--danger)', bg: 'var(--danger-bg)', bd: 'var(--danger)' }
          : r.choice === 'ignore'
            ? { fg: 'var(--text-3)', bg: 'var(--surface)', bd: 'var(--border-strong)' }
            : { fg: 'var(--accent)', bg: 'var(--accent-dim)', bd: 'var(--accent)' };
        return {
          ...r, catFg: this.catColors(r.cat).fg, catBg: this.catColors(r.cat).bg, ...this.actionBadge(r.action),
          pathShort: this.shortPath(r.path), pathFull: r.path || '',
          notActionable: !r.actionable,
          choiceFg: choiceCol.fg, choiceBg: choiceCol.bg, choiceBd: choiceCol.bd,
          onChoice: (ev) => this.setScanChoice(r.id, ev.target.value),
          onSel: () => this.toggleScanSel(r.id)
        };
      }),
      scanHasActionable: (scanS.results || []).some((r) => r.actionable && !r.done),
      scanActionableCount: (scanS.results || []).filter((r) => r.actionable && !r.done).length,
      scanSelCount: (scanS.results || []).filter((r) => r.sel && r.actionable && !r.done).length,
      scanAllSel: (() => { const a = (scanS.results || []).filter((r) => r.actionable && !r.done); return a.length > 0 && a.every((r) => r.sel); })(),
      scanToggleAll: (ev) => this.toggleScanSelAll(ev.target.checked),
      scanBulk: s.scanBulk,
      onScanBulk: (ev) => this.setState({ scanBulk: ev.target.value }),
      applyScanToSelected: () => this.applyScanToSelected(),
      applyScanActions: () => this.neutralizeThreats(),
      scanApplyLabel: 'Neutralize threats (' + (scanS.results || []).filter((r) => r.actionable && !r.done && r.sel).length + ')',

      rtTitle: 'Real-time protection is ' + (s.rt ? 'on' : 'off'),
      rtSub: s.rt
        ? 'Watching ' + (s.drives.filter((d) => d.on).length + 4) + ' locations · files, downloads and USB drives'
        : 'Turn on real-time protection to keep watching for threats.',
      rtToggle: (() => {
        const base = this.mkToggle(s.rt, () => this.toggleRt());
        // add a 22px-knob translation for the bigger toggle on the Protection hero.
        return { ...base, knob22: s.rt ? '24px' : '0' };
      })(),
      rtWarn: s.rtWarn && !s.rt,
      // Hero state tint (A+B state pattern) — mirrors the Dashboard hero.
      rtHeroStripe: s.rt ? '#3EBB6A' : '#B42318',
      rtHeroTintBg: s.rt ? 'var(--success-bg)' : 'var(--danger-bg)',
      rtHeroTintBorder: s.rt ? 'var(--success-line)' : 'var(--danger-line)',
      // Sparkline cards — 3 real-metric sparklines. Each pulls its 60-sample history from
      // state.sparkHistory[key], derives smooth line + area paths, and shows the latest value.
      sparks: this.buildSparks(s),
      // Last USB event chip — reads bridge.GetLastUsbEvent (compact JSON) and formats for display.
      // Auto-hides when there are no events yet or the service call fails.
      lastUsbEvent: this.buildLastUsbEvent(s),
      // GlassWire v2 chart — smooth bezier + category-icon pins + range switch + click details.
      ...this.buildGlassWire(s),
      gwRangeBtns: [['12h', '12h'], ['24h', '24h'], ['7d', '7d']].map(([k, label]) => ({
        label, select: () => this.setState({ gwRange: k, gwDetail: null }),
        bg: s.gwRange === k ? 'var(--card)' : 'transparent',
        color: s.gwRange === k ? 'var(--text-1)' : 'var(--text-3)',
        shadow: s.gwRange === k ? 'var(--shadow-sm)' : 'none'
      })),
      gwHasDetail: s.gwDetail !== null,
      gwNoDetail: s.gwDetail === null,
      gwDetailX: s.gwDetail ? Math.max(5, Math.min(1200 - 305, s.gwDetail.x - 150)) : 0,
      gwViewInQuar: () => this.setState({ page: 'quarantine', gwDetail: null, gwCatFilter: null }),
      gwDetailTime: s.gwDetail ? s.gwDetail.time : '',
      gwDetailCount: s.gwDetail ? s.gwDetail.count : 0,
      gwDetailHeadline: s.gwDetail ? (s.gwDetail.time + ' · ' + s.gwDetail.count + ' ' + (s.gwDetail.count === 1 ? 'threat' : 'threats') + ' blocked') : '',
      // Detail view: filter by category → group by family → limit to top 10 groups.
      // A single Worm burst with 45 identical detections becomes one collapsed row instead of 45.
      gwDetailItems: (function () {
        if (!s.gwDetail) return [];
        const filter = s.gwCatFilter;
        const items = filter ? s.gwDetail.items.filter(function (it) { return it.cat === filter; }) : s.gwDetail.items;
        const gmap = {};
        items.forEach(function (it) {
          const key = it.family || it.name;
          if (!gmap[key]) gmap[key] = { family: key, catLabel: it.catLabel, catHref: it.catHref, files: [], acts: {} };
          gmap[key].files.push({ name: it.name, path: it.path, act: it.act });
          gmap[key].acts[it.act] = (gmap[key].acts[it.act] || 0) + 1;
        });
        const groups = Object.keys(gmap).map(function (k) { return gmap[k]; })
          .sort(function (a, b) { return b.files.length - a.files.length; });
        return groups.slice(0, 10).map(function (g) {
          const nFiles = g.files.length;
          const preview = g.files.slice(0, 3).map(function (f) { return f.name; }).join(' · ');
          const dominantAct = Object.keys(g.acts).sort(function (a, b) { return g.acts[b] - g.acts[a]; })[0] || 'Blocked';
          return {
            family: g.family,
            catLabel: g.catLabel,
            catHref: g.catHref,
            count: nFiles,
            countLabel: nFiles + ' file' + (nFiles === 1 ? '' : 's'),
            preview: preview,
            hasMore: nFiles > 3,
            moreCount: Math.max(0, nFiles - 3),
            moreLabel: nFiles > 3 ? ' · +' + (nFiles - 3) + ' more' : '',
            act: dominantAct,
            firstPath: g.files[0].path
          };
        });
      })(),
      // "See all N in Quarantine →" footer + hidden-groups counter.
      gwHasHidden: (function () {
        if (!s.gwDetail) return false;
        const filter = s.gwCatFilter;
        const items = filter ? s.gwDetail.items.filter(function (it) { return it.cat === filter; }) : s.gwDetail.items;
        const fams = {};
        items.forEach(function (it) { fams[it.family || it.name] = true; });
        return Object.keys(fams).length > 10;
      })(),
      gwHiddenLabel: (function () {
        if (!s.gwDetail) return '';
        const filter = s.gwCatFilter;
        const items = filter ? s.gwDetail.items.filter(function (it) { return it.cat === filter; }) : s.gwDetail.items;
        return 'See all ' + items.length + ' in Quarantine →';
      })(),
      // Filter chips: [All 45] [Worm 42] [Trojan 3] — click to filter the list.
      gwFilterChips: (function () {
        if (!s.gwDetail) return [];
        const items = s.gwDetail.items;
        const catCounts = {};
        items.forEach(function (it) {
          if (!catCounts[it.cat]) catCounts[it.cat] = { key: it.cat, label: it.catLabel, count: 0 };
          catCounts[it.cat].count++;
        });
        const cats = Object.keys(catCounts).map(function (k) { return catCounts[k]; })
          .sort(function (a, b) { return b.count - a.count; });
        const chips = [{
          key: 'all',
          label: 'All ' + items.length,
          isActive: !s.gwCatFilter,
          bg: !s.gwCatFilter ? '#3EBB6A' : 'transparent',
          color: !s.gwCatFilter ? '#fff' : 'var(--text-2)',
          border: !s.gwCatFilter ? '#3EBB6A' : 'var(--border-strong)',
          select: () => this.setState({ gwCatFilter: null })
        }];
        cats.forEach((c) => {
          const active = s.gwCatFilter === c.key;
          chips.push({
            key: c.key,
            label: c.label + ' ' + c.count,
            isActive: active,
            bg: active ? '#DC2626' : 'transparent',
            color: active ? '#fff' : 'var(--text-2)',
            border: active ? '#DC2626' : 'var(--border-strong)',
            select: () => this.setState({ gwCatFilter: c.key })
          });
        });
        return chips;
      }).call(this),
      gwHasFilterChips: s.gwDetail && s.gwDetail.items.length > 3,
      gwCloseDetail: () => this.setState({ gwDetail: null, gwCatFilter: null }),
      drives: s.drives.map((d, i) => ({
        name: d.n, desc: `${d.t} · ${d.g} GB`, on: d.on,
        toggle: () => {
          const on = !this.state.drives[i].on;
          this.setState((st) => ({ drives: st.drives.map((x, j) => j === i ? { ...x, on } : x) }), () => {
            const b = this.bridge();
            if (b) { Promise.resolve(b.SetWatchFolders(JSON.stringify(this.state.drives.filter((x) => x.on).map((x) => x.n)))).catch((e) => this.toast('Failed to save watched drives', String(e), 'red')); }
          });
          this.addLog('event', (on ? 'Watched drive added: ' : 'Watched drive removed: ') + d.n);
        }
      })),
      exclusions: s.exclusions.map((e, i) => ({
        label: e,
        remove: () => {
          const b = this.bridge();
          if (b) { Promise.resolve(b.RemoveExclusion(e)).catch(() => {}); }
          this.setState((st) => ({ exclusions: st.exclusions.filter((_, j) => j !== i) }));
        }
      })),
      exclValue: s.exclValue,
      onExclInput: (ev) => this.setState({ exclValue: ev.target.value }),
      addExclusion: () => {
        const v = s.exclValue.trim();
        if (!v) return;
        const b = this.bridge();
        if (b) { Promise.resolve(b.AddExclusion(v)).catch(() => {}); }
        this.setState((st) => ({ exclusions: [...st.exclusions, v], exclValue: '' }));
        this.addLog('event', 'Exclusion added: ' + v);
      },

      whitelistRows: s.whitelist.map((w) => {
        const meta = WLTYPE[w.type] || WLTYPE.file;
        return {
          icon: meta.icon, tint: meta.tint, typeLabel: meta.label, scope: meta.scope, path: w.path, added: w.added || '',
          remove: () => {
            const b = this.bridge();
            if (b) { Promise.resolve(b.RemoveWhitelist(w.path)).catch(() => {}); }
            this.setState((st) => ({ whitelist: st.whitelist.filter((x) => x.path !== w.path) }));
            this.addLog('event', 'Whitelist removed: ' + w.path);
          }
        };
      }),
      wlEmpty: s.whitelist.length === 0,
      wlAddFile: () => this.wlPick('file'),
      wlAddFolder: () => this.wlPick('folder'),
      wlAddProcess: () => this.wlOpenProcess(),
      wlAddDrive: () => this.setState({ wlModal: 'drive', wlSel: [] }),
      wlModalOpen: s.wlModal != null,
      wlModalTitle: s.wlModal === 'process' ? 'Select running processes' : s.wlModal === 'drive' ? 'Select drives' : '',
      wlModalRows: this.wlModalRows(),
      wlSelCount: (s.wlSel || []).length + ' selected',
      wlAddSelected: () => this.wlAddSelected(),
      wlModalStop: (ev) => { try { ev.stopPropagation(); } catch (e) {} },
      wlModalClose: () => this.setState({ wlModal: null, wlSel: [] }),
      // Upgrade confirmation modal bindings (Item 1 — replaces native confirm()).
      ucOpen: s.upgradeConfirm != null,
      ucVersion: s.upgradeConfirm ? (s.upgradeConfirm.version || '') : '',
      ucHasNotes: !!(s.upgradeConfirm && s.upgradeConfirm.notes),
      ucNotes: s.upgradeConfirm ? (s.upgradeConfirm.notes || '') : '',
      ucStop: (ev) => { try { ev.stopPropagation(); } catch (e) {} },
      ucCancel: () => this.setState({ upgradeConfirm: null }),
      ucProceed: () => this.upgradeConfirmProceed(),

      rtRecent, rtHasRecent: rtRecent.length > 0, rtNoRecent: rtRecent.length === 0,

      quarHeading: s.quarantine.length + ' file' + (s.quarantine.length !== 1 ? 's' : '') + ' locked here',
      quarHeroBg: s.quarantine.length === 0 ? 'var(--success-bg)' : 'var(--warning-bg)',
      quarHeroBorder: s.quarantine.length === 0 ? 'var(--success-line)' : 'var(--warning-line)',
      quarHeroIconBg: s.quarantine.length === 0 ? 'var(--success)' : 'var(--warning)',
      quarIsEmpty: s.quarantine.length === 0,
      quarNotEmpty: s.quarantine.length !== 0,
      wlHeroSub: (s.whitelist.length === 0 ? 'No trusted items yet' : s.whitelist.length + ' trusted ' + (s.whitelist.length === 1 ? 'item' : 'items')) + ' — skipped by real-time protection and all scans.',
      wlIsEmpty: s.whitelist.length === 0,
      wlNotEmpty: s.whitelist.length !== 0,
      logsIsEmpty: (s.logs || []).length === 0,
      logsNotEmpty: (s.logs || []).length !== 0,
      // Filtered view — search + hide vault duplicates. Every row shows Restore + Delete now (no more review state).
      quarView: (() => {
        const q = (s.quarSearch || '').toLowerCase();
        return s.quarantine.filter((r) => {
          if (s.quarHideDups && r.dup) return false;
          if (q && !((r.file || '').toLowerCase().includes(q) || (r.det || '').toLowerCase().includes(q) || (r.path || '').toLowerCase().includes(q))) return false;
          return true;
        }).map((r) => ({
          ...r, catFg: this.catColors(r.cat).fg, catBg: this.catColors(r.cat).bg,
          rowBg: 'transparent',
          fileColor: r.dup ? 'var(--text-3)' : 'var(--text-1)',
          allow: () => this.openRestoreModal([r]),
          del: () => this.quarDecide(r.id, 'delete', r.file),
          toggle: () => this.setState((st) => ({ quarantine: st.quarantine.map((x) => x.id === r.id ? { ...x, sel: !x.sel } : x) }))
        }));
      })(),
      quarHasDups: s.quarantine.some((q) => q.dup),
      quarDupText: s.quarantine.filter((q) => q.dup).length + ' vault-duplicate record' + (s.quarantine.filter((q) => q.dup).length !== 1 ? 's' : ''),
      quarHasItems: s.quarantine.length > 0,
      quarEmpty: s.quarantine.length === 0,
      // Sticky selection bar
      quarSelN: s.quarantine.filter((q) => q.sel).length,
      quarHasSel: s.quarantine.some((q) => q.sel),
      quarSelCount: s.quarantine.filter((q) => q.sel).length + ' selected',
      quarClearSel: () => this.setState((st) => ({ quarantine: st.quarantine.map((q) => ({ ...q, sel: false })) })),
      quarAllSel: s.quarantine.length > 0 && s.quarantine.every((q) => q.sel),
      quarToggleAll: (ev) => {
        const c = ev.target.checked;
        this.setState((st) => ({ quarantine: st.quarantine.map((q) => ({ ...q, sel: c })) }));
      },
      quarRestore: () => {
        const sel = s.quarantine.filter((q) => q.sel);
        if (!sel.length) return;
        this.openRestoreModal(sel);
      },
      quarDelete: () => this.quarAction('delete'),
      quarSearch: (ev) => this.setState({ quarSearch: ev.target.value }),
      quarHideDups: !!s.quarHideDups,
      quarToggleHideDups: (ev) => this.setState({ quarHideDups: ev.target.checked }),
      quarClearDups: () => this.quarClearDups(),
      // ---- Restore modal bindings ----
      rmOpen: !!s.restoreModal,
      rmTitle: s.restoreModal ? ((s.restoreModal.ids || []).length > 1 ? 'Restore ' + s.restoreModal.ids.length + ' files' : 'Restore from quarantine') : '',
      rmSubtitle: s.restoreModal ? (s.restoreModal.subtitleTag || '') : '',
      rmOrigPath: s.restoreModal ? (s.restoreModal.origPath || '(preserved per file)') : '',
      rmFolderPath: s.restoreModal ? (s.restoreModal.folder || 'Not chosen') : '',
      rmDestOrig: !!(s.restoreModal && !s.restoreModal.folder),
      rmDestFolder: !!(s.restoreModal && s.restoreModal.folder),
      rmOrigBorder: (s.restoreModal && !s.restoreModal.folder) ? 'var(--accent)' : 'var(--border)',
      rmOrigBg: (s.restoreModal && !s.restoreModal.folder) ? 'var(--accent-soft)' : 'transparent',
      rmFolderBorder: (s.restoreModal && s.restoreModal.folder) ? 'var(--accent)' : 'var(--border)',
      rmFolderBg: (s.restoreModal && s.restoreModal.folder) ? 'var(--accent-soft)' : 'transparent',
      rmWlFile: !!(s.restoreModal && s.restoreModal.wlFile),
      rmWlProc: !!(s.restoreModal && s.restoreModal.wlProc),
      rmReason: s.restoreModal ? (s.restoreModal.reason || '') : '',
      // Disabled when process-whitelist is checked but no reason typed, or when folder mode has no folder yet.
      rmDisabled: !!(s.restoreModal && ((s.restoreModal.wlProc && !(s.restoreModal.reason || '').trim()) || (s.restoreModal.destMode === 'folder' && !s.restoreModal.folder))),
      rmOpacity: (s.restoreModal && ((s.restoreModal.wlProc && !(s.restoreModal.reason || '').trim()) || (s.restoreModal.destMode === 'folder' && !s.restoreModal.folder))) ? '.5' : '1',
      rmClose: () => this.setState({ restoreModal: null }),
      rmStop: (ev) => { try { ev.stopPropagation(); } catch (e) {} },
      rmPickOrig: () => this.setState((st) => ({ restoreModal: st.restoreModal ? { ...st.restoreModal, destMode: 'orig', folder: '' } : st.restoreModal })),
      rmPickFolder: () => this.setState((st) => ({ restoreModal: st.restoreModal ? { ...st.restoreModal, destMode: 'folder' } : st.restoreModal })),
      rmBrowse: (ev) => { try { ev.stopPropagation(); } catch (e) {} this.restoreModalBrowse(); },
      rmToggleWlFile: (ev) => this.setState((st) => ({ restoreModal: st.restoreModal ? { ...st.restoreModal, wlFile: ev.target.checked } : st.restoreModal })),
      rmToggleWlProc: (ev) => this.setState((st) => ({ restoreModal: st.restoreModal ? { ...st.restoreModal, wlProc: ev.target.checked } : st.restoreModal })),
      rmReasonInput: (ev) => this.setState((st) => ({ restoreModal: st.restoreModal ? { ...st.restoreModal, reason: ev.target.value } : st.restoreModal })),
      rmSubmit: () => this.restoreModalSubmit(),

      checkUpdate: () => this.checkUpdate(),
      analyzeLogs: () => this.analyzeLogs(),
      analyzeBtnLabel: s.analyzing ? 'Analyzing…' : 'Analyze logs',
      analyzeBtnOpacity: s.analyzing ? '.6' : '1',
      analyzeMsgVisible: !!s.analyzeMsg,
      analyzeMsg: s.analyzeMsg || '',
      analyzeMsgColor: s.analyzeOk === false ? 'var(--danger, #b91c1c)' : (s.analyzeOk === true ? 'var(--success, #16a34a)' : 'var(--text-3)'),
      upgradeNow: () => this.upgradeNow(),
      upgradeBtnLabel: s.upgrading ? 'Upgrading…' : 'Check for upgrade',
      upgradeBtnOpacity: s.upgrading ? '.6' : '1',
      upgradeMsgVisible: !!s.upgradeMsg,
      upgradeMsg: s.upgradeMsg || '',
      upgradeMsgColor: s.upgradeOk === false ? 'var(--danger, #b91c1c)' : (s.upgradeOk === true ? 'var(--success, #16a34a)' : 'var(--text-3)'),
      applyReload: () => {
        try {
          const b = this.bridge();
          if (!b || !b.ReloadSignaturesNow) { this.toast('Unavailable', 'Signature reload not supported by this build.', 'red'); return; }
          if (!confirm('Apply new signatures now?\n\nProtection will briefly pause (~5 seconds) while the engine reloads. Any active scan will be interrupted.')) return;
          Promise.resolve(b.ReloadSignaturesNow()).catch(() => {});
          this.toast('Reloading signatures…', 'Engine will refresh in a few seconds.', 'blue');
          this.addLog('update', 'User requested signature reload — engine restarting');
        } catch (e) {}
      },
      updBtnLabel: (s.updChecking || (s.updPhase && s.updPhase !== 'idle' && s.updPhase !== 'done')) ? 'Checking…' : 'Check for updates',
      updBtnOpacity: s.updChecking ? '.6' : '1',
      // Live progress bar — stays visible for the whole duration of a check (including "checking"
      // phase which is very short) so the user always sees SOMETHING moving when they click.
      // Also visible during a background auto-check (updPhase non-idle without a UI click).
      updProgressVisible: !!(s.updChecking || (s.updPhase && s.updPhase !== 'idle')),
      dbDateStr: s.dbDate || '—',
      // Version-only display for the Updates card (2026-07-09 fix 3 — user asked for just the
      // version, not sig count + date). Falls back to "—" so a stale/empty state is visible
      // as "Version —" instead of hidden. refreshUpdateStatus() populates s.dbVersion.
      dbVersionStr: s.dbVersion ? s.dbVersion : '—',
      updPhaseLabel: (() => {
        // Non-technical phase text — no MB, no filenames, no seconds. Reads as reassuring UI copy
        // rather than raw dev output.
        switch (s.updPhase) {
          case 'checking':    return 'Checking for updates…';
          case 'downloading': return 'Downloading signature updates…';
          case 'verifying':   return 'Verifying signature files…';
          case 'installing':  return 'Applying updates…';
          case 'done':        return 'Signature database up to date';
          default:            return 'Working…';
        }
      })(),
      // Show the 4-row per-part list ONLY during the download phase. Verify/install/done are single
      // steps — a 4-row list there would just be four rows all showing "Complete" (visual noise).
      updShowParts: s.updPhase === 'downloading',
      // Per-part rows for the download panel. Deliberately ignores byte counts (never shown to the
      // user per project decision — see 2026-08-17 discussion on hiding "184 MB every update").
      // A part with total=0 (Content-Length not seen yet) still renders — its bar sits at 0% and
      // the label reads "Waiting…" until the first progress tick arrives.
      updParts: (() => {
        const raw = Array.isArray(s.updParts) ? s.updParts : [{},{},{},{}];
        // Pad to 4 rows if the status file was captured before all 4 threads announced themselves.
        while (raw.length < 4) raw.push({});
        return raw.slice(0, 4).map((p, i) => {
          const d = Number(p.d) || 0;
          const t = Number(p.t) || 0;
          const pct = t > 0 ? Math.min(100, Math.round(d * 100 / t)) : 0;
          const isDone = t > 0 && d >= t;
          const isActive = d > 0 && !isDone;
          const icon = isDone ? '✓' : (isActive ? '⟳' : '⏳');
          const iconColor = isDone ? 'var(--success)' : 'var(--accent)';
          const barColor  = isDone ? 'var(--success)' : 'var(--accent)';
          return {
            label: 'Signature block ' + (i + 1),
            pct, pctLabel: isDone ? 'Complete' : (t > 0 ? pct + '%' : 'Waiting…'),
            icon, iconColor, barColor,
          };
        });
      })(),
      updPct: (() => {
        // Rebalanced from the old 5-85% download → 90 → 95 → 100 mapping. New: download owns
        // 5-90% (matches perceived reality that the download IS the work); verify sits at 94%;
        // install at 97%; done at 100%. Fewer visible jumps, no long pause at ~85%.
        const p = s.updPhase;
        if (p === 'checking')    return 3;
        if (p === 'downloading') return Math.min(90, 3 + Math.round((Number(s.updPct) || 0) * 0.87));
        if (p === 'verifying')   return 94;
        if (p === 'installing')  return 97;
        if (p === 'done')        return 100;
        return 0;
      })(),
      updProgressText: '',
      // Right-side indicator flags — animated dots while any phase is active, a green ✓ when
      // the cycle lands on "done". Percentage was removed 2026-08-17 to avoid the "why is it
      // stuck at 47%" reaction. Motion signals "alive"; the check signals "complete".
      updDone:    s.updPhase === 'done',
      updWorking: !!(s.updPhase && s.updPhase !== 'idle' && s.updPhase !== 'done'),
      // Reassuring one-liner beneath the bar. Non-technical, no numbers, no filenames. Matches
      // the "calm UI" direction chosen 2026-08-17 after the 4-row list read as too scary.
      updSubText: (() => {
        switch (s.updPhase) {
          case 'checking':    return 'Reaching out for the latest signatures…';
          case 'downloading': return 'Preparing your latest protection updates.';
          case 'verifying':   return 'Making sure everything arrived safely.';
          case 'installing':  return 'Applying the new database to the engine.';
          case 'done':        return 'Your database is now up to date.';
          default:            return '';
        }
      })(),
      // Header state — driven by the real updater result from Bridge.GetUpdateStatus().
      //   up-to-date   → green check
      //   installed    → amber "restart-pending" (new VDB staged, active after service restart)
      //   failed       → red alert
      //   checking…    → blue spinner (shares the icon set)
      updHeadline: (() => {
        if (s.updChecking) return 'Checking for updates…';
        if (s.updPending)  return 'New Virus Database ready — applying in next 3 mins';
        if (s.updResult === 'installed')  return 'New Virus Database ready — applying in next 3 mins';
        if (s.updResult === 'up-to-date') return 'Virus database is up to date';
        if (s.updResult === 'failed')     return 'Update failed — using last-known-good VDB';
        return 'Virus database is up to date';
      })(),
      updHeaderBg: (s.updResult === 'failed') ? 'var(--danger-bg)'
                  : (s.updPending || s.updResult === 'installed') ? 'var(--warn-bg, #FEF3C7)'
                  : 'var(--success-bg)',
      updHeaderFg: (s.updResult === 'failed') ? 'var(--danger)'
                  : (s.updPending || s.updResult === 'installed') ? 'var(--warn, #B45309)'
                  : 'var(--success)',
      updHeaderIcon: (s.updResult === 'failed') ? '#i-alert' : '#i-check',
      // "checked X ago" — computed only when we have a real timestamp. Before the first
      // Bridge.GetUpdateStatus() Promise resolves the timestamp is empty, and we'd rather show
      // nothing than a flickering "never checked" that flips to a real value 500 ms later.
      updCheckedAgo: (() => {
        if (!s.updLastCheckIso) return '';
        try {
          const d = new Date(s.updLastCheckIso);
          const secs = Math.max(0, Math.floor((Date.now() - d.getTime()) / 1000));
          if (secs < 60) return 'checked just now';
          if (secs < 3600) return 'checked ' + Math.floor(secs / 60) + ' min ago';
          if (secs < 86400) return 'checked ' + Math.floor(secs / 3600) + ' h ago';
          return 'checked ' + Math.floor(secs / 86400) + ' d ago';
        } catch (e) { return ''; }
      })(),
      // Separator + label bundled — empty when there's no data yet, so the trailing bullet doesn't
      // hang in the UI.
      updCheckedAgoTail: (() => {
        if (!s.updLastCheckIso) return '';
        return ' · ' + (function () {
          try {
            const d = new Date(s.updLastCheckIso);
            const secs = Math.max(0, Math.floor((Date.now() - d.getTime()) / 1000));
            if (secs < 60) return 'checked just now';
            if (secs < 3600) return 'checked ' + Math.floor(secs / 60) + ' min ago';
            if (secs < 86400) return 'checked ' + Math.floor(secs / 3600) + ' h ago';
            return 'checked ' + Math.floor(secs / 86400) + ' d ago';
          } catch (e) { return 'checked recently'; }
        })();
      })(),
      updAuto: this.mkToggle(s.updAuto, () => this.setState((st) => ({ updAuto: !st.updAuto }), () => {
        try { const b = this.bridge(); if (b && b.SetVdbAutoUpdate) Promise.resolve(b.SetVdbAutoUpdate(!!this.state.updAuto)).catch(() => {}); } catch (e) {}
      })),
      updHours: s.updHours || 6,
      updHoursStr: String(s.updHours || 6),
      updHoursChange: (ev) => {
        const h = Number(ev.target.value) || 6;
        this.setState({ updHours: h }, () => {
          try { const b = this.bridge(); if (b && b.SetVdbUpdateHours) Promise.resolve(b.SetVdbUpdateHours(h)).catch(() => {}); } catch (e) {}
        });
      },

      logTabs: [['all', 'All'], ['scan', 'Scan'], ['realtime', 'Protection'], ['update', 'Update'], ['usb', 'USB'], ['event', 'Events']].map(([k, label]) => ({
        label, select: () => { this.setState({ logTab: k }); this.refreshLogs(k); },
        color: s.logTab === k ? 'var(--accent)' : 'var(--text-3)',
        line: s.logTab === k ? 'var(--accent)' : 'transparent'
      })),
      logRows,
      logsEmpty: logRows.length === 0,
      logQueryVal: s.logQuery || '',
      setLogQuery: (ev) => this.setState({ logQuery: (ev && ev.target && ev.target.value) || '' }),
      isUsbTab: s.logTab === 'usb',
      // E1 — the 'realtime'/Protection tab now renders the structured detection table, not flat log lines.
      isDetectionTab: s.logTab === 'realtime',
      detectionRows,
      detectionsEmpty: detectionRows.length === 0,
      isFlatLogTab: s.logTab !== 'scan' && s.logTab !== 'usb' && s.logTab !== 'realtime',   // flat log-lines table hidden for structured tabs
      usbRows: usbEventRows,
      usbEmpty: usbEventRows.length === 0,
      scanHistoryRows,
      scanHistoryEmpty: scanHistoryRows.length === 0,
      isScanTab: s.logTab === 'scan',
      isNotScanTab: s.logTab !== 'scan',
      exportLogs: () => this.toast('Logs exported', 'Saved to logs\\export.csv', 'blue'),

      settingsSeg: [['simple', 'Simple'], ['advanced', 'Advanced']].map(([k, label]) => ({
        label, select: () => this.setState({ settingsMode: k }),
        bg: s.settingsMode === k ? 'var(--card)' : 'transparent',
        color: s.settingsMode === k ? 'var(--text-1)' : 'var(--text-3)'
      })),
      settingsHint: s.settingsMode === 'simple' ? 'Everyday settings. Switch to Advanced for full control.' : 'Full engine control. Defaults are recommended.',
      isSimple: s.settingsMode === 'simple',
      isAdvanced: s.settingsMode === 'advanced',

      scanScope: [['recommended', 'Recommended'], ['all', 'All files']].map(([k, label]) => ({
        label, select: () => setPolicy({ mode: k }, () => { const b = this.bridge(); if (b) Promise.resolve(b.SetRealtimeScanMode(k)).catch(() => {}); }),
        bg: sp.mode === k ? 'var(--accent)' : 'transparent',
        color: sp.mode === k ? 'var(--on-accent)' : 'var(--text-3)'
      })),
      scanExt: sp.extensions,
      onScanExt: (ev) => setPolicy({ extensions: ev.target.value }),
      saveScanExt: () => { const b = this.bridge(); if (b) { const arr = String(sp.extensions).split(',').map((x) => x.trim().replace(/^\./, '').toLowerCase()).filter(Boolean); Promise.resolve(b.SetRealtimeExtensions(JSON.stringify(arr))).then(() => this.toast('Saved', 'Real-time extensions updated', 'green')).catch(() => {}); } },
      rtMaxMB: sp.rtMaxMB,
      onRtMax: (ev) => { const v = parseInt(ev.target.value, 10) || 0; setPolicy({ rtMaxMB: v }, () => { const b = this.bridge(); if (b) Promise.resolve(b.SetRealtimeMaxMB(v)).catch(() => {}); }); },
      odMaxMB: sp.odMaxMB,
      onOdMax: (ev) => { const v = parseInt(ev.target.value, 10) || 0; setPolicy({ odMaxMB: v }, () => { const b = this.bridge(); if (b) Promise.resolve(b.SetOnDemandMaxMB(v)).catch(() => {}); }); },
      rtArchivesTgl: this.mkToggle(sp.rtArchives, () => setPolicy({ rtArchives: !sp.rtArchives }, () => { const b = this.bridge(); if (b) Promise.resolve(b.SetRealtimeArchives(!sp.rtArchives)).catch(() => {}); })),
      rtNetworkTgl: this.mkToggle(sp.rtNetwork, () => setPolicy({ rtNetwork: !sp.rtNetwork }, () => { const b = this.bridge(); if (b) Promise.resolve(b.SetRealtimeNetwork(!sp.rtNetwork)).catch(() => {}); })),
      odArchivesTgl: this.mkToggle(sp.odArchives, () => setPolicy({ odArchives: !sp.odArchives }, () => { const b = this.bridge(); if (b) Promise.resolve(b.SetOnDemandArchives(!sp.odArchives)).catch(() => {}); })),

      // v26 Scan intensity — 3-mode picker + optional Custom extension editor. Switching from
      // Thorough → Smart offers to prune the cache immediately (~5 s) instead of waiting for LRU.
      odScanModes: [
        { key: 'smart',    label: 'Smart',      badge: 'RECOMMENDED', badgeFg: '#065F46', badgeBg: '#D1FAE5',
          desc: 'Scans executables, scripts, documents and archives. Skips safe file types (images, video, data). ~5× faster · same threat coverage.' },
        { key: 'thorough', label: 'Thorough',   badge: 'MAX',        badgeFg: '#7C2D12', badgeBg: '#FFEDD5',
          desc: 'Scans every file regardless of type. Slowest — recommended for post-cleanup verification or new systems.' },
        { key: 'custom',   label: 'Custom',     badge: 'ADVANCED',    badgeFg: '#4B5563', badgeBg: '#E5E7EB',
          desc: 'Pick your own extensions to scan. Anything not in the list is skipped.' }
      ].map((m) => ({
        ...m,
        checked: (sp.odScanMode || 'smart') === m.key,
        pick: () => {
          const prev = sp.odScanMode || 'smart';
          if (prev === m.key) return;
          setPolicy({ odScanMode: m.key }, () => {
            const b = this.bridge();
            if (b && b.SetOdScanMode) Promise.resolve(b.SetOdScanMode(m.key)).catch(() => {});
          });
          // Offer cache prune when moving to a narrower mode from Thorough.
          if (prev === 'thorough' && (m.key === 'smart' || m.key === 'custom')) {
            if (window.confirm('Also clean up scan-cache?\n\nThe cache remembers thousands of media/data files from Thorough scans. Pruning frees disk + RAM immediately (~5 s).\n\nRecommended.')) {
              try {
                const b2 = this.bridge();
                if (b2 && b2.PruneScanCacheToMode) Promise.resolve(b2.PruneScanCacheToMode()).then((n) => this.toast('Cache pruned', n + ' stale entries removed.', 'green')).catch(() => {});
              } catch (e) {}
            }
          }
        }
      })),
      odScanCustomOpen: (sp.odScanMode || 'smart') === 'custom',
      odIncludeExtsText: (sp.odIncludeExts || []).join(', '),
      odSmartCount: (sp.smartModeExts || []).length || 65,
      onOdIncludeExts: (ev) => {
        const raw = String(ev.target.value || '');
        // Debounce persistence — write once when the user stops typing (500 ms).
        clearTimeout(this._extPersistTimer);
        this._extPersistTimer = setTimeout(() => {
          const b = this.bridge();
          if (b && b.SetOdIncludeExts) Promise.resolve(b.SetOdIncludeExts(raw)).catch(() => {});
        }, 500);
      },
      resetToSmartDefaults: () => {
        const defaults = (sp.smartModeExts || []).join(', ');
        setPolicy({ odIncludeExts: (sp.smartModeExts || []).slice() });
        const b = this.bridge();
        if (b && b.SetOdIncludeExts) Promise.resolve(b.SetOdIncludeExts(defaults)).catch(() => {});
      },
      // Scan-cache kill switch — rarely useful, but present so a suspicious cache can be bisected without a rebuild.
      scanCacheTgl: this.mkToggle(sp.scanCache !== false, () => setPolicy({ scanCache: !(sp.scanCache !== false) }, () => { const b = this.bridge(); if (b && b.SetScanCacheEnabled) Promise.resolve(b.SetScanCacheEnabled(!(sp.scanCache !== false))).catch(() => {}); })),
      // Weekly cache-ignoring re-verify of high-risk locations.
      weeklySweepTgl: this.mkToggle(sp.weeklySweep !== false, () => setPolicy({ weeklySweep: !(sp.weeklySweep !== false) }, () => { const b = this.bridge(); if (b && b.SetWeeklyRootSweepEnabled) Promise.resolve(b.SetWeeklyRootSweepEnabled(!(sp.weeklySweep !== false))).catch(() => {}); })),
      // USB protection master + 4 modules + auto-scan on insertion (v20).
      usbMasterTgl:   this.mkToggle(sp.usbProtection      !== false, () => setPolicy({ usbProtection:      !(sp.usbProtection      !== false) }, () => { const b = this.bridge(); if (b && b.SetUsbProtectionEnabled)    Promise.resolve(b.SetUsbProtectionEnabled(   !(sp.usbProtection      !== false))).catch(() => {}); })),
      usbAutoTgl:     this.mkToggle(sp.usbAutoScanInsertion !== false, () => setPolicy({ usbAutoScanInsertion: !(sp.usbAutoScanInsertion !== false) }, () => { const b = this.bridge(); if (b && b.SetUsbAutoScanOnInsertion) Promise.resolve(b.SetUsbAutoScanOnInsertion(!(sp.usbAutoScanInsertion !== false))).catch(() => {}); })),
      usbVaccineTgl:  this.mkToggle(sp.usbVaccine         !== false, () => setPolicy({ usbVaccine:         !(sp.usbVaccine         !== false) }, () => { const b = this.bridge(); if (b && b.SetUsbVaccineEnabled)       Promise.resolve(b.SetUsbVaccineEnabled(      !(sp.usbVaccine         !== false))).catch(() => {}); })),
      usbUnhideTgl:   this.mkToggle(sp.usbUnhide          !== false, () => setPolicy({ usbUnhide:          !(sp.usbUnhide          !== false) }, () => { const b = this.bridge(); if (b && b.SetUsbUnhideEnabled)        Promise.resolve(b.SetUsbUnhideEnabled(       !(sp.usbUnhide          !== false))).catch(() => {}); })),
      usbLauncherTgl: this.mkToggle(sp.usbRootLauncher    !== false, () => setPolicy({ usbRootLauncher:    !(sp.usbRootLauncher    !== false) }, () => { const b = this.bridge(); if (b && b.SetUsbRootLauncherEnabled)  Promise.resolve(b.SetUsbRootLauncherEnabled( !(sp.usbRootLauncher    !== false))).catch(() => {}); })),
      usbLnkTgl:      this.mkToggle(sp.usbLnkAnalyzer     !== false, () => setPolicy({ usbLnkAnalyzer:     !(sp.usbLnkAnalyzer     !== false) }, () => { const b = this.bridge(); if (b && b.SetUsbLnkAnalyzerEnabled)   Promise.resolve(b.SetUsbLnkAnalyzerEnabled(  !(sp.usbLnkAnalyzer     !== false))).catch(() => {}); })),
      usbRemediationTgl: this.mkToggle(sp.usbRemediation  !== false, () => setPolicy({ usbRemediation:     !(sp.usbRemediation     !== false) }, () => { const b = this.bridge(); if (b && b.SetUsbRemediationEnabled)  Promise.resolve(b.SetUsbRemediationEnabled( !(sp.usbRemediation     !== false))).catch(() => {}); })),

      // ---- v22 System Remediation — master + per-mode (RT/OD) 6 sub-toggles ----
      // Which mode's sub-toggles are shown is controlled by s.remedProfile ('rt' or 'od').
      remedMasterTgl: this.mkToggle(sp.remediation !== false, () => setPolicy({ remediation: !(sp.remediation !== false) }, () => { const b = this.bridge(); if (b && b.SetRemediationEnabled) Promise.resolve(b.SetRemediationEnabled(!(sp.remediation !== false))).catch(() => {}); })),
      // Content types section — unified toggles that flip BOTH real-time and on-demand engine flags.
      // Read state = ON if EITHER mode is on (defensive — matches user expectation "protection is on").
      // Write = fires both RT and OD setters so nothing drifts.
      contentTypes: (() => {
        const engines = s.engines || [];
        const find = (rtKey) => engines.find(e => e.rtKey === rtKey) || { rtOn: true, odOn: true };
        const build = (title, rtKey, rtSet, odKey, odSet) => {
          const row = find(rtKey);
          const on = !!row.rtOn || !!row.odOn;
          return this.mkToggle(on, () => {
            const nv = !on;
            const b = this.bridge();
            if (b) {
              if (b[rtSet]) Promise.resolve(b[rtSet](nv)).catch(() => {});
              if (b[odSet]) Promise.resolve(b[odSet](nv)).catch(() => {});
            }
            this.setState(st => ({ engines: (st.engines || []).map(e => e.rtKey === rtKey ? { ...e, rtOn: nv, odOn: nv } : e) }));
          });
        };
        return [
          { title: 'Script protection',       ...build('Script protection',       'engJs',      'SetEngineJs',      'odEngJs',      'SetOdEngineJs') },
          { title: 'Office macro protection', ...build('Office macro protection', 'engPcode',   'SetEnginePcode',   'odEngPcode',   'SetOdEnginePcode') },
          { title: '.NET protection',         ...build('.NET protection',         'engDotNet',  'SetEngineDotNet',  'odEngDotNet',  'SetOdEngineDotNet') }
        ];
      })(),
      remedProfileRt: () => this.setState({ remedProfile: 'rt' }),
      remedProfileOd: () => this.setState({ remedProfile: 'od' }),
      remedRtBtnBg:    (s.remedProfile || 'rt') === 'rt' ? 'var(--card)' : 'transparent',
      remedRtBtnColor: (s.remedProfile || 'rt') === 'rt' ? 'var(--text-1)' : 'var(--text-3)',
      remedOdBtnBg:    (s.remedProfile || 'rt') === 'od' ? 'var(--card)' : 'transparent',
      remedOdBtnColor: (s.remedProfile || 'rt') === 'od' ? 'var(--text-1)' : 'var(--text-3)',
      // Build the 6 per-mode toggles. The active mode picks which keys we read/write.
      ...(() => {
        const modeIsRt = (s.remedProfile || 'rt') === 'rt';
        const mk = (rtKey, odKey, rtSetter, odSetter) => {
          const key = modeIsRt ? rtKey : odKey;
          const setter = modeIsRt ? rtSetter : odSetter;
          const cur = sp[key] !== false;
          return this.mkToggle(cur, () => setPolicy({ [key]: !cur }, () => { const b = this.bridge(); if (b && b[setter]) Promise.resolve(b[setter](!cur)).catch(() => {}); }));
        };
        return {
          remedKillTgl:    mk('remedKillRt',    'remedKillOd',    'SetRemediateKillProcRt',       'SetRemediateKillProcOd'),
          remedPersTgl:    mk('remedPersRt',    'remedPersOd',    'SetRemediatePersistenceRt',    'SetRemediatePersistenceOd'),
          remedHostsTgl:   mk('remedHostsRt',   'remedHostsOd',   'SetRemediateHostsRt',          'SetRemediateHostsOd'),
          remedBrowserTgl: mk('remedBrowserRt', 'remedBrowserOd', 'SetRemediateBrowserRt',        'SetRemediateBrowserOd'),
          remedBadTgl:     mk('remedBadRt',     'remedBadOd',     'SetRemediateBadAutostartRt',   'SetRemediateBadAutostartOd'),
          remedRebootTgl:  mk('remedRebootRt',  'remedRebootOd',  'SetRemediateRebootRt',         'SetRemediateRebootOd'),
          remedExclTgl:    mk('remedExclRt',    'remedExclOd',    'SetRemediateExclusionsRt',     'SetRemediateExclusionsOd')
        };
      })(),
      // "Run cleanup now" button — fires Bridge.RunSystemSanitizeNow, shows toast, refreshes chip.
      runCleanupNow: () => {
        const b = this.bridge();
        if (!b || !b.RunSystemSanitizeNow) { this.toast('Cleanup', 'Service not available.', 'red'); return; }
        try {
          const j = b.RunSystemSanitizeNow();
          const r = (typeof j === 'string') ? JSON.parse(j) : (j || {});
          if (r.skipped) { this.toast('Cleanup', r.reason || 'Nothing to do.', 'amber'); return; }
          const total = r.total | 0;
          if (total === 0) {
            this.toast('Cleanup', 'System is already clean — nothing to remove.', 'green');
          } else {
            const parts = [];
            if (r.hosts)    parts.push(r.hosts + ' hosts entry' + (r.hosts === 1 ? '' : 'ies'));
            if (r.browsers) parts.push(r.browsers + ' browser hijack' + (r.browsers === 1 ? '' : 's'));
            if (r.other)    parts.push(r.other + ' other item' + (r.other === 1 ? '' : 's'));
            this.toast('Cleanup complete', 'Removed ' + parts.join(', ') + '.', 'green');
          }
          // Refresh the chip immediately after run.
          try { const jj = b.GetLastRemediation(); const lr = (jj && jj !== 'null') ? JSON.parse(jj) : null; if (lr) this.setState({ lastRemediation: this.formatLastRemediation(lr) }); } catch (e) {}
        } catch (e) { this.toast('Cleanup', String(e), 'red'); }
      },
      // Last cleanup chip descriptor — built from Bridge.GetLastRemediation.
      lastRemediation: this.formatLastRemediation(s.lastRemediationRaw),
      // Currently-connected USB drives widget + Vaccinate-all button (2026-07-07).
      usbDrives: (s.usbDrives || []).map((u) => {
        const drv = u.drive || '';
        return {
          drive: drv, label: u.label || '(no label)', sizeGb: u.sizeGb || 0,
          statusText: u.vaccinated ? 'Vaccinated · autorun-worm infection blocked' : 'Not vaccinated · click Vaccinate below',
          statusColor: u.vaccinated ? '#16A34A' : 'var(--text-3)',
          iconBg: u.vaccinated ? 'rgba(13,148,136,.14)' : 'var(--icon-bg)',
          iconFg: u.vaccinated ? '#0D9488' : 'var(--text-2)',
          showVaccinate:   !u.vaccinated,
          showUnvaccinate:  u.vaccinated,
          scanNow: () => {
            const b = this.bridge();
            if (b && b.OpenScanWindow) Promise.resolve(b.OpenScanWindow('custom', drv)).catch(() => {});
          },
          vaccinate: () => {
            const b = this.bridge(); if (!b || !b.VaccinateUsbDrive) return;
            try {
              const r = b.VaccinateUsbDrive(drv);
              const msg = { ok: drv + ' vaccinated.', already: drv + ' was already vaccinated.', failed: 'Vaccination failed for ' + drv + '.' }[r] || 'Unknown result';
              this.toast('USB vaccine', msg, r === 'failed' ? 'red' : 'green');
              if (this.state.refreshUsbDrives) this.state.refreshUsbDrives();
            } catch (e) { this.toast('USB vaccine', String(e), 'red'); }
          },
          unvaccinate: () => {
            const b = this.bridge(); if (!b || !b.UnvaccinateUsbDrive) return;
            try {
              const r = b.UnvaccinateUsbDrive(drv);
              const msg = { ok: 'Vaccine removed from ' + drv + '.', already: drv + ' was not vaccinated.', failed: 'Could not remove vaccine from ' + drv + '.' }[r] || 'Unknown result';
              this.toast('USB vaccine', msg, r === 'failed' ? 'red' : 'amber');
              if (this.state.refreshUsbDrives) this.state.refreshUsbDrives();
            } catch (e) { this.toast('USB vaccine', String(e), 'red'); }
          }
        };
      }),
      usbDrivesEmpty: !(s.usbDrives && s.usbDrives.length > 0),
      refreshUsbDrives: () => {
        const b = this.bridge();
        if (!b || !b.GetUsbDrivesStatus) { this.setState({ usbDrives: [] }); return; }
        try {
          const j = b.GetUsbDrivesStatus();
          const arr = (typeof j === 'string') ? JSON.parse(j) : (j || []);
          this.setState({ usbDrives: arr });
        } catch (e) { this.setState({ usbDrives: [] }); }
      },
      vaccinateAllUsb: () => {
        const b = this.bridge();
        if (!b || !b.VaccinateAllUsb) { this.toast('Vaccinate', 'Service not available.', 'red'); return; }
        try {
          const j = b.VaccinateAllUsb();
          const r = (typeof j === 'string') ? JSON.parse(j) : (j || {});
          const parts = [];
          if (r.ok) parts.push(r.ok + ' vaccinated');
          if (r.alreadyDone) parts.push(r.alreadyDone + ' already OK');
          if (r.failed) parts.push(r.failed + ' failed');
          const msg = parts.length ? parts.join(' · ') : 'No removable drives connected.';
          this.toast('USB vaccination', msg, r.failed ? 'amber' : 'green');
          // Refresh the list so the new "Vaccinated" badges appear.
          try {
            const j2 = b.GetUsbDrivesStatus();
            const arr = (typeof j2 === 'string') ? JSON.parse(j2) : (j2 || []);
            this.setState({ usbDrives: arr });
          } catch (e) {}
        } catch (e) { this.toast('Vaccinate', String(e), 'red'); }
      },
      simpleRows: [
        ['rt', 'Real-time protection', ''],
        ['kill', 'Stop running malware', ''],
        ['procguard', 'Process guard', ''],
        ['procmem', 'Scan process memory (fileless)', ''],
        ['procmod', 'Scan loaded DLLs', ''],
        ['usb', 'Scan removable drives (USB)', ''],
        ['notif', 'Show notifications', '']
      ].map(([k, title, sub]) => {
        const on = k === 'rt' ? s.rt : s.simpleToggles[k];
        const persist = { notif: 'SetShowNotifications', kill: 'SetKillRunningMalware', procguard: 'SetProcessGuard', procmem: 'SetScanProcessMemory', procmod: 'SetScanModules' };
        let toggle;
        if (k === 'rt') toggle = () => this.toggleRt();
        else if (persist[k]) toggle = () => { const nv = !s.simpleToggles[k]; this.setState((st) => ({ simpleToggles: { ...st.simpleToggles, [k]: nv } }), () => { const b = this.bridge(); if (b) Promise.resolve(b[persist[k]](nv)).catch(() => {}); }); };
        else toggle = () => this.setState((st) => ({ simpleToggles: { ...st.simpleToggles, [k]: !st.simpleToggles[k] } }));
        return { title, sub, ...this.mkToggle(on, toggle) };
      }),
      accSections: accSectionsDef.map((d) => ({
        icon: d.icon, title: d.title,
        rows: mkRows(d.list),
        mh: s.acc[d.key] ? '900px' : '0px',
        rot: s.acc[d.key] ? 'rotate(90deg)' : 'rotate(0deg)',
        toggleOpen: () => this.setState((st) => ({ acc: { ...st.acc, [d.key]: !st.acc[d.key] } }))
      })),
      // Advanced mode switch (real-time vs on-demand) + on-detection action
      advProfiles: [['rt', 'Real-time'], ['od', 'On-demand']].map(([k, label]) => ({
        label, select: () => this.setState({ advProfile: k }),
        bg: s.advProfile === k ? 'var(--accent)' : 'transparent',
        color: s.advProfile === k ? 'var(--on-accent)' : 'var(--text-3)'
      })),
      advProfileHint: isRt
        ? 'These settings apply to real-time protection (files as they appear). Keep it lean for speed.'
        : 'These settings apply to manual scans (Quick / Full / Custom / right-click). Can be more thorough.',
      actionChoices: [['quarantine', 'Quarantine'], ['delete', 'Delete'], ['ask', 'Ask me'], ['log', 'Log only']].map(([k, label]) => ({
        label, select: () => setAction(k),
        bg: curAction === k ? 'var(--accent)' : 'transparent',
        color: curAction === k ? 'var(--on-accent)' : 'var(--text-2)',
        border: curAction === k ? 'var(--accent)' : 'var(--border-strong)'
      })),
      actionHint: ACTION_HINTS[curAction] || ACTION_HINTS.quarantine,

      perfToggleOpen: () => this.setState((st) => ({ perfOpen: !st.perfOpen })),
      perfMh: s.perfOpen ? '600px' : '0px',
      perfRot: s.perfOpen ? 'rotate(90deg)' : 'rotate(0deg)',
      perfPresets: [['fast', 'Fast'], ['balanced', 'Balanced'], ['thorough', 'Thorough'], ['custom', 'Custom']].map(([k, label]) => ({
        label,
        bg: perf.preset === k ? 'var(--card)' : 'transparent',
        color: perf.preset === k ? 'var(--text-1)' : 'var(--text-3)',
        select: () => {
          const p = PRESETS[k];
          if (p) {
            this.setState((st) => ({ [perfKey]: { ...st[perfKey], preset: k, item: p[0], global: p[1], extract: p[2] } }), () => {
              const b = this.bridge();
              if (b) { Promise.resolve(b[perfSetter.item](p[0])).catch(() => {}); Promise.resolve(b[perfSetter.global](p[1])).catch(() => {}); Promise.resolve(b[perfSetter.extract](p[2])).catch(() => {}); }
            });
          } else {
            this.setState((st) => ({ [perfKey]: { ...st[perfKey], preset: k } }));
          }
        }
      })),
      perfCustomOpacity: perf.preset === 'custom' ? '1' : '.5',
      perfCustomEvents: perf.preset === 'custom' ? 'auto' : 'none',
      perfFields: perfFieldsDef.map(([label, k]) => ({
        label, value: perf[k],
        onChange: (ev) => {
          const n = parseInt(ev.target.value, 10); const nv = isNaN(n) || n < 0 ? 0 : n;
          this.setState((st) => ({ [perfKey]: { ...st[perfKey], [k]: nv, preset: 'custom' } }), () => {
            const b = this.bridge();
            if (b) Promise.resolve(b[perfSetter[k]](nv)).catch(() => {});
          });
        }
      })),
      memToggle: this.mkToggle(perf.mem, () => {
        const nv = !perf.mem;
        this.setState((st) => ({ [perfKey]: { ...st[perfKey], mem: nv } }), () => {
          const b = this.bridge();
          if (b) Promise.resolve(b[perfSetter.mem](nv)).catch(() => {});
        });
      }),
      pwToggleOpen: () => this.setState((st) => ({ pwOpen: !st.pwOpen })),
      pwMh: s.pwOpen ? '600px' : '0px',
      pwRot: s.pwOpen ? 'rotate(90deg)' : 'rotate(0deg)',
      pwSusp: this.mkToggle(s.pwSusp, () => this.setState((st) => ({ pwSusp: !st.pwSusp }))),
      pwValue: s.pwValue,
      onPwInput: (ev) => this.setState({ pwValue: ev.target.value }),
      addPw: () => {
        const v = s.pwValue.trim();
        if (v) this.setState((st) => ({ passwords: [...st.passwords, v], pwValue: '' }));
      },
      passwords: s.passwords.map((p, i) => ({
        label: p,
        remove: () => this.setState((st) => ({ passwords: st.passwords.filter((_, j) => j !== i) }))
      })),

      trustToggle: this.mkToggle(s.trustPublishers, () => {
        const nv = !s.trustPublishers;
        this.setState({ trustPublishers: nv }, () => { const b = this.bridge(); if (b) Promise.resolve(b.SetTrustPublishers(nv)).catch(() => {}); });
      }),
      pubValue: s.pubValue,
      onPubInput: (ev) => this.setState({ pubValue: ev.target.value }),
      addPublisher: () => {
        this.setState((st) => {
          const v = (st.pubValue || '').trim();
          if (!v) return { pubValue: '' };
          const b = this.bridge();
          if (b) Promise.resolve(b.AddTrustedPublisher(v)).catch(() => {});
          return { addedPublishers: st.addedPublishers.indexOf(v) === -1 ? [...st.addedPublishers, v] : st.addedPublishers, pubValue: '' };
        });
      },
      addedPubs: s.addedPublishers.map((p) => ({
        label: p,
        remove: () => { const b = this.bridge(); if (b) Promise.resolve(b.RemoveTrustedPublisher(p)).catch(() => {}); this.setState((st) => ({ addedPublishers: st.addedPublishers.filter((x) => x !== p) })); }
      })),

      // ---- AMSI script/macro protection (Phase 2) ----
      amsiAvailable: s.amsi.available,
      amsiUnavailable: !s.amsi.available,
      amsiEnabledTgl: this.mkToggle(s.amsi.enabled, () => {
        const nv = !(this.state.amsi && this.state.amsi.enabled);
        this.setState((st) => ({ amsi: { ...st.amsi, enabled: nv } }), () => {
          const b = this.bridge();
          if (b && b.SetAmsiEnabled) Promise.resolve(b.SetAmsiEnabled(nv)).catch(() => {});
        });
        // Log rendering now driven by SERVICE (AntivirusCore.MaybeReload logs Script Guard
        // ENABLED/DISABLED to jr-av.log on the next settings-file change, which Bridge.GetLogs
        // reads back on the next 5-s sync). Client-side addLog would be overwritten on sync.
      }),
      amsiModeSeg: [['enforce', 'Block'], ['audit', 'Watch only']].map(([k, label]) => ({
        label, select: () => setAmsi({ mode: k }, 'SetAmsiMode', k),
        bg: s.amsi.mode === k ? 'var(--accent)' : 'transparent',
        color: s.amsi.mode === k ? 'var(--on-accent)' : 'var(--text-3)'
      })),
      amsiHostRows: [
        ['PowerShell', 'hostPs', 'SetAmsiHostPs'],
        ['Windows Script Host (VBScript / JScript)', 'hostWsh', 'SetAmsiHostWsh'],
        ['Office macros (VBA)', 'hostOffice', 'SetAmsiHostOffice'],
        ['HTA (mshta) & other', 'hostOther', 'SetAmsiHostOther']
      ].map(([title, key, setter]) => ({ title, ...this.mkToggle(s.amsi[key], () => setAmsi({ [key]: !s.amsi[key] }, setter, !s.amsi[key])) })),
      amsiMaxMB: s.amsi.maxMB,
      onAmsiMax: (ev) => { const v = parseInt(ev.target.value, 10); const nv = isNaN(v) || v <= 0 ? 4 : v; setAmsi({ maxMB: nv }, 'SetAmsiMaxMB', nv); },
      amsiHeurTgl: this.mkToggle(s.amsi.heuristics, () => setAmsi({ heuristics: !s.amsi.heuristics }, 'SetAmsiHeuristics', !s.amsi.heuristics)),
      amsiStatusText: s.amsi.available ? (s.amsi.registered ? 'Provider registered · AMSI active on this PC' : 'AMSI available — provider not registered') : 'Not available on this Windows version (Windows 10+ only)',
      amsiStatusOk: s.amsi.available && s.amsi.registered,
      amsiReRegister: () => { const b = this.bridge(); if (b) Promise.resolve(b.RegisterAmsi()).then((r) => { if (r) this.toast('Register failed', String(r), 'red'); else this.toast('Registering AMSI', 'Approve the elevation prompt, then reopen.', 'blue'); }).catch((e) => this.toast('Error', String(e), 'red')); },

      // ---- Settings v2 (5-section layout) — additional bindings ----
      notifTgl: this.mkToggle(s.simpleToggles.notif, () => { const nv = !s.simpleToggles.notif; this.setState((st) => ({ simpleToggles: { ...st.simpleToggles, notif: nv } }), () => { const b = this.bridge(); if (b) Promise.resolve(b.SetShowNotifications(nv)).catch(() => {}); }); }),

      // ---- Ransom Guard v1.1.0 section ----
      // Feature-gated hide: whole block disappears when the tier doesn't include Behavior.
      // ProHunt row is separately hidden for tiers below Internet Security.
      rgSection: (() => {
        const rg = s.rgSettings || { enabled: true, mode: 'behavior', sensitivity: 'med', canaryCount: 8, snapCount: 0, snapMB: 0, lastActivity: 'Never' };
        const sectionVisible = tqFeatureAllowed('RansomGuardBehavior');
        const proHuntFeat = tqFeatureAllowed('RansomGuardProHunt');
        const masterOn = !!rg.enabled;
        const proHuntOn = rg.mode === 'prohunt';
        const setRg = (patch, bridgeCall) => this.setState((st) => ({ rgSettings: { ...st.rgSettings, ...patch } }), () => { const b = this.bridge(); if (b && bridgeCall) { try { Promise.resolve(bridgeCall(b)).catch(() => {}); } catch (e) { /* silent */ } } });
        const sens = ['low', 'med', 'high'];
        const sensLabels = { low: 'Low', med: 'Medium', high: 'High' };
        // P5 · exact thresholds surfaced from RansomBehavior.cs sensitivity presets. Kept in sync
        // by hand — if the C# preset numbers change, update these strings too.
        const sensHints = {
          low:  'Low: 50 writes/sec across 8 folders + entropy ≥ 7.7 · fewer false alarms',
          med:  'Medium: 30 writes/sec across 5 folders + entropy ≥ 7.5 · balanced',
          high: 'High: 20 writes/sec across 4 folders + entropy ≥ 7.3 · trigger earlier'
        };
        return {
          visible: sectionVisible,
          enabled: masterOn,
          masterBg: masterOn ? 'var(--success)' : 'var(--border)',
          masterKnob: masterOn ? '17px' : '0px',
          masterToggle: () => setRg({ enabled: !masterOn }, (b) => b.SetRansomGuardEnabled ? b.SetRansomGuardEnabled(!masterOn) : null),
          proHuntFeatureVisible: proHuntFeat,
          proHuntBg: proHuntOn ? 'var(--accent)' : 'var(--border)',
          proHuntKnob: proHuntOn ? '17px' : '0px',
          proHuntToggle: () => { const nextMode = proHuntOn ? 'behavior' : 'prohunt'; setRg({ mode: nextMode }, (b) => b.SetRansomGuardMode ? b.SetRansomGuardMode(nextMode) : null); },
          sensHint: sensHints[rg.sensitivity] || sensHints.med,
          sensChoices: sens.map((k) => ({
            label: sensLabels[k],
            select: () => setRg({ sensitivity: k }, (b) => b.SetRansomGuardSensitivity ? b.SetRansomGuardSensitivity(k) : null),
            bg: rg.sensitivity === k ? 'var(--accent-dim)' : 'transparent',
            color: rg.sensitivity === k ? 'var(--accent)' : 'var(--text-2)'
          })),
          canaryCount: rg.canaryCount,
          snapCount: rg.snapCount,
          snapMB: rg.snapMB,
          lastActivity: rg.lastActivity || 'Never'
        };
      })(),

      killTgl: this.mkToggle(s.simpleToggles.kill, () => { const nv = !s.simpleToggles.kill; this.setState((st) => ({ simpleToggles: { ...st.simpleToggles, kill: nv } }), () => { const b = this.bridge(); if (b) Promise.resolve(b.SetKillRunningMalware(nv)).catch(() => {}); }); }),
      procguardTgl: this.mkToggle(s.simpleToggles.procguard, () => {
        const nv = !(this.state.simpleToggles && this.state.simpleToggles.procguard);
        this.setState((st) => ({ simpleToggles: { ...st.simpleToggles, procguard: nv } }), () => {
          const b = this.bridge();
          if (b && b.SetProcessGuard) Promise.resolve(b.SetProcessGuard(nv)).catch(() => {});
        });
        // Log rendering now driven by SERVICE (AntivirusCore.MaybeReload logs Process Guard
        // ENABLED/DISABLED to jr-av.log on the next settings-file change, which Bridge.GetLogs
        // reads back on the next 5-s sync). Client-side addLog would be overwritten on sync.
      }),
      procmemTgl: this.mkToggle(s.simpleToggles.procmem, () => { const nv = !s.simpleToggles.procmem; this.setState((st) => ({ simpleToggles: { ...st.simpleToggles, procmem: nv } }), () => { const b = this.bridge(); if (b) Promise.resolve(b.SetScanProcessMemory(nv)).catch(() => {}); }); }),
      procmodTgl: this.mkToggle(s.simpleToggles.procmod, () => { const nv = !s.simpleToggles.procmod; this.setState((st) => ({ simpleToggles: { ...st.simpleToggles, procmod: nv } }), () => { const b = this.bridge(); if (b) Promise.resolve(b.SetScanModules(nv)).catch(() => {}); }); }),
      usbTgl: this.mkToggle(s.simpleToggles.usb, () => this.setState((st) => ({ simpleToggles: { ...st.simpleToggles, usb: !st.simpleToggles.usb } }))),
      rtMasterTgl: (() => { const base = this.mkToggle(s.rt, () => this.toggleRt()); return base; })(),
      rtActionOpts: [['quarantine', 'Quarantine (recommended)'], ['delete', 'Delete permanently'], ['ask', 'Ask me each time'], ['log', 'Log only']].map(([value, label]) => ({
        value, label,
        isSelected: s.advActions.rt === value,
        isNotSelected: s.advActions.rt !== value
      })),
      onRtActionChange: (ev) => { const val = ev.target.value; this.setState((st) => ({ advActions: { ...st.advActions, rt: val } }), () => { const b = this.bridge(); if (b) Promise.resolve(b.SetRealtimeAction(val)).catch(() => {}); }); },
      rtActionHint: ACTION_HINTS[s.advActions.rt] || ACTION_HINTS.quarantine,
      odActionOpts: [['quarantine', 'Quarantine (recommended)'], ['delete', 'Delete permanently'], ['ask', 'Ask me each time'], ['log', 'Log only']].map(([value, label]) => ({
        value, label,
        isSelected: s.advActions.od === value,
        isNotSelected: s.advActions.od !== value
      })),
      onOdActionChange: (ev) => { const val = ev.target.value; this.setState((st) => ({ advActions: { ...st.advActions, od: val } }), () => { const b = this.bridge(); if (b) Promise.resolve(b.SetOnDemandAction(val)).catch(() => {}); }); },
      odActionHint: ACTION_HINTS[s.advActions.od] || ACTION_HINTS.quarantine,
      scanScopeChoices: [['recommended', 'Recommended'], ['all', 'All files']].map(([k, label]) => ({
        label, select: () => setPolicy({ mode: k }, () => { const b = this.bridge(); if (b) Promise.resolve(b.SetRealtimeScanMode(k)).catch(() => {}); }),
        bg: sp.mode === k ? 'var(--accent)' : 'transparent',
        color: sp.mode === k ? 'var(--on-accent)' : 'var(--text-2)'
      })),
      // Real-time extensions — chip tags (parse comma-list, add/remove per item)
      extList: (function(){ return String(sp.extensions || '').split(',').map(function(x){return x.trim().replace(/^\./,'').toLowerCase();}).filter(Boolean); })(),
      extValue: s.extValue || '',
      onExtInput: (ev) => this.setState({ extValue: ev.target.value }),
      addExt: () => {
        const v = String(s.extValue || '').trim().replace(/^\./,'').toLowerCase();
        if (!v) return;
        const cur = String(sp.extensions || '').split(',').map(function(x){return x.trim().replace(/^\./,'').toLowerCase();}).filter(Boolean);
        if (cur.indexOf(v) !== -1) { this.setState({ extValue: '' }); return; }
        const arr = cur.concat([v]);
        this.setState({ extValue: '' });
        setPolicy({ extensions: arr.join(', ') }, () => { const b = this.bridge(); if (b) Promise.resolve(b.SetRealtimeExtensions(JSON.stringify(arr))).then(() => this.toast('Saved', 'Added .' + v, 'green')).catch(() => {}); });
      },
      extChips: (function(){ return String(sp.extensions || '').split(',').map(function(x){return x.trim().replace(/^\./,'').toLowerCase();}).filter(Boolean); })().map((ext, i, self) => ({
        label: ext,
        remove: () => {
          const arr = String(sp.extensions || '').split(',').map(function(x){return x.trim().replace(/^\./,'').toLowerCase();}).filter(Boolean).filter((_, j) => j !== i);
          setPolicy({ extensions: arr.join(', ') }, () => { const b = this.bridge(); if (b) Promise.resolve(b.SetRealtimeExtensions(JSON.stringify(arr))).catch(() => {}); });
        }
      })),
      productVerLine: 'TreeIQ Anti-Malware v' + (s.productVersion || '1.0.0'),
      productEngLine: 'Engine ' + (s.engineVersion || '6.5.5') + ' · Database ' + (s.dbVersion || '—'),
      productSigLine: 'Signatures: ' + (typeof s.signatures === 'number' ? this.fmt(s.signatures) : String(s.signatures || '—')),

      toasts: s.toasts
    };
  }
}
</script>


</body></html>
