/* ── Local Fonts ── */
@font-face { font-family:'Vazir'; src:url('/fonts/vazir-400.woff2') format('woff2'); font-weight:400; font-display:swap; }
@font-face { font-family:'Vazir'; src:url('/fonts/vazir-500.woff2') format('woff2'); font-weight:500; font-display:swap; }
@font-face { font-family:'Vazir'; src:url('/fonts/vazir-700.woff2') format('woff2'); font-weight:600 700; font-display:swap; }
@font-face { font-family:'JetBrains Mono'; src:url('/fonts/jbmono-400.woff2') format('woff2'); font-weight:400; font-display:swap; }
@font-face { font-family:'JetBrains Mono'; src:url('/fonts/jbmono-500.woff2') format('woff2'); font-weight:500; font-display:swap; }
@font-face { font-family:'JetBrains Mono'; src:url('/fonts/jbmono-600.woff2') format('woff2'); font-weight:600; font-display:swap; }
@font-face { font-family:'Inter'; src:url('/fonts/inter-400.woff2') format('woff2'); font-weight:400; font-display:swap; }
@font-face { font-family:'Inter'; src:url('/fonts/inter-500.woff2') format('woff2'); font-weight:500; font-display:swap; }
@font-face { font-family:'Inter'; src:url('/fonts/inter-600.woff2') format('woff2'); font-weight:600; font-display:swap; }
@font-face { font-family:'DM Sans'; src:url('/fonts/dmsans-400.woff2') format('woff2'); font-weight:400; font-display:swap; }
@font-face { font-family:'DM Sans'; src:url('/fonts/dmsans-500.woff2') format('woff2'); font-weight:500; font-display:swap; }
@font-face { font-family:'DM Sans'; src:url('/fonts/dmsans-600.woff2') format('woff2'); font-weight:600; font-display:swap; }
@font-face { font-family:'Outfit'; src:url('/fonts/outfit-400.woff2') format('woff2'); font-weight:400; font-display:swap; }
@font-face { font-family:'Outfit'; src:url('/fonts/outfit-500.woff2') format('woff2'); font-weight:500; font-display:swap; }
@font-face { font-family:'Outfit'; src:url('/fonts/outfit-600.woff2') format('woff2'); font-weight:600; font-display:swap; }

:root {
  --bg:#0d1117; --bg2:#161b22; --bg3:#21262d; --bg4:#30363d;
  --line:#30363d; --line2:#484f58;
  --t1:#e6edf3; --t2:#8b949e; --t3:#484f58;
  --green:#1a9e5a; --green2:#157a44; --gbg:#0f2419; --gt:#3fb950;
  --red:#f85149; --rbg:#2a1a1a; --rt:#ff7b72;
  --amber:#d29922; --abg:#2a1f00; --at:#e3b341;
  --blue:#58a6ff; --bbg:#0d1f3c; --bt:#79c0ff;
  --cyan:#39c5cf; --cbg:#0a2a2e; --ct:#56d4dd;
  --purple:#7c3aed;--pbg:#f5f3ff;   --pt:#4c1d95;
  --mono:'JetBrains Mono',monospace;
  --sans:'Vazir',sans-serif;
  --sh:0 1px 4px rgba(0,0,0,.08);
}
*{box-sizing:border-box;margin:0;padding:0;}
html,body{height:100%;overflow:hidden;display:flex;flex-direction:column;}
body{background:var(--bg);color:var(--t1);font-family:var(--sans);display:flex;flex-direction:column;}

/* TOPBAR */
.top{height:50px;background:var(--bg2);border-bottom:1px solid var(--line);display:flex;align-items:center;padding:0 20px;gap:12px;flex-shrink:0;box-shadow:var(--sh);}
.logo{font-family:var(--mono);font-size:13px;font-weight:600;color:var(--green);display:flex;align-items:center;gap:8px;}
.pulse{width:8px;height:8px;border-radius:50%;background:var(--green);box-shadow:0 0 0 2px var(--gbg);animation:blink 2.5s infinite;}
@keyframes blink{0%,100%{opacity:1}50%{opacity:.2}}
.chips{display:flex;gap:5px;margin-right:auto;}
.chip{font-family:var(--sans);font-size:10px;padding:3px 9px;border-radius:20px;border:1px solid;font-weight:500;}
.chip.g{color:var(--gt);border-color:#a3d9b8;background:var(--gbg);}
.chip.r{color:var(--rt);border-color:#f4b8b8;background:var(--rbg);}
.chip.a{color:var(--at);border-color:#f0d090;background:var(--abg);}
.chip.b{color:var(--bt);border-color:#b8cef8;background:var(--bbg);}
.chip.n{color:var(--t2);border-color:var(--line2);background:var(--bg3);}
.chip.p{color:var(--pt);border-color:#c4b5fd;background:var(--pbg);}

/* LAYOUT */
.layout{display:grid;grid-template-columns:310px 1fr;flex:1;overflow:hidden;min-height:0;height:100%;}
.sidebar{border-left:1px solid var(--line);overflow-y:auto;background:var(--bg2);}
.main{display:flex;flex-direction:column;overflow:hidden;flex:1;min-height:0;}

/* SIDEBAR */
.sb-sec{padding:12px 14px;border-bottom:1px solid var(--line);}
.sb-ttl{font-family:var(--sans);font-size:9px;letter-spacing:.15em;color:var(--t3);text-transform:uppercase;margin-bottom:8px;display:flex;align-items:center;gap:5px;}
.sb-ttl::before{content:'';flex:1;height:1px;background:var(--line);}

.drop{border:1px dashed var(--line2);border-radius:7px;padding:12px;text-align:center;cursor:pointer;font-family:var(--sans);font-size:11px;color:var(--t3);transition:all .2s;display:block;}
.drop:hover{border-color:var(--green);color:var(--green);background:var(--gbg);}
.drop input{display:none;}

.inp{background:var(--bg3);border:1px solid var(--line);border-radius:5px;padding:6px 8px;color:var(--t1);font-family:var(--sans);font-size:11px;outline:none;transition:border-color .15s;width:100%;}
.inp:focus{border-color:var(--cyan);}
.inp::placeholder{color:var(--t3);}
textarea.inp{resize:vertical;min-height:80px;line-height:1.6;}

.g2{display:grid;grid-template-columns:1fr 1fr;gap:4px;margin-bottom:4px;}
.g3{display:grid;grid-template-columns:1fr 1fr 60px;gap:4px;margin-bottom:4px;}

/* server list */
.srv-list{display:flex;flex-direction:column;gap:2px;max-height:200px;overflow-y:auto;}
.srv-item{display:flex;align-items:center;gap:7px;padding:6px 8px;border-radius:5px;cursor:pointer;border:1px solid transparent;transition:all .12s;}
.srv-item:hover{background:var(--bg3);border-color:var(--line);}
.srv-item.selected{background:var(--gbg);border-color:#a3d9b8;}
.srv-dot{width:7px;height:7px;border-radius:50%;flex-shrink:0;background:var(--t3);}
.srv-item.s-ok   .srv-dot{background:var(--green);}
.srv-item.s-fail .srv-dot{background:var(--red);}
.srv-item.s-run  .srv-dot{background:var(--amber);animation:blink .7s infinite;}
.srv-item.s-skip .srv-dot{background:var(--blue);}
.srv-name{font-family:var(--sans);font-size:11px;font-weight:500;color:var(--t1);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.srv-ip{font-family:var(--sans);font-size:9px;color:var(--t3);}
.srv-checks{display:flex;gap:2px;flex-shrink:0;}
.ck{width:6px;height:6px;border-radius:50%;}
.ck.ok,.ck.running{background:var(--green);}
.ck.missing,.ck.err{background:var(--red);}
.ck.stopped{background:var(--amber);}
.ck.unknown{background:var(--line2);}
/* protocol badge — inline after the IP, inside .srv-ip (kept on one line so the
   responsive .srv-item{min-height:44px} row doesn't grow a third text line).
   Colours are rgba tints of the accent so they read on both dark + light themes. */
.srv-proto{display:inline-block;margin-left:5px;padding:0 4px;border-radius:3px;
  font-family:var(--mono);font-size:8px;line-height:12px;font-weight:600;
  letter-spacing:.02em;vertical-align:1px;white-space:nowrap;
  max-width:96px;overflow:hidden;text-overflow:ellipsis;}
/* Protocol identity colours — blue/orange, NOT the original blue/purple. The purple
   pair measured ΔE 0.9 for deuteranopia (indistinguishable for ~6% of men) and 10.7
   even with normal colour vision. Blue/orange scores ΔE 29.3 / 33.5. Values are the
   dark-mode steps; the light theme re-steps them below (a flip is not a substitute).
   Identity is never colour-alone here — every badge and tile carries its text label. */
.srv-proto.p-vless{background:rgba(56,139,253,.16);color:#388bfd;}
.srv-proto.p-wg{background:rgba(219,109,40,.18);color:#db6d28;}
.srv-proto.p-general{background:rgba(57,197,207,.16);color:var(--cyan);}
.srv-proto.p-unset{background:rgba(139,148,158,.14);color:var(--t2);}
/* p-vless/p-unset track --blue/--t2, which the light theme already darkens. p-wg and
   p-general do not: the light block redefines only green/red/amber/blue, so --cyan and
   the purple stay light and would wash out on a light tint. Darken both here, for both
   light-theme selectors used in this file. */
body.light-theme .srv-proto.p-vless,
[data-theme="light"] .srv-proto.p-vless{color:#1f6feb;}
body.light-theme .srv-proto.p-wg,
[data-theme="light"] .srv-proto.p-wg{color:#b45309;}
body.light-theme .srv-proto.p-general,
[data-theme="light"] .srv-proto.p-general{color:#0e7490;}

/* ── protocol filter chips (above the All/None row) ───────────────────────────── */
.srv-pchips{display:flex;flex-wrap:wrap;gap:4px;margin-bottom:6px;}
.srv-pchip{display:inline-flex;align-items:center;gap:4px;cursor:pointer;
  background:var(--bg3);border:1px solid var(--line);border-radius:11px;
  color:var(--t2);font-family:var(--mono);font-size:9px;line-height:1;
  padding:4px 7px;transition:border-color .12s,background .12s,color .12s;}
.srv-pchip:hover{border-color:var(--line2);color:var(--t1);}
.srv-pchip .pc-n{font-weight:600;opacity:.75;}
/* active chip: tinted in its own protocol colour so it matches the row badges */
.srv-pchip.on{color:var(--t1);border-color:var(--line2);background:var(--bg4);}
.srv-pchip.p-vless.on{border-color:#388bfd;color:#388bfd;background:rgba(56,139,253,.14);}
.srv-pchip.p-wg.on{border-color:#db6d28;color:#db6d28;background:rgba(219,109,40,.16);}
.srv-pchip.p-general.on{border-color:var(--cyan);color:var(--cyan);background:rgba(57,197,207,.14);}
body.light-theme .srv-pchip.p-vless.on,
[data-theme="light"] .srv-pchip.p-vless.on{color:#1f6feb;border-color:#1f6feb;}
body.light-theme .srv-pchip.p-wg.on,
[data-theme="light"] .srv-pchip.p-wg.on{color:#b45309;border-color:#b45309;}
body.light-theme .srv-pchip.p-general.on,
[data-theme="light"] .srv-pchip.p-general.on{color:#0e7490;border-color:#0e7490;}

/* tabs */
.stabs{display:flex;gap:3px;margin-bottom:7px;}
.stab{flex:1;padding:5px;border-radius:4px;border:1px solid var(--line);background:var(--bg3);color:var(--t3);font-family:var(--sans);font-size:9px;text-align:center;cursor:pointer;transition:all .14s;letter-spacing:.04em;}
.stab.on{border-color:var(--cyan);color:var(--ct);background:var(--cbg);}

.builtin-info{background:var(--bg3);border:1px solid var(--line);border-radius:5px;padding:9px;font-family:var(--sans);font-size:10px;color:var(--t2);line-height:1.9;}
.builtin-info strong{color:var(--gt);display:block;margin-bottom:3px;font-weight:600;}

/* settings */
.srow{display:flex;align-items:center;gap:7px;margin-bottom:5px;}
.srow label{font-family:var(--sans);font-size:10px;color:var(--t2);width:68px;}
input[type=range]{flex:1;accent-color:var(--green);}
.sval{font-family:var(--sans);font-size:10px;color:var(--gt);min-width:28px;text-align:left;font-weight:500;}

/* buttons */
.btn{padding:7px 12px;border-radius:5px;border:1px solid;cursor:pointer;font-family:var(--sans);font-size:11px;font-weight:500;transition:all .14s;white-space:nowrap;display:inline-flex;align-items:center;gap:5px;}
.btn:disabled{opacity:.35;cursor:not-allowed;}
.btn.primary  {color:var(--gt);border-color:#a3d9b8;background:var(--gbg);}
.btn.primary:hover:not(:disabled){background:var(--green2);color:#fff;border-color:var(--green2);}
.btn.secondary{color:var(--ct);border-color:#9dd8e8;background:var(--cbg);}
.btn.secondary:hover:not(:disabled){background:var(--cyan);color:#fff;border-color:var(--cyan);}
.btn.purple   {color:var(--pt);border-color:#c4b5fd;background:var(--pbg);}
.btn.purple:hover:not(:disabled){background:var(--purple);color:#fff;border-color:var(--purple);}
.btn.ghost    {color:var(--t2);border-color:var(--line);background:transparent;}
.btn.ghost:hover:not(:disabled){border-color:var(--line2);color:var(--t1);background:var(--bg3);}
.btn.danger   {color:var(--rt);border-color:#f4b8b8;background:var(--rbg);}
.btn.danger:hover:not(:disabled){background:var(--red);color:#fff;}
.btn.sm{padding:4px 9px;font-size:10px;}

/* pbar */
.pbar-bg{height:3px;background:var(--bg4);border-radius:2px;overflow:hidden;}
.pbar{height:100%;background:var(--green);border-radius:2px;transition:width .4s;}

/* MAIN TABS */
/* DEDUPE BATCH: merged. `display` was declared twice inside this very block (flex!important
   then a plain flex, same value), and a second .main-tabs rule far below added
   position/z-index while re-stating background and flex-shrink at identical values.
   All of it collapses to one rule with no value changes. display keeps its !important:
   switchMain() writes display on view containers, and source order cannot beat an
   inline style. */
.main-tabs{display:flex!important;position:relative;z-index:20;border-bottom:1px solid var(--line);background:var(--bg2);flex-shrink:0;}
.mtab{padding:10px 18px;font-family:var(--sans);font-size:11px;color:var(--t3);cursor:pointer;border-bottom:2px solid transparent;transition:all .14s;}
.mtab.on{color:var(--gt);border-bottom-color:var(--green);font-weight:600;}
.mtab:hover:not(.on){color:var(--t2);}

/* console */
.console{flex:1;overflow-y:auto;padding:14px 18px;font-family:var(--mono);font-size:11px;line-height:1.8;}
.l{white-space:pre-wrap;word-break:break-all;padding:1px 0;}
.l.info{color:var(--t2);}
.l.ok  {color:var(--green);}
.l.err {color:var(--red);}
.l.warn{color:var(--amber);}
.l.cmd {color:var(--cyan);}
.l.out {color:var(--t1);}
.l.head{color:var(--t1);font-weight:600;margin-top:8px;padding-top:6px;border-top:1px solid var(--line);}

/* compare panel */
.compare-panel{flex:1;overflow-y:auto;padding:16px;}
.cmp-section{margin-bottom:20px;}
.cmp-title{font-family:var(--sans);font-size:11px;font-weight:600;padding:6px 10px;border-radius:5px;margin-bottom:8px;display:flex;align-items:center;gap:8px;}
.cmp-title.missing{color:var(--rt);background:var(--rbg);border:1px solid #f4b8b8;}
.cmp-title.extra  {color:var(--at);background:var(--abg);border:1px solid #f0d090;}
.cmp-title.synced {color:var(--gt);background:var(--gbg);border:1px solid #a3d9b8;}
.cmp-table{width:100%;border-collapse:collapse;font-family:var(--sans);font-size:11px;}
.cmp-table th{color:var(--t3);font-size:9px;letter-spacing:.1em;padding:5px 8px;border-bottom:1px solid var(--line);text-align:right;background:var(--bg3);}
.cmp-table td{padding:5px 8px;border-bottom:1px solid var(--bg4);vertical-align:middle;}
.cmp-table tr:last-child td{border-bottom:none;}
.cmp-table tr:hover td{background:var(--bg3);}
.cmp-act{display:flex;gap:4px;}

/* footer stats */
.fstats{display:grid;grid-template-columns:repeat(4,1fr);border-top:1px solid var(--line);flex-shrink:0;background:var(--bg2);}
.fstat{padding:7px;text-align:center;border-left:1px solid var(--line);}
.fstat:last-child{border-left:none;}
.fv{font-family:var(--sans);font-size:18px;font-weight:600;}
.fl{font-size:9px;color:var(--t3);font-family:var(--sans);}

/* MODAL */
.overlay{position:fixed;inset:0;background:rgba(0,0,0,.3);display:flex;align-items:center;justify-content:center;z-index:100;opacity:0;pointer-events:none;transition:opacity .2s;}
.overlay.show{opacity:1;pointer-events:all;}
/* Responsive batch 1 (2026-07-31): was `width:460px` with no max-height — on a phone the
   460px box overflowed a ~390px viewport and .overlay's justify-content:center pushed the
   left edge off-screen (unreachable). No max-height meant a tall modal overflowed top+bottom
   with the action buttons out of reach. min() resolves to 460px on desktop = unchanged there.
   The vh line is a fallback for browsers that don't parse dvh; dvh tracks the mobile URL bar. */
.modal{background:var(--bg2);border:1px solid var(--line);border-radius:10px;padding:22px;width:min(460px,calc(100vw - 24px));max-height:calc(100vh - 32px);max-height:calc(100dvh - 32px);overflow-y:auto;box-shadow:0 8px 32px rgba(0,0,0,.12);}
.modal h3{font-size:14px;font-weight:600;color:var(--t1);margin-bottom:5px;}
.modal p{font-size:12px;color:var(--t2);margin-bottom:14px;line-height:1.6;}
.modal-srv-list{background:var(--bg3);border:1px solid var(--line);border-radius:5px;padding:9px;max-height:130px;overflow-y:auto;margin-bottom:14px;font-family:var(--mono);font-size:10px;color:var(--t2);line-height:1.9;}
.chk-row{display:flex;align-items:center;gap:8px;margin-bottom:8px;font-size:12px;color:var(--t2);}
.chk-row input{accent-color:var(--green);}
.modal-btns{display:flex;gap:8px;justify-content:flex-end;margin-top:14px;}
.modal-res{margin-top:10px;padding:9px;border-radius:5px;font-family:var(--sans);font-size:11px;display:none;}
.modal-res.ok {background:var(--gbg);color:var(--gt);border:1px solid #a3d9b8;}
.modal-res.err{background:var(--rbg);color:var(--rt);border:1px solid #f4b8b8;}

/* Edit Server dialog (built in serveredit.js with inline styles). It already sets
   width:360px;max-width:92vw so it fits horizontally, but sets NO max-height — its 8 field
   rows + buttons overflow a phone in landscape, and .overlay-style centering makes the top
   and bottom unreachable. max-height/overflow-y are not set inline, so these apply cleanly
   with no !important and no JS change. Desktop: taller than any real content = no-op. */
#editModalCard{max-height:calc(100vh - 32px);max-height:calc(100dvh - 32px);overflow-y:auto;}

::-webkit-scrollbar{width:4px;}
::-webkit-scrollbar-track{background:transparent;}
::-webkit-scrollbar-thumb{background:var(--line2);border-radius:2px;}
input[type=checkbox]{accent-color:var(--green);}


/* Terminal Tab */
/* Batch 7: virtual key bar, hidden unless the drawer/touch layout is active. This is
   the ONLY declaration batch 5+7 adds outside a media query. It targets an element that
   did not exist before, so it cannot alter how anything else renders on desktop. */
#termKeys{display:none;}

/* Batch 6: also new elements, also inert on desktop. Same reasoning as #termKeys —
   these selectors match nothing that existed before, so they cannot change how the
   desktop layout renders. */
#termPaneBar{display:none;}
#tbMoreBtn{display:none;}

.term-grid{display:grid;gap:6px;padding:10px;flex:1;min-height:0;;box-sizing:border-box;}
.term-pane{background:#0d1117;border:1px solid var(--line);border-radius:8px;display:flex;flex-direction:column;overflow:hidden;transition:box-shadow .2s;min-height:280px;height:100%;box-sizing:border-box;}
.term-pane:focus-within{box-shadow:0 0 0 2px #ff6b6b;border-color:#ff6b6b;}
.term-pane-header{display:flex;align-items:center;gap:6px;padding:5px 10px;background:#161b22;border-bottom:1px solid var(--line);font-family:var(--mono);font-size:10px;flex-shrink:0;}
.tdot{width:8px;height:8px;border-radius:50%;background:#333;flex-shrink:0;}
.tdot.ok{background:var(--green);box-shadow:0 0 6px var(--green);}
.tdot.err{background:var(--red);}
.tdot.wait{background:#f59e0b;animation:tblink 1s infinite;}
.tpname{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--t2);}
.tpclose{cursor:pointer;color:var(--t3);padding:0 4px;font-size:12px;}
.tpclose:hover{color:var(--red);}
.term-out{display:none;}
.term-inrow{display:flex;align-items:center;padding:4px 8px;border-top:1px solid #21262d;background:#0d1117;flex-shrink:0;}
.term-prompt{color:var(--green);font-family:var(--mono);font-size:11px;margin-right:6px;}
.term-inp{flex:1;background:transparent;border:none;outline:none;color:#c9d1d9;font-family:"JetBrains Mono","Courier New",monospace;font-size:12px;caret-color:var(--green);}
/* DEDUPE BATCH: `.term-toolbar` deleted — it matched NO element. The terminal toolbar is an
   inline-styled div, and batch 6 marked it `.term-tbar` precisely so this dead name
   would not be resurrected. Batch 3 already deleted the .term-toolbar rules that lived
   inside the media queries; this was the last copy, and the only one outside them. */
.tgsel{background:var(--bg3);border:1px solid var(--line);border-radius:5px;padding:4px 8px;color:var(--t2);font-size:11px;font-family:var(--mono);outline:none;cursor:pointer;}
.term-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;color:var(--t3);font-family:var(--mono);font-size:11px;cursor:pointer;height:100%;}
.term-empty:hover{background:rgba(255,255,255,.02);}
@keyframes tblink{0%,100%{opacity:1}50%{opacity:.3}}


/* ═══ Command Palette ═══════════════════════════════════════════════════════ */
#cmdOverlay {
  display:none; position:fixed; inset:0; background:rgba(0,0,0,.6);
  z-index:1000; backdrop-filter:blur(4px); animation:fadeIn .15s ease;
}
#cmdOverlay.open { display:flex; align-items:flex-start; justify-content:center; padding-top:80px; }
@keyframes fadeIn { from{opacity:0} to{opacity:1} }

#cmdBox {
  background:#161b22; border:1px solid #30363d; border-radius:12px;
  width:680px; max-height:70vh; display:flex; flex-direction:column;
  box-shadow:0 24px 64px rgba(0,0,0,.6); overflow:hidden;
  animation:slideDown .15s ease;
}
@keyframes slideDown { from{transform:translateY(-12px);opacity:0} to{transform:translateY(0);opacity:1} }

#cmdSearch {
  width:100%; background:transparent; border:none; outline:none;
  color:#e6edf3; font-size:16px; font-family:'JetBrains Mono',monospace;
  padding:18px 20px; border-bottom:1px solid #21262d;
  caret-color:#3fb950;
}
#cmdSearch::placeholder { color:#484f58; }

.cmd-tabs {
  display:flex; border-bottom:1px solid #21262d; padding:0 16px;
  background:#0d1117;
}
.cmd-tab {
  padding:10px 16px; font-size:12px; font-family:'JetBrains Mono',monospace;
  color:#484f58; cursor:pointer; border-bottom:2px solid transparent;
  transition:all .15s; margin-bottom:-1px;
}
.cmd-tab.active { color:#3fb950; border-bottom-color:#3fb950; }
.cmd-tab:hover:not(.active) { color:#8b949e; }

#cmdList {
  overflow-y:auto; flex:1; padding:8px;
}
#cmdList::-webkit-scrollbar { width:4px; }
#cmdList::-webkit-scrollbar-thumb { background:#30363d; border-radius:2px; }

.cmd-section-title {
  font-size:9px; font-family:'JetBrains Mono',monospace; color:#484f58;
  letter-spacing:.15em; text-transform:uppercase;
  padding:10px 12px 4px; margin-top:4px;
}

.cmd-item {
  display:flex; align-items:center; gap:10px; padding:9px 12px;
  border-radius:6px; cursor:pointer; transition:background .1s;
  font-family:'JetBrains Mono',monospace;
}
.cmd-item:hover { background:#21262d; }
.cmd-item.focused { background:#21262d; outline:1px solid #3fb950; }

.cmd-badge {
  font-size:9px; padding:2px 7px; border-radius:10px;
  font-weight:600; flex-shrink:0; letter-spacing:.05em;
}
.badge-apt     { background:#1f3a1f; color:#3fb950; }
.badge-systemd { background:#1a2a4a; color:#58a6ff; }
.badge-network { background:#3a2a1a; color:#f0883e; }
.badge-vnstat  { background:#2a1a3a; color:#bc8cff; }
.badge-docker  { background:#1a3a4a; color:#39c5cf; }
.badge-server  { background:#3a1a1a; color:#ff7b72; }
.badge-history { background:#2a2a1a; color:#e3b341; }
.badge-custom  { background:#2a2a2a; color:#8b949e; }

.cmd-text { flex:1; font-size:12px; color:#c9d1d9; min-width:0; }
.cmd-text code {
  color:#79c0ff; font-size:11px; display:block;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.cmd-desc { font-size:10px; color:#484f58; margin-top:1px; }

.cmd-star {
  font-size:14px; color:#484f58; cursor:pointer; flex-shrink:0;
  transition:color .15s; padding:2px 4px;
}
.cmd-star:hover { color:#e3b341; }
.cmd-star.fav { color:#e3b341; }

.cmd-send {
  font-size:10px; color:#484f58; background:#21262d; border:1px solid #30363d;
  border-radius:4px; padding:2px 8px; cursor:pointer; flex-shrink:0;
  font-family:'JetBrains Mono',monospace; transition:all .15s;
}
.cmd-send:hover { color:#3fb950; border-color:#3fb950; }

.cmd-empty {
  text-align:center; padding:40px 20px;
  color:#484f58; font-family:'JetBrains Mono',monospace; font-size:12px;
}

.cmd-footer {
  padding:10px 16px; border-top:1px solid #21262d; background:#0d1117;
  display:flex; gap:16px; align-items:center;
  font-size:10px; color:#484f58; font-family:'JetBrains Mono',monospace;
}
.cmd-footer kbd {
  background:#21262d; border:1px solid #30363d; border-radius:3px;
  padding:1px 5px; font-size:9px; color:#8b949e;
}
.cmd-footer span { margin-left:auto; }


/* ── Server Persistence ──────────────────────────────────────────────────── */
.srv-actions {
  display:flex; gap:6px; padding:8px 14px 4px;
  border-bottom:1px solid var(--line);
}
.srv-actions .btn { flex:1; font-size:11px; padding:5px 8px; }

.autosave-bar {
  display:flex; align-items:center; gap:6px; padding:6px 14px;
  background:var(--gbg); border-bottom:1px solid #a3d9b8;
  font-size:11px; color:var(--gt); font-family:var(--mono);
  animation:slideIn .3s ease;
}
@keyframes slideIn { from{opacity:0;transform:translateY(-6px)} to{opacity:1;transform:translateY(0)} }
.autosave-bar .dot { width:6px;height:6px;border-radius:50%;background:var(--green);flex-shrink:0; }
.autosave-bar .dismiss {
  margin-left:auto; cursor:pointer; color:var(--green2);
  font-size:10px; padding:1px 6px; border-radius:3px;
  border:1px solid var(--green2); transition:all .15s;
}
.autosave-bar .dismiss:hover { background:var(--green2); color:#fff; }

.saved-sessions {
  display:flex; flex-direction:column; gap:4px;
  padding:6px 14px 8px;
}
.saved-session-item {
  display:flex; align-items:center; gap:6px; padding:6px 10px;
  background:var(--bg3); border-radius:6px; cursor:pointer;
  border:1px solid var(--line); transition:all .15s;
}
.saved-session-item:hover { border-color:var(--green); background:var(--gbg); }
.saved-session-item .ss-name { flex:1; font-size:12px; color:var(--t1); font-weight:500; }
.saved-session-item .ss-meta { font-size:10px; color:var(--t3); font-family:var(--mono); }
.saved-session-item .ss-del {
  color:var(--t3); font-size:14px; padding:0 4px;
  cursor:pointer; transition:color .15s;
}
.saved-session-item .ss-del:hover { color:var(--red); }
.ss-empty { text-align:center; padding:16px; color:var(--t3); font-size:12px; }


/* ═══ AI Assistant Panel ════════════════════════════════════════════════════ */
#aiOverlay {
  display:none; position:fixed; inset:0; background:rgba(0,0,0,.65);
  z-index:1001; backdrop-filter:blur(4px);
}
#aiOverlay.open { display:flex; align-items:flex-start; justify-content:center; padding-top:60px; }

#aiBox {
  background:#0d1117; border:1px solid #30363d; border-radius:14px;
  width:760px; max-height:80vh; display:flex; flex-direction:column;
  box-shadow:0 32px 80px rgba(0,0,0,.7); overflow:hidden;
  animation:aiSlide .18s ease;
}
@keyframes aiSlide { from{transform:translateY(-16px);opacity:0} to{transform:translateY(0);opacity:1} }

.ai-header {
  display:flex; align-items:center; gap:10px; padding:16px 20px;
  border-bottom:1px solid #21262d; background:#161b22; flex-shrink:0;
}
.ai-header-title { font-size:14px; font-weight:600; color:#e6edf3; flex:1; }
.ai-provider-badge {
  font-size:10px; font-family:monospace; padding:3px 10px;
  border-radius:10px; background:#1f3a1f; color:#3fb950;
  border:1px solid #2d5a2d;
}
.ai-close {
  color:#484f58; cursor:pointer; font-size:18px; line-height:1;
  padding:2px 6px; border-radius:4px; transition:all .15s;
}
.ai-close:hover { color:#ff7b72; background:#2a1a1a; }

.ai-tabs {
  display:flex; border-bottom:1px solid #21262d; background:#0d1117;
  padding:0 16px; flex-shrink:0;
}
.ai-tab {
  padding:10px 16px; font-size:12px; font-family:monospace;
  color:#484f58; cursor:pointer; border-bottom:2px solid transparent;
  transition:all .15s; margin-bottom:-1px; white-space:nowrap;
}
.ai-tab.active { color:#58a6ff; border-bottom-color:#58a6ff; }
.ai-tab:hover:not(.active) { color:#8b949e; }

.ai-body { flex:1; overflow-y:auto; padding:16px 20px; display:flex; flex-direction:column; gap:12px; }

.ai-input-row { display:flex; gap:8px; }
.ai-textarea {
  flex:1; background:#161b22; border:1px solid #30363d; border-radius:8px;
  color:#c9d1d9; font-family:monospace; font-size:12px;
  padding:10px 14px; resize:vertical; min-height:60px; outline:none;
  transition:border-color .15s;
}
.ai-textarea:focus { border-color:#58a6ff; }
.ai-textarea::placeholder { color:#484f58; }

.ai-context-area {
  background:#0d1117; border:1px solid #21262d; border-radius:8px;
  color:#8b949e; font-family:monospace; font-size:11px;
  padding:10px 14px; resize:vertical; min-height:80px; outline:none;
}

.ai-btn {
  background:#1f6feb; color:#fff; border:none; border-radius:8px;
  padding:10px 18px; font-size:12px; font-weight:600; cursor:pointer;
  transition:background .15s; white-space:nowrap; align-self:flex-end;
}
.ai-btn:hover { background:#388bfd; }
.ai-btn:disabled { background:#21262d; color:#484f58; cursor:not-allowed; }

.ai-result {
  background:#161b22; border:1px solid #30363d; border-radius:8px;
  padding:14px; font-family:monospace; font-size:12px; color:#c9d1d9;
  white-space:pre-wrap; word-break:break-word; line-height:1.6;
  position:relative;
}
.ai-result-actions {
  display:flex; gap:8px; margin-top:10px; flex-wrap:wrap;
}
.ai-result-btn {
  font-size:11px; padding:5px 12px; border-radius:5px; cursor:pointer;
  border:1px solid #30363d; background:#21262d; color:#c9d1d9;
  transition:all .15s; font-family:monospace;
}
.ai-result-btn:hover { border-color:#3fb950; color:#3fb950; }
.ai-result-btn.primary { background:#1f3a1f; border-color:#3fb950; color:#3fb950; }
.ai-result-btn.primary:hover { background:#3fb950; color:#0d1117; }

.ai-label {
  font-size:10px; font-family:monospace; color:#484f58;
  letter-spacing:.1em; text-transform:uppercase; margin-bottom:4px;
}

.ai-loading {
  display:flex; align-items:center; gap:10px; color:#58a6ff;
  font-family:monospace; font-size:12px; padding:10px 0;
}
.ai-spinner {
  width:16px; height:16px; border:2px solid #21262d;
  border-top-color:#58a6ff; border-radius:50%;
  animation:spin .8s linear infinite;
}
@keyframes spin { to{transform:rotate(360deg)} }

.ai-error { color:#ff7b72; font-family:monospace; font-size:12px; padding:8px 0; }

.ai-footer {
  padding:10px 20px; border-top:1px solid #21262d; background:#0d1117;
  display:flex; align-items:center; gap:12px; flex-shrink:0;
  font-size:10px; color:#484f58; font-family:monospace;
}
.ai-footer kbd {
  background:#21262d; border:1px solid #30363d; border-radius:3px;
  padding:1px 5px; font-size:9px;
}
.ai-provider-select {
  background:#161b22; border:1px solid #30363d; border-radius:5px;
  color:#8b949e; font-size:10px; font-family:monospace;
  padding:3px 8px; outline:none; cursor:pointer; margin-left:auto;
}


/* ═══ Dark / Light Theme ════════════════════════════════════════════════════ */
:root {
  --theme-transition: background .2s, color .2s, border-color .2s;
}
body { transition: var(--theme-transition); }

body.light-theme {
  --bg:#f4f6fb; --bg2:#ffffff; --bg3:#eef0f7; --bg4:#e4e8f4;
  --line:#c8d0e0; --line2:#b0bcd0;
  --t1:#1a2035; --t2:#4a5568; --t3:#8896aa;
  --green:#1a9e5a; --green2:#157a44; --gbg:#e8f8ef; --gt:#0f5c30;
  --red:#d93030; --rbg:#fdecea; --rt:#8b1a1a;
  --amber:#c07800; --abg:#fff8e6; --at:#7a4d00;
  --blue:#2563eb; --bbg:#eff4ff; --bt:#1a3a8f;
  --mono:'JetBrains Mono',monospace;
  --sans:'Vazirmatn',sans-serif;
}

/* theme toggle button */
.theme-btn {
  width:32px; height:32px; border-radius:8px; border:1px solid var(--line);
  background:var(--bg3); color:var(--t2); cursor:pointer;
  display:flex; align-items:center; justify-content:center;
  font-size:16px; transition:all .2s; flex-shrink:0;
}
.theme-btn:hover { border-color:var(--green); color:var(--green); }

/* ═══ Mobile Responsive ═════════════════════════════════════════════════════ */
/* Mobile rules that used to live here were merged into the consolidated
   responsive block at the end of this file (batch 3, 2026-07-31). */



/* Hide mobile menu btn on desktop */
.mobile-menu-btn { display: none; }


[data-theme="light"] {
  --bg:#f0f4f8 !important; --bg2:#ffffff !important; --bg3:#e8edf5 !important;
  --bg4:#dde3ef !important; --line:#c8d0e0 !important; --line2:#b0bcd0 !important;
  --t1:#1a2035 !important; --t2:#4a5568 !important; --t3:#8896aa !important;
}
[data-theme="light"] .console { background:#f8f9fc !important; color:#1a2035 !important; }
[data-theme="light"] .term-pane { background:#1a1f2e !important; }







/* Stacking order: topbar above sidebar above terminal.
   DEDUPE BATCH: each of these was declared twice — once here with !important and again a few
   lines below without it, at the same specificity and the same value (200 / 150). The
   !important copies are the ones kept: they are what actually resolves today, and
   keeping them preserves the importance flag exactly, which the plain copies could not.
   The duplicates said nothing new; they only made the file look like there was a
   disagreement to referee. */
/* topbar همیشه بالا */
.top { z-index: 200 !important; position: relative !important; }
/* sidebar overlay بالاتر از terminal */
.sidebar { z-index: 150 !important; }
/* #sidebarBackdrop{z-index:149!important} used to sit here. Batch 2's canonical
   #sidebarBackdrop rule (further down, with the transition/opacity/pointer-events that
   make the drawer work) already sets z-index:149, so this line only duplicated it.
   Dropping it does drop the !important — provably inert here: those two were the only
   declarations of z-index on that element anywhere in the file, and the backdrop is
   static markup with no style attribute, so there is nothing left for !important to
   beat. Verified by the resolver rather than assumed. */

#termBackBtn { display: none; }





/* Terminal — same flex behavior as other views */
#view-terminal { flex-direction: column; min-height: 0; }


/* Main tabs always above terminal — DEDUPE BATCH: folded into the .main-tabs rule in the
   MAIN TABS section above. Same values, one rule. */


/* ═══ SSH Key Manager ═══════════════════════════════════════════════════════ */
.key-drop-zone {
  border: 2px dashed var(--line2); border-radius: 8px;
  padding: 14px; text-align: center; cursor: pointer;
  transition: all .2s; background: var(--bg3);
  font-size: 12px; color: var(--t3);
}
.key-drop-zone:hover, .key-drop-zone.drag-over {
  border-color: var(--green); color: var(--green); background: var(--gbg);
}
.key-drop-zone input[type=file] { display:none; }

.key-item {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 10px; border-radius: 6px;
  background: var(--bg3); border: 1px solid var(--line);
  margin-bottom: 5px; transition: all .15s;
}
.key-item:hover { border-color: var(--line2); }
.key-icon { font-size: 16px; flex-shrink: 0; }
.key-name { flex: 1; font-size: 12px; font-family: var(--mono); color: var(--t1);
             white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.key-size { font-size: 10px; color: var(--t3); font-family: var(--mono); flex-shrink: 0; }
.key-del { color: var(--t3); cursor: pointer; font-size: 16px; padding: 0 2px;
            transition: color .15s; flex-shrink: 0; }
.key-del:hover { color: var(--red); }

/* .proto-select is styled identically to .key-select but deliberately does NOT carry
   that class: keyPopulateSelects() (index.html) treats `.key-select` as "this is an
   SSH-key dropdown" and overwrites the options of every such element, which wiped the
   protocol options on load. Separate class = same look, left alone by the key loader. */
.key-select, .proto-select {
  width: 100%; background: var(--bg3); border: 1px solid var(--line);
  border-radius: 5px; color: var(--t1); font-size: 11px; font-family: var(--mono);
  padding: 4px 8px; outline: none; cursor: pointer; transition: border .15s;
}
.key-select:focus, .proto-select:focus { border-color: var(--green); }

/* server list item - key badge */
.srv-key-badge {
  font-size: 9px; font-family: var(--mono); padding: 1px 5px;
  border-radius: 4px; background: var(--bbg); color: var(--bt);
  border: 1px solid var(--blue); margin-left: 4px;
}


.key-required { border-color: var(--amber) !important; }
.key-hint { font-size:10px; color:var(--amber); margin-top:3px; display:none; }


.srv-search {
  display:flex; align-items:center; gap:6px;
  padding:8px 14px; border-bottom:1px solid var(--line);
  background:var(--bg2);
}
.srv-search-inp {
  flex:1; background:var(--bg3); border:1px solid var(--line);
  border-radius:6px; color:var(--t1); font-size:12px;
  padding:5px 10px; outline:none; font-family:var(--mono);
  transition:border .15s;
}
.srv-search-inp:focus { border-color:var(--green); }
.srv-search-inp::placeholder { color:var(--t3); }
.srv-search-clear {
  color:var(--t3); cursor:pointer; font-size:16px;
  padding:2px 4px; transition:color .15s; display:none;
}
.srv-search-clear:hover { color:var(--red); }




/* ── AI Box + CMD Box light theme ──────────────────────────────────────── */
body.light-theme #aiBox,
body.light-theme #cmdBox {
  background: #ffffff !important;
  border-color: #d0d7de !important;
  box-shadow: 0 8px 40px rgba(0,0,0,.15) !important;
}
body.light-theme .ai-header,
body.light-theme .cmd-header {
  background: #f6f8fa !important;
  border-bottom-color: #d0d7de !important;
}
body.light-theme .ai-header-title,
body.light-theme .cmd-header h3 { color: #1f2328 !important; }
body.light-theme .ai-tabs,
body.light-theme .cmd-tabs { background: #ffffff !important; border-bottom-color: #d0d7de !important; }
body.light-theme .ai-tab { color: #656d76 !important; }
body.light-theme .ai-tab.active { color: #0969da !important; border-bottom-color: #0969da !important; }
body.light-theme .ai-body,
body.light-theme .cmd-body { background: #ffffff !important; }
body.light-theme .ai-textarea,
body.light-theme .cmd-input {
  background: #f6f8fa !important;
  border-color: #d0d7de !important;
  color: #1f2328 !important;
}
body.light-theme .ai-textarea:focus { border-color: #0969da !important; }
body.light-theme .ai-textarea::placeholder { color: #8c959f !important; }
body.light-theme .ai-context-area,
body.light-theme .ai-result-box,
body.light-theme .cmd-result {
  background: #f6f8fa !important;
  border-color: #d0d7de !important;
  color: #1f2328 !important;
}
body.light-theme .ai-label,
body.light-theme .ai-section-label { color: #656d76 !important; }
body.light-theme .ai-provider-badge {
  background: #dafbe1 !important;
  color: #116329 !important;
  border-color: #acd5b3 !important;
}
body.light-theme .ai-close:hover { background: #ffd7d5 !important; color: #d1242f !important; }
body.light-theme .ai-lib-item,
body.light-theme .cmd-item {
  background: #f6f8fa !important;
  border-color: #d0d7de !important;
  color: #1f2328 !important;
}
body.light-theme .ai-lib-item:hover,
body.light-theme .cmd-item:hover { background: #eaf5ff !important; border-color: #0969da !important; }
body.light-theme .ai-footer,
body.light-theme .cmd-footer {
  background: #f6f8fa !important;
  border-top-color: #d0d7de !important;
  color: #656d76 !important;
}
body.light-theme .overlay { background: rgba(0,0,0,.3) !important; }


body.light-theme .badge-apt     { background:#d1f8d1 !important; color:#1a6b1a !important; }
body.light-theme .badge-systemd { background:#dbeafe !important; color:#1e40af !important; }
body.light-theme .badge-network { background:#ffedd5 !important; color:#9a3412 !important; }
body.light-theme .badge-vnstat  { background:#ede9fe !important; color:#5b21b6 !important; }
body.light-theme .badge-docker  { background:#cffafe !important; color:#155e75 !important; }
body.light-theme .badge-server  { background:#fee2e2 !important; color:#991b1b !important; }
body.light-theme .badge-history { background:#fef9c3 !important; color:#854d0e !important; }
body.light-theme .badge-custom  { background:#f1f5f9 !important; color:#475569 !important; }
body.light-theme .badge-cyan    { background:#cffafe !important; color:#155e75 !important; }
body.light-theme .badge-purple  { background:#ede9fe !important; color:#5b21b6 !important; }
body.light-theme .badge-users   { background:#f0fdf4 !important; color:#166534 !important; }
body.light-theme .cmd-send { background:#f6f8fa !important; border-color:#d0d7de !important; color:#57606a !important; }
body.light-theme .cmd-send:hover { color:#1a9e5a !important; border-color:#1a9e5a !important; }
body.light-theme .cmd-section-title { color:#94a3b8 !important; }
body.light-theme #cmdSearch { background:#f6f8fa !important; color:#1f2328 !important; border-bottom-color:#d0d7de !important; }
body.light-theme .cmd-text code { color:#0550ae !important; }
body.light-theme .cmd-desc { color:#656d76 !important; }
body.light-theme .cmd-star { color:#d0d7de !important; }
body.light-theme .cmd-star.fav { color:#d29922 !important; }
body.light-theme .ai-result { background:#f6f8fa !important; border-color:#d0d7de !important; color:#1f2328 !important; }
body.light-theme .ai-result-btn { background:#f6f8fa !important; border-color:#d0d7de !important; color:#1f2328 !important; }
body.light-theme .ai-btn { background:#0969da !important; }
body.light-theme .ai-provider-select { background:#f6f8fa !important; border-color:#d0d7de !important; color:#57606a !important; }


/* ── Terminal Fullscreen ─────────────────────────────────────────── */
/* DEDUPE BATCH: this was the FIRST of two identical-purpose copies of .term-pane-fs and its
   three .tpfs-btn rules. The second copy (further down, just above "Schedule") declares
   the same property set and, being later at equal specificity with both sides
   !important, won every one of them — so everything here was already dead. Deleted
   rather than merged, because a merge would have to pick a winner and there is nothing
   to pick: the surviving copy IS what renders today.

   The one property whose VALUE actually differed was z-index: 9999 here vs 2500 in the
   survivor. 2500 is what has been in effect; it still clears .top (200), .sidebar (150)
   and #sftpOverlay (2000), which is the ordering that matters. Deleting this block
   changes nothing on screen — it only stops the file from claiming 9999.

   The .tpfs-btn colours differed too, and again only the later ones ever applied:
   hover was var(--green) (#1a9e5a) here vs #3fb950 there, and the in-fullscreen colour
   var(--amber) (#d29922) vs #f0883e. Those are visibly different greens and oranges —
   which is the tell that this copy has been dead a long time. */

/* ── Font size slider ────────────────────────────────────────────── */
.font-slider-wrap {
  display: flex;
  align-items: center;
  gap: 4px;
  flex-shrink: 0;
}
.font-slider-a { font-size: 9px; color: var(--t3); user-select: none; }
.font-slider-b { font-size: 14px; color: var(--t3); user-select: none; }
#termFontSlider {
  width: 60px;
  accent-color: var(--green);
  cursor: pointer;
}
#termFontLabel {
  font-family: var(--mono);
  font-size: 9px;
  color: var(--t3);
  min-width: 24px;
}


/* ═══ SFTP File Manager ════════════════════════════════════════════ */
#sftpOverlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);
  z-index:2000;backdrop-filter:blur(4px);align-items:flex-start;
  justify-content:center;padding-top:40px;}
#sftpOverlay.open{display:flex;}
#sftpBox{background:#0d1117;border:1px solid #30363d;border-radius:14px;
  width:860px;max-width:96vw;max-height:82vh;display:flex;
  flex-direction:column;box-shadow:0 24px 80px rgba(0,0,0,.8);overflow:hidden;}
.sftp-header{display:flex;align-items:center;gap:10px;padding:14px 18px;
  border-bottom:1px solid #21262d;background:#161b22;flex-shrink:0;}
.sftp-title{font-size:14px;font-weight:700;color:#e6edf3;flex:1;}
#sftpServerSel{background:#21262d;border:1px solid #30363d;border-radius:6px;
  color:#c9d1d9;font-size:11px;font-family:monospace;padding:4px 8px;
  outline:none;cursor:pointer;max-width:220px;}
.sftp-close{color:#484f58;cursor:pointer;font-size:18px;padding:2px 6px;
  border-radius:4px;transition:all .15s;}
.sftp-close:hover{color:#ff7b72;background:#2a1a1a;}
.sftp-toolbar{display:flex;align-items:center;gap:6px;padding:10px 18px;
  border-bottom:1px solid #21262d;background:#0d1117;flex-shrink:0;flex-wrap:wrap;}
.sftp-path-bar{padding:6px 18px;background:#161b22;border-bottom:1px solid #21262d;
  font-family:monospace;font-size:11px;color:#58a6ff;flex-shrink:0;
  display:flex;align-items:center;gap:4px;flex-wrap:wrap;}
.sftp-crumb{cursor:pointer;color:#58a6ff;}
.sftp-crumb:hover{text-decoration:underline;}
.sftp-crumb-sep{color:#484f58;}
.sftp-table-wrap{flex:1;overflow-y:auto;}
#sftpTable{width:100%;border-collapse:collapse;font-size:12px;font-family:monospace;}
#sftpTable thead tr{background:#161b22;position:sticky;top:0;}
#sftpTable th{color:#484f58;font-size:9px;letter-spacing:.1em;padding:7px 12px;
  text-align:left;border-bottom:1px solid #21262d;font-weight:600;
  text-transform:uppercase;}
#sftpTable td{padding:6px 12px;border-bottom:1px solid #0d1117;
  color:#c9d1d9;vertical-align:middle;}
#sftpTable tr:hover td{background:#161b22;}
.sftp-icon{font-size:14px;margin-right:4px;}
.sftp-name{cursor:pointer;}
.sftp-name:hover{color:#58a6ff;text-decoration:underline;}
.sftp-dir .sftp-name{color:#79c0ff;}
.sftp-size{color:#484f58;font-size:11px;}
.sftp-date{color:#484f58;font-size:11px;}
.sftp-actions{display:flex;gap:4px;justify-content:flex-end;}
.sftp-act-btn{background:#21262d;border:1px solid #30363d;border-radius:4px;
  color:#c9d1d9;cursor:pointer;padding:2px 8px;font-size:11px;
  font-family:monospace;transition:all .15s;}
.sftp-act-btn:hover{border-color:#3fb950;color:#3fb950;}
.sftp-act-btn.del:hover{border-color:#ff7b72;color:#ff7b72;}
.sftp-status{padding:8px 18px;background:#161b22;border-top:1px solid #21262d;
  font-size:11px;font-family:monospace;flex-shrink:0;}
.sftp-status.ok{color:#3fb950;}
.sftp-status.err{color:#ff7b72;}
.sftp-status.busy{color:#d29922;}
.sftp-drop-zone{border:2px dashed #30363d;border-radius:8px;padding:20px;
  text-align:center;color:#484f58;font-size:12px;font-family:monospace;
  cursor:pointer;transition:all .2s;margin:0 18px 10px;}
.sftp-drop-zone:hover,.sftp-drop-zone.drag-over{
  border-color:#3fb950;color:#3fb950;background:#0f2419;}
.sftp-drop-zone input[type=file]{display:none;}
.sftp-empty{padding:40px;text-align:center;color:#484f58;font-family:monospace;}


/* ═══ Toasts (BATCH 9) ═════════════════════════════════════════════════════════
   Previously built entirely from an inline cssText in toast() (index.html). The values
   below are that string, property for property, so desktop appearance is unchanged —
   the differences are structural: toasts stack instead of overlapping, and the fade-in
   referenced by the old inline style finally exists. */
#toastStack {
  position:fixed;
  right:24px; bottom:24px;
  z-index:99999;
  display:flex; flex-direction:column; align-items:flex-end;
  gap:8px;
  pointer-events:none;   /* the stack must never swallow clicks on the UI beneath it */
}
.toast {
  background:#0d1f17; color:#3fb950; border:1px solid #3fb950;
  border-radius:8px;
  padding:11px 16px;
  font-size:13px;
  font-family:var(--mono,monospace);
  box-shadow:0 8px 24px rgba(0,0,0,.4);
  animation:tFade .2s ease;
  pointer-events:auto;
}
.toast.err { background:#2d1114; color:#f85149; border-color:#f85149; }
/* The animation the inline style asked for and never had. */
@keyframes tFade { from { opacity:0; transform:translateY(6px); } to { opacity:1; transform:translateY(0); } }


.term-pane.fullscreen {
  position: fixed !important;
  inset: 0 !important;
  z-index: 500 !important;
  border-radius: 0 !important;
}
.term-pane.fullscreen .xterm-container { height: calc(100vh - 34px) !important; height: calc(100dvh - 34px) !important; }
/* DEDUPE BATCH: second of three .tpfs-btn copies, likewise fully shadowed by the third.
   Its font-size:13px was the only property that would have changed anything, and it
   never applied — the surviving copy sets 14px. */


.term-ctx-menu {
  position:fixed; background:var(--bg2); border:1px solid var(--line);
  border-radius:8px; padding:4px; z-index:1000; min-width:150px;
  box-shadow:0 8px 24px rgba(0,0,0,.4); display:none;
}
.term-ctx-item {
  padding:7px 14px; font-size:12px; font-family:var(--mono);
  color:var(--t1); cursor:pointer; border-radius:5px; transition:background .1s;
  display:flex; align-items:center; gap:8px;
}
.term-ctx-item:hover { background:var(--bg3); }
.term-ctx-sep { height:1px; background:var(--line); margin:3px 6px; }


.tpane-badge {
  position:absolute; top:-4px; right:-4px;
  width:8px; height:8px; border-radius:50%;
  background:#f85149; border:2px solid var(--bg2);
  display:none; animation:badgePulse 1s infinite;
}
@keyframes badgePulse {
  0%,100%{opacity:1;transform:scale(1)}
  50%{opacity:.6;transform:scale(1.3)}
}
.term-pane-header { position:relative; }
.term-tab-badge {
  display:inline-block; background:#f85149; color:#fff;
  border-radius:10px; font-size:9px; padding:1px 5px;
  margin-left:4px; font-weight:700; vertical-align:middle;
}


/* ── Auth / User / Admin ──────────────────────────────────────────── */
#userBar{
  display:flex;align-items:center;gap:10px;padding:6px 14px;
  background:#0d1117;border-bottom:1px solid #21262d;
  font-size:11px;font-family:monospace;flex-shrink:0;
}
.ubar-user{color:#3fb950;font-weight:700;}
.ubar-role{
  background:#21262d;border-radius:4px;color:#8b949e;
  font-size:9px;padding:2px 7px;text-transform:uppercase;letter-spacing:.5px;
}
.ubar-spacer{flex:1;}
.ubar-btn{
  background:none;border:1px solid #30363d;border-radius:6px;
  color:#8b949e;cursor:pointer;font-size:11px;font-family:monospace;
  padding:4px 10px;transition:all .15s;
}
.ubar-btn:hover{border-color:#3fb950;color:#3fb950;}
.ubar-btn.danger:hover{border-color:#f85149;color:#f85149;}

/* ── Admin Modal ──────────────────────────────────────────────────── */
#adminOverlay{
  display:none;position:fixed;inset:0;background:rgba(0,0,0,.75);
  z-index:3000;backdrop-filter:blur(4px);
  align-items:center;justify-content:center;
}
#adminOverlay.open{display:flex;}
#adminBox{
  background:#0d1117;border:1px solid #30363d;border-radius:14px;
  width:560px;max-width:94vw;max-height:85vh;display:flex;
  flex-direction:column;box-shadow:0 24px 80px rgba(0,0,0,.9);overflow:hidden;
}
.adm-header{
  display:flex;align-items:center;gap:10px;padding:16px 20px;
  border-bottom:1px solid #21262d;background:#161b22;flex-shrink:0;
}
.adm-title{font-size:15px;font-weight:700;color:#e6edf3;flex:1;}
.adm-close{color:#484f58;cursor:pointer;font-size:18px;padding:2px 6px;border-radius:4px;}
.adm-close:hover{color:#f85149;background:#2a1a1a;}
.adm-tabs{display:flex;border-bottom:1px solid #21262d;background:#161b22;flex-shrink:0;}
.adm-tab{
  padding:10px 20px;font-size:12px;font-family:monospace;color:#8b949e;
  cursor:pointer;border-bottom:2px solid transparent;transition:all .15s;
  font-weight:600;letter-spacing:.3px;
}
.adm-tab.active{color:#3fb950;border-bottom-color:#3fb950;}
.adm-body{flex:1;overflow-y:auto;padding:20px;}
.adm-section{margin-bottom:24px;}
.adm-section h3{font-size:11px;color:#484f58;text-transform:uppercase;
  letter-spacing:.5px;margin-bottom:14px;font-weight:600;}
.adm-field{margin-bottom:12px;}
.adm-field label{display:block;font-size:11px;color:#8b949e;margin-bottom:5px;
  font-weight:600;text-transform:uppercase;letter-spacing:.3px;}
.adm-field input,.adm-field select{
  width:100%;background:#161b22;border:1px solid #30363d;border-radius:7px;
  color:#e6edf3;font-size:13px;padding:9px 12px;outline:none;
  transition:border-color .15s;font-family:monospace;
}
.adm-field input:focus,.adm-field select:focus{border-color:#3fb950;}
.adm-row{display:flex;gap:10px;}
.adm-row .adm-field{flex:1;}
.adm-btn{
  background:#238636;border:1px solid rgba(63,185,80,.4);border-radius:7px;
  color:#fff;font-size:12px;font-weight:600;padding:9px 18px;cursor:pointer;
  transition:all .15s;font-family:monospace;
}
.adm-btn:hover{background:#2ea043;}
.adm-btn.danger{background:#2d1114;border-color:rgba(248,81,73,.4);color:#f85149;}
.adm-btn.danger:hover{background:#3d1414;}
.adm-status{font-size:11px;font-family:monospace;padding:8px 10px;
  border-radius:6px;margin-top:10px;display:none;}
.adm-status.ok{background:#0f2419;color:#3fb950;border:1px solid #1e4a2a;display:block;}
.adm-status.err{background:#2d1114;color:#f85149;border:1px solid #4a1e1e;display:block;}
.user-table{width:100%;border-collapse:collapse;font-size:12px;font-family:monospace;}
.user-table th{color:#484f58;font-size:9px;letter-spacing:.1em;padding:6px 10px;
  text-align:left;border-bottom:1px solid #21262d;text-transform:uppercase;}
.user-table td{padding:8px 10px;border-bottom:1px solid #0d1117;color:#c9d1d9;
  vertical-align:middle;}
.user-table tr:hover td{background:#161b22;}
.role-badge{
  background:#21262d;border-radius:4px;font-size:9px;padding:2px 7px;
  color:#8b949e;text-transform:uppercase;letter-spacing:.4px;
}
.role-badge.admin{background:#0f2419;color:#3fb950;}
.u-actions{display:flex;gap:6px;}
.u-act{background:none;border:1px solid #30363d;border-radius:5px;color:#8b949e;
  cursor:pointer;font-size:10px;padding:3px 8px;font-family:monospace;transition:all .15s;}
.u-act:hover{border-color:#3fb950;color:#3fb950;}
.u-act.del:hover{border-color:#f85149;color:#f85149;}

/* ── Terminal Fullscreen ──────────────────────────────────────────── */
/* z-index 2500 -> 500 (2026-08-02).
   A fullscreen terminal pane is CONTENT, not a dialog. At 2500 it outranked every
   overlay in the app, so on desktop it painted over the SFTP manager (2000), the
   command palette (1000), the AI panel (1001) and its own context menu (1000) — open
   any of them while fullscreen and they were invisible behind the pane.

   500 puts it above all app chrome and below every dialog, which is the whole ladder:
     20 tabs · 100 .overlay · 149 backdrop · 150 sidebar · 200 topbar
     500 FULLSCREEN PANE
     1000 context menu + command palette · 1001 AI · 2000 SFTP · 3000 admin · 99999 toasts
   500 is not arbitrary: the (dead) .term-pane.fullscreen rule above already used it,
   so this makes the two fullscreen implementations agree.

   Desktop-only bug. On mobile switchMain() sets #view-terminal to position:relative
   with z-index:1, which creates a stacking context that confines this fixed element
   regardless of its value — so phones already layered dialogs above the pane, and the
   numbers alone would have told you the opposite. Nothing changes on mobile. */
.term-pane-fs{
  position:fixed!important;inset:0!important;z-index:500!important;
  border-radius:0!important;margin:0!important;
  width:100vw!important;height:100vh!important;
  height:100dvh!important;   /* batch 6 */
}
.tpfs-btn{
  cursor:pointer;color:var(--t3,#8b949e);padding:0 4px;font-size:14px;
  transition:color .15s;user-select:none;flex-shrink:0;
}
.tpfs-btn:hover{color:#3fb950;}
.term-pane-fs .tpfs-btn{color:#f0883e;}


/* ── Schedule ─────────────────────────────────────────────────────── */
.sched-lbl{display:block;font-size:10px;color:var(--t3,#8b949e);margin-bottom:5px;
  font-weight:600;text-transform:uppercase;letter-spacing:.3px;font-family:var(--mono,monospace);}
.sched-inp{width:100%;background:var(--bg,#0d1117);border:1px solid var(--line,#30363d);
  border-radius:7px;color:var(--t1,#e6edf3);font-size:13px;padding:8px 11px;outline:none;
  transition:border-color .15s;box-sizing:border-box;}
.sched-inp:focus{border-color:#3fb950;}
.sched-card{background:var(--bg2,#161b22);border:1px solid var(--line,#30363d);
  border-radius:10px;padding:14px 16px;margin-bottom:12px;display:flex;align-items:center;gap:14px;}
.sched-card.disabled{opacity:.5;}
.sched-card-main{flex:1;min-width:0;}
.sched-card-name{font-size:13px;font-weight:700;color:var(--t1,#e6edf3);margin-bottom:3px;}
.sched-card-cmd{font-family:var(--mono,monospace);font-size:11px;color:#58a6ff;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.sched-card-meta{font-family:var(--mono,monospace);font-size:10px;color:var(--t3,#8b949e);margin-top:4px;}
.sched-badge{display:inline-block;background:var(--bg,#0d1117);border:1px solid var(--line,#30363d);
  border-radius:4px;padding:2px 8px;font-size:9px;color:#8b949e;margin-right:6px;
  text-transform:uppercase;letter-spacing:.3px;}
.sched-badge.on{background:#0f2419;color:#3fb950;border-color:#1e4a2a;}
.sched-actions{display:flex;gap:6px;flex-shrink:0;}
.sched-act{background:var(--bg,#0d1117);border:1px solid var(--line,#30363d);border-radius:6px;
  color:#8b949e;cursor:pointer;padding:5px 10px;font-size:11px;font-family:var(--mono,monospace);
  transition:all .15s;}
.sched-act:hover{border-color:#3fb950;color:#3fb950;}
.sched-act.del:hover{border-color:#f85149;color:#f85149;}
.sched-act.run:hover{border-color:#d29922;color:#d29922;}
.sched-log-entry{background:var(--bg2,#161b22);border:1px solid var(--line,#30363d);
  border-radius:7px;padding:10px 12px;margin-bottom:8px;font-family:var(--mono,monospace);font-size:11px;}
.sched-log-head{display:flex;gap:10px;align-items:center;margin-bottom:6px;}
.sched-log-code{border-radius:4px;padding:1px 7px;font-size:9px;font-weight:700;}
.sched-log-code.ok{background:#0f2419;color:#3fb950;}
.sched-log-code.err{background:#2d1114;color:#f85149;}
.sched-log-out{color:#8b949e;white-space:pre-wrap;max-height:120px;overflow-y:auto;
  background:var(--bg,#0d1117);border-radius:5px;padding:8px;margin-top:4px;}


/* ── Schedule view containment fix ────────────────────────────────── */
#view-schedule{min-height:0;overflow-y:auto;}


/* tab bar always visible on mobile */


/* ── AI panel: always dark, regardless of light theme ───────────── */
body.light-theme #aiBox,
#aiBox {
  background:#0d1117 !important;
  border-color:#30363d !important;
  color:#e6edf3 !important;
}
body.light-theme #aiBox .ai-header,
#aiBox .ai-header { background:#161b22 !important; border-color:#21262d !important; }
body.light-theme #aiBox .ai-header-title { color:#e6edf3 !important; }
body.light-theme #aiBox .ai-tabs { background:#0d1117 !important; border-color:#21262d !important; }
body.light-theme #aiBox .ai-tab { color:#8b949e !important; }
body.light-theme #aiBox .ai-tab.active { color:#58a6ff !important; }
body.light-theme #aiBox .ai-body { background:#0d1117 !important; }
body.light-theme #aiBox .ai-label { color:#8b949e !important; }
body.light-theme #aiBox .ai-textarea,
body.light-theme #aiBox .ai-context-area {
  background:#161b22 !important; border-color:#30363d !important; color:#e6edf3 !important;
}
body.light-theme #aiBox .ai-result {
  background:#161b22 !important; border-color:#30363d !important; color:#e6edf3 !important;
}
body.light-theme #aiBox .ai-footer { background:#0d1117 !important; border-color:#21262d !important; color:#8b949e !important; }
body.light-theme #aiBox .ai-footer kbd { background:#21262d !important; border-color:#30363d !important; color:#8b949e !important; }
body.light-theme #aiBox code { color:#79c0ff !important; }
body.light-theme #aiBox .ai-provider-select { background:#161b22 !important; border-color:#30363d !important; color:#8b949e !important; }
body.light-theme #aiBox div { color:inherit; }
/* DEDUPE BATCH: a first, partial "same treatment for command palette" pass used to sit here
   — six rules, every one of them superseded by the fuller block immediately below.
   Five were identical value-for-value. The sixth, .cmd-footer, set color:#8b949e while
   the block below sets #484f58; both !important, later wins, so #484f58 is the colour
   that has been rendering. Deleting the shadowed copy changes nothing and removes the
   appearance of two rules disagreeing about the palette footer. */


/* ── Command palette: always dark in light theme (full) ─────────── */
body.light-theme #cmdBox {
  background:#0d1117 !important; border-color:#30363d !important; color:#e6edf3 !important;
}
body.light-theme #cmdBox #cmdSearch {
  background:#161b22 !important; border-color:#30363d !important; color:#e6edf3 !important;
}
body.light-theme #cmdBox #cmdSearch::placeholder { color:#484f58 !important; }
body.light-theme #cmdBox .cmd-tabs { background:#0d1117 !important; border-color:#21262d !important; }
body.light-theme #cmdBox .cmd-tab { color:#8b949e !important; }
body.light-theme #cmdBox .cmd-tab.active { color:#3fb950 !important; border-bottom-color:#3fb950 !important; }
body.light-theme #cmdBox .cmd-section-title { color:#8b949e !important; background:transparent !important; }
body.light-theme #cmdBox .cmd-item { background:transparent !important; color:#e6edf3 !important; }
body.light-theme #cmdBox .cmd-item:hover { background:#21262d !important; }
body.light-theme #cmdBox .cmd-item.focused { background:#21262d !important; outline-color:#3fb950 !important; }
body.light-theme #cmdBox .cmd-text code { color:#e6edf3 !important; background:transparent !important; }
body.light-theme #cmdBox .cmd-desc { color:#8b949e !important; }
body.light-theme #cmdBox .cmd-star { color:#484f58 !important; }
body.light-theme #cmdBox .cmd-star.fav { color:#e3b341 !important; }
body.light-theme #cmdBox .cmd-send { background:#161b22 !important; border-color:#30363d !important; color:#8b949e !important; }
body.light-theme #cmdBox .cmd-send:hover { color:#3fb950 !important; border-color:#3fb950 !important; }
body.light-theme #cmdBox .cmd-footer { background:#0d1117 !important; border-color:#21262d !important; color:#484f58 !important; }
body.light-theme #cmdBox .cmd-footer kbd { background:#21262d !important; border-color:#30363d !important; color:#8b949e !important; }
body.light-theme #cmdBox .cmd-empty { color:#484f58 !important; }
/* keep the category badges colored (they look good) */


/* ── IP Checker ───────────────────────────────────────────────────── */
.ipc-itab{padding:11px 16px;font-size:12px;color:var(--t2);border-bottom:2px solid transparent;cursor:pointer;font-weight:500;transition:all .14s;}
.ipc-itab.on{color:var(--green);border-bottom-color:var(--green);font-weight:600;}
.ipc-lbl{display:block;font-size:10px;color:var(--t3);margin-bottom:5px;text-transform:uppercase;letter-spacing:.4px;font-weight:600;}
.ipc-inp{background:var(--bg2);border:1px solid var(--line);border-radius:7px;color:var(--t1);padding:8px 11px;outline:none;transition:border-color .14s;box-sizing:border-box;}
.ipc-inp:focus{border-color:var(--green);}
.ipc-stat{background:var(--bg2);border:1px solid var(--line);border-radius:10px;padding:12px 20px;text-align:center;min-width:90px;}
.ipc-stat-n{font-size:22px;font-weight:700;color:var(--t1);font-family:var(--mono);}
.ipc-stat-l{font-size:10px;color:var(--t3);text-transform:uppercase;letter-spacing:.4px;margin-top:2px;}
.ipc-table th{text-align:left;font-size:9px;text-transform:uppercase;letter-spacing:.1em;color:var(--t3);padding:8px 10px;border-bottom:1px solid var(--line);font-weight:600;}
.ipc-table td{padding:8px 10px;border-bottom:1px solid var(--bg2);color:var(--t1);}
.ipc-table tr:hover td{background:var(--bg2);}
.ipc-verdict{border-radius:20px;padding:2px 10px;font-size:11px;font-weight:700;}
.ipc-saved-card{display:flex;align-items:center;gap:12px;background:var(--bg2);border:1px solid var(--line);border-radius:9px;padding:11px 14px;margin-bottom:9px;}
#ipcSummary{display:none;}
#ipcSummary.show{display:flex;}
#ipcToolbar{display:none;}
#ipcToolbar.show{display:flex;}

/* ═══ Responsive batch 2 (2026-07-31) — drawer backdrop & scroll ═══════════════
   Appended as its own block on purpose: batch 2 stays isolated and reviewable, and the
   media-query consolidation (batch 3) will absorb this along with the other blocks.
   Everything mobile-only lives inside the media query, so desktop is untouched. */

/* Static backdrop. Inert and invisible outside the breakpoint — display:none means it
   cannot intercept a single desktop click even if a stale .show class were left on it. */
#sidebarBackdrop{
  display:none;
  position:fixed;
  top:var(--header-h, 50px); left:0; right:0; bottom:0;
  background:rgba(0,0,0,.5);
  opacity:0; pointer-events:none;
  transition:opacity .3s ease;
  z-index:149;
}


/* ═══ Responsive — consolidated mobile block ═══════════════════════════════════
   Batch 3 (2026-07-31). Replaces EIGHT overlapping @media blocks that had accreted
   over four rounds of mobile patches and were overriding each other: three styled
   .sidebar, three styled .layout, two styled .main-tabs with mutually exclusive
   wrap-vs-scroll rules. Merged property-by-property, preserving the value that
   actually won the old cascade — not by concatenation.

   Deleted as dead: the .sidebar accordion (superseded by the fixed drawer), all
   .term-toolbar rules (that class matches NO element — the toolbar is an inline-styled
   div), .main{width:100vw} (already overridden by width:100%), and duplicate
   .fstats / .chips / #aiBox width declarations.

   Sits at end-of-file so it wins on source order; that removed 49 of the 54
   !important declarations the mobile CSS used to need. The 5 that remain are
   load-bearing — they beat INLINE styles written by termSetGrid()/switchMain() and
   by the elements' own style attributes, which source order cannot beat.

   Everything here is inside max-width queries, so desktop rendering is untouched. */

/* BATCH 5 (2026-07-31): breakpoint moved 768px → 999px. Small tablets in portrait
   (iPad Mini 744, iPad 810/834, iPad Pro 1024-landscape-of-smaller) previously fell
   through to the full desktop grid — a 310px fixed sidebar left ~490px for content and
   the layout visibly broke. They now get the drawer layout and touch sizing. The JS
   guards read MOBILE_MAX in index.html; keep the two in sync.
   Desktop is now >=1000px and is unchanged from batch 4. */
@media (max-width: 999px) {

  /* ── layout shell ───────────────────────────────────────────────────────── */
  html, body { height:auto; overflow-y:auto; overflow-x:hidden; }

  .layout {
    grid-template-columns:1fr;
    grid-template-rows:auto 1fr;
    position:relative;
    height:auto;
    min-height:0;
  }

  .main { grid-column:1; width:100%; min-height:0; }

  /* ── off-canvas drawer ──────────────────────────────────────────────────── */
  /* z-index is deliberately NOT set here: .sidebar{z-index:150!important} outside the
     media queries already supplies it and beat the old block's z-index:100. Declaring
     it would change the stacking order rather than preserve it. */
  .sidebar {
    position:fixed;
    top:var(--header-h, 50px); left:0; right:0; bottom:0;
    width:85vw;
    transform:translateX(-100%);
    transition:transform .3s ease;
    max-height:none;
    overflow-y:auto;
    /* The old accordion block set the `overflow` SHORTHAND to hidden and the drawer
       block overrode only overflow-y — so overflow-x was hidden. Kept explicit: with
       overflow-y non-visible, a `visible` x-axis computes to `auto` per spec and the
       drawer would grow a horizontal scrollbar. */
    overflow-x:hidden;
    /* BATCH 9: the drawer runs to bottom:0 and left:0, so on a home-bar phone its last
       server sits under the indicator, and in landscape its content runs under the
       notch. Padding rather than inset so the drawer's background still bleeds to the
       screen edge. */
    padding-bottom:env(safe-area-inset-bottom);
    padding-left:env(safe-area-inset-left);
    border-left:none;
    border-right:1px solid var(--line);
    border-bottom:1px solid var(--line);
  }
  .sidebar.mobile-open { transform:translateX(0); }

  #sidebarBackdrop { display:block; }
  #sidebarBackdrop.show { opacity:1; pointer-events:auto; }
  body.sidebar-open { overflow:hidden; }

  /* was a 200px cap with its own scrollbar — a nested scroll box inside the drawer */
  .srv-list { max-height:none; }

  /* ── topbar ─────────────────────────────────────────────────────────────── */
  /* BATCH 9 — safe-area insets. Every value below collapses to the previous one on a
     device with no insets (env() reports 0px), so this is a no-op everywhere except
     notched/home-bar hardware. The topbar grows rather than pads inward: it has a
     fixed height:50px, so padding-top alone would eat 50px of content instead of
     clearing the status bar. Batch 2's --header-h is measured from .layout's offsetTop
     at runtime, so the drawer follows the taller topbar automatically — nothing else
     needs to know. */
  .top   {
    gap:6px;
    padding-left:max(10px, env(safe-area-inset-left));
    padding-right:max(10px, env(safe-area-inset-right));
    padding-top:env(safe-area-inset-top);
    height:calc(50px + env(safe-area-inset-top));
  }
  .logo  { font-size:11px; }
  /* gap is unobservable while display:none, but preserved so the resolved cascade is
     provably value-for-value identical rather than merely equivalent-in-practice. */
  .chips { display:none; gap:3px; }

  .mobile-menu-btn {
    display:flex;
    align-items:center; justify-content:center;
    width:44px; height:44px; border-radius:8px;   /* batch 4: was 32px */
    border:1px solid var(--line); background:var(--bg3);
    color:var(--t2); cursor:pointer; font-size:18px;
    flex-shrink:0;
  }

  /* ── tab bar — single-row scroll strip (batch 4) ─────────────────────────── */
  /* Was flex-wrap:wrap, which stacked 6 tabs into two rows of 10px labels. Now one
     nowrap row that scrolls horizontally, so labels stay legible at 12px and tabs get
     a 44px touch height. flex:0 0 auto is essential — flex:1 would stretch the tabs to
     fill the bar and there would be nothing to scroll. switchMain() scrolls the active
     tab into view (index.html) for tabs reached programmatically rather than by tap. */
  .main-tabs {
    display:flex; position:sticky; top:0; z-index:20;
    background:var(--bg2);
    flex-wrap:nowrap;
    overflow-x:auto; overflow-y:hidden;
    white-space:nowrap;
    scroll-snap-type:x proximity;
    -webkit-overflow-scrolling:touch;
    scrollbar-width:none;              /* Firefox */
  }
  .main-tabs::-webkit-scrollbar { display:none; }   /* Safari/Chrome */

  .mtab {
    flex:0 0 auto;
    scroll-snap-align:start;
    min-height:44px;
    display:flex; align-items:center;
    font-size:12px;
    padding:0 14px;
    min-width:auto;
  }

  /* ── views ──────────────────────────────────────────────────────────────── */
  .console { font-size:11px; }
  .btn     { font-size:11px; padding:6px 10px; }
  .sb-sec  { padding:10px 12px; }

  .fstats { grid-template-columns:repeat(2, 1fr); }
  .fv     { font-size:20px; }

  #view-schedule {
    position:relative;
    height:auto;
    min-height:calc(100vh - 130px);
    z-index:1;
  }

  /* ── terminal ───────────────────────────────────────────────────────────── */
  /* !important is load-bearing below: switchMain() writes inline styles on
     #view-terminal and #termGrid carries inline grid/overflow values that
     termSetGrid() rewrites at runtime. Source order cannot beat an inline style. */

  /* BATCH 6: was height:auto + min-height:calc(100vh - 90px), which let the view grow
     past the screen (so the page scrolled and the drawer detached), measured against
     100vh — a unit that on mobile excludes the URL bar and ignores the keyboard
     entirely. --term-h is measured from visualViewport by syncTermHeight(). The
     fallback chain matters: --term-h is unset until the first measurement lands. */
  #view-terminal {
    height:var(--term-h, calc(100vh - 90px)) !important;
    height:var(--term-h, calc(100dvh - 90px)) !important;
    min-height:0 !important;
    overflow:hidden !important;
  }

  #termGrid {
    grid-template-columns:1fr !important;
    grid-template-rows:repeat(auto-fill, 420px) !important;
    overflow-y:auto !important;    /* was visible — the view now owns the height */
    min-height:0 !important;
  }

  /* ── toolbar overflow (batch 6) ──────────────────────────────────────────── */
  #tbMoreBtn {
    display:inline-flex; align-items:center; justify-content:center;
    min-width:44px; min-height:44px;
    background:var(--bg3); border:1px solid var(--line); border-radius:6px;
    color:var(--t2); font-size:18px; cursor:pointer; flex-shrink:0;
  }
  #view-terminal.tb-open #tbMoreBtn { border-color:var(--green); color:var(--gt); }
  /* ☰ Menu first on a narrow toolbar — reordered in CSS so the DOM (and therefore the
     desktop tab order) is untouched. */
  .term-tbar #termBackBtn { order:-1; }

  #termBackBtn {
    display:flex;
    align-items:center;
    min-height:44px;          /* batch 4 — no class="btn", so it missed the .btn rule */
    gap:6px;
    font-size:12px;
    padding:4px 14px;
    background:var(--bg3);
    border:1px solid var(--line);
    border-radius:6px;
    color:var(--t2);
    cursor:pointer;
  }

  /* ── overlays ───────────────────────────────────────────────────────────── */
  #aiBox, #cmdBox {
    width:95vw;
    max-height:85vh;
    margin-top:20px;
  }

  /* batch 4: the real top offset lives on the OVERLAY (80px for cmd, 60px for ai),
     not on the box. The old 480px block put padding-top:20px on the box, which added
     inner padding instead of reducing the offset it was trying to cancel. Fixed at
     source; on a phone the palette now opens 40px from the top instead of ~100px. */
  #cmdOverlay.open, #aiOverlay.open { padding-top:20px; }

  /* ── touch targets (batch 4) ─────────────────────────────────────────────── */
  /* Apple HIG asks 44x44, Material 48x48. Everything here was 20–32px. min-height is
     used rather than height so nothing that already exceeds 44px shrinks, and
     inline-flex+align-items keeps existing labels vertically centred as they grow. */
  .btn, .btn.sm {
    min-height:44px;
    display:inline-flex; align-items:center; justify-content:center;
  }
  .btn.sm { font-size:11px; padding:6px 12px; }

  .srv-item      { min-height:44px; }          /* the primary server picker */
  .stab          { min-height:44px; display:flex; align-items:center; justify-content:center; }
  .ubar-btn      { min-height:44px; }
  .adm-tab,
  .ipc-itab      { min-height:44px; display:flex; align-items:center; }
  .sftp-act-btn  { min-height:44px; min-width:44px; }
  .key-select,
  .proto-select  { min-height:44px; }
  /* filter chips are a compact multi-select row, not primary controls: 34px keeps a
     comfortable tap target without a 5-chip row eating 88px of sidebar on two lines. */
  .srv-pchip     { min-height:34px; padding:6px 10px; font-size:10px; }

  /* Icon-only controls: square so the target is 44px in both axes. */
  .sftp-close, .adm-close, .key-del, .srv-search-clear, .theme-btn {
    min-width:44px; min-height:44px;
    display:inline-flex; align-items:center; justify-content:center;
  }

  /* ── iOS input zoom (batch 4) ────────────────────────────────────────────── */
  /* Mobile Safari force-zooms the page on focus for any field under 16px and does not
     zoom back out. Nearly every input here was 11–12px, so tapping any field left the
     layout zoomed. 16px is the threshold, not a preference. Range inputs and
     checkboxes are excluded — font-size does nothing for them and 16px would only
     disturb their metrics.

     !important is load-bearing, same rationale as #termGrid: SEVEN of these fields
     carry an inline font-size (#termBroadcast, #ipcManual, #ipcFilter, #ipcWsUrl,
     #termGsel, #fontSelect, the .key-select pair). Source order cannot beat a style
     attribute, so without !important the worst offenders — the ones you actually type
     into — would keep triggering the zoom. */
  input:not([type=range]):not([type=checkbox]):not([type=radio]),
  select,
  textarea,
  .inp, .ipc-inp, .srv-search-inp, .key-select, .ai-textarea, .ai-context-area {
    font-size:16px !important;
  }

  /* ── BATCH 5: broadcast input ────────────────────────────────────────────── */
  /* Carried an inline width:280px, which forced the terminal toolbar to wrap an extra
     row on every narrow screen. !important beats the style attribute; flex-basis lets
     it take the leftover room instead of dictating it. */
  #termBroadcast {
    width:auto !important;
    flex:1 1 160px;
    min-width:0;
  }

  /* ── BATCH 7: virtual key bar ────────────────────────────────────────────── */
  /* A soft keyboard has no Esc, Tab, Ctrl or arrows, so on a phone you could not send
     Ctrl-C, escape vim, tab-complete, or reach shell history at all — the terminal was
     visible but not really usable. Sits above the grid; batch 6 can move it to hover
     over the soft keyboard once visualViewport sizing lands. */
  #termKeys {
    display:flex;
    gap:6px;
    padding:6px 8px;
    background:var(--bg2);
    border-bottom:1px solid var(--line);
    overflow-x:auto;
    flex-shrink:0;
    scrollbar-width:none;
    -webkit-overflow-scrolling:touch;
  }
  #termKeys::-webkit-scrollbar { display:none; }

  .tkey {
    flex:0 0 auto;
    min-width:44px; min-height:44px;
    display:inline-flex; align-items:center; justify-content:center;
    padding:0 12px;
    background:var(--bg3);
    border:1px solid var(--line);
    border-radius:8px;
    color:var(--t1);
    font-family:var(--mono);
    font-size:13px;
    cursor:pointer;
    user-select:none;
    -webkit-tap-highlight-color:transparent;
  }
  .tkey:active { background:var(--bg4); }
  /* Ctrl is a sticky modifier — armed until the next character is typed. */
  .tkey.armed {
    background:var(--gbg);
    border-color:var(--green);
    color:var(--gt);
  }

  /* ── BATCH 8 — SFTP file manager ─────────────────────────────────────────── */
  /* Applies to phone AND tablet; the 699px block below adds the card layout on top.

     The overflow source is NOT this stylesheet. index.html hard-codes 368px of inline
     <th style="width:…"> across four of the five columns, and #sftpTable declares no
     table-layout — so `width:100%` acts as a floor, not a ceiling, and auto layout
     expands past the box to satisfy content min-widths. Add ~120px of cell padding and
     the table demands ~490px inside a box that is ~370px wide on a phone.

     Two different answers by tier: below 700px the card layout hides <thead>, which
     retires those inline attributes without touching the markup; between 700 and 999
     the table stays, so it needs a real horizontal scroller. */
  .sftp-table-wrap { overflow-x:auto; -webkit-overflow-scrolling:touch; }

  /* .sftp-act-btn got min-height in batch 4 — but one of the six toolbar controls is a
     <label> (the Upload picker, index.html:3204), and min-height does nothing on an
     inline box. That control alone stayed ~20px tall while the five <button> siblings
     grew. Every other rule in the batch-4 group already sets inline-flex; this was the
     omission, not a new decision. */
  .sftp-act-btn { display:inline-flex; align-items:center; justify-content:center; }

  /* Six 44px controls wrap to three or four rows inside a ~370px box, pushing the file
     list — the entire point of the dialog — below the fold. Same scroll strip as the
     main tab bar in batch 4: flex:0 0 auto is load-bearing, since flex:1 would stretch
     the buttons to fit and leave nothing to scroll. */
  .sftp-toolbar {
    flex-wrap:nowrap;
    overflow-x:auto;
    scrollbar-width:none;
    -webkit-overflow-scrolling:touch;
  }
  .sftp-toolbar::-webkit-scrollbar { display:none; }
  .sftp-toolbar .sftp-act-btn { flex:0 0 auto; }

  /* The header wants 220px of <select> plus a 44px close button before .sftp-title
     (flex:1) gets anything, so the title collapses to a few characters. Giving the
     select its own row costs one line and keeps all three legible. order:3 moves it
     after the close button without touching the DOM or the tab order. */
  .sftp-header   { flex-wrap:wrap; }
  #sftpServerSel { order:3; flex:1 1 100%; max-width:none; min-width:0; }

  /* ── BATCH 9 — toasts on touch ───────────────────────────────────────────── */
  /* Anchored bottom-right at 24px, a toast landed on the home indicator and, with a
     long message, ran off the right edge — it has no max-width and the text does not
     wrap usefully at that corner. Full-width strip instead, lifted clear of the
     indicator. align-items:stretch so a long message wraps across the strip rather
     than pushing itself off-screen. */
  #toastStack {
    left:12px;
    right:12px;
    bottom:calc(12px + env(safe-area-inset-bottom));
    align-items:stretch;
  }
  .toast { font-size:14px; }

  /* ── BATCH 10 — long-press context menu ──────────────────────────────────── */
  /* 7px padding at 12px type gives ~28px rows: fine for a mouse, too small for a
     thumb, and these sit directly under the finger that opened them. */
  .term-ctx-menu { min-width:210px; padding:6px; }
  .term-ctx-item { min-height:44px; font-size:14px; padding:0 16px; }
  .term-ctx-sep  { margin:6px; }

  /* iOS pops its own selection callout on a long press, which would fight this menu
     and leave the magnifier on screen behind it. This disables that one behaviour
     WITHOUT touching user-select: the xterm helper textarea must stay selectable or
     typing and IME break, so the blunter fix is not available here. */
  [id^="xterm-container-"] { -webkit-touch-callout:none; }
}

/* ═══ BATCH 5 — small-tablet tier ══════════════════════════════════════════════
   Refinements on top of the phone layout for 700–999px. Everything not listed here
   inherits the phone treatment above, which is the intent: a tablet is still a touch
   device and still wants 44px targets and 16px inputs. Terminal grid columns are
   deliberately NOT touched here — terminal layout is batch 6, kept isolated. */
@media (min-width: 700px) and (max-width: 999px) {
  /* 85vw is right on a phone but would be ~870px on a 1024px tablet. */
  .sidebar { width:340px; }

  /* Room for four across again, and for the status chips the phone layout hides. */
  .fstats { grid-template-columns:repeat(4, 1fr); }
  .chips  { display:flex; }

  /* BATCH 9: restated in longhand for the same reason as the phone tier — the old
     `padding:0 16px` shorthand sits in a LATER block than the phone rule and would
     otherwise reset padding-top back to 0, discarding the status-bar clearance on any
     tablet that has one (iPads do, in both orientations). */
  .top     {
    gap:10px;
    padding-left:max(16px, env(safe-area-inset-left));
    padding-right:max(16px, env(safe-area-inset-right));
    padding-top:env(safe-area-inset-top);
    height:calc(50px + env(safe-area-inset-top));
  }
  .mtab    { font-size:13px; padding:0 18px; }
  .console { font-size:12px; }
}

/* The 480px block is gone as of batch 4. Its last declaration was
   `#aiBox,#cmdBox{padding-top:20px}`, which batch 3 preserved verbatim despite being
   mistargeted; batch 4 fixed the actual offset on #cmdOverlay/#aiOverlay inside the
   768px block, leaving this block empty. Batch 5 adds the min-width tablet tier. */


/* ═══ BATCH 6 — phone terminal: single pane + collapsed toolbar ════════════════
   Below 700px only. Tablets (700–999) keep the multi-pane grid; a 2x2 grid on a
   390px screen gives ~195px panes, roughly 24 columns — not a terminal. */
@media (max-width: 699px) {

  /* Toolbar: keep ☰ Menu, Connect, SFTP, AI, Commands and the ⋯ toggle; everything
     marked .tb-more collapses behind it. The grid selector goes entirely — there is
     no grid to choose from in single-pane mode. */
  .term-tbar .tb-more   { display:none; }
  .term-tbar .tb-grid   { display:none; }
  .term-tbar .tb-spacer { display:none; }
  #tbMoreBtn            { display:inline-flex; }

  /* Explicit per-type display when expanded: `display:inline-flex` is wrong for the
     broadcast <input> and for the slider wrapper, and `revert` is not safe far enough
     back in mobile Safari. */
  #view-terminal.tb-open .term-tbar .tb-more                  { display:inline-flex; }
  #view-terminal.tb-open .term-tbar input.tb-more             { display:inline-block; }
  #view-terminal.tb-open .term-tbar .font-slider-wrap.tb-more { display:flex; }

  /* Single pane. Panes stay in the DOM and stay connected — only visibility changes,
     so no session is dropped by switching. */
  #termGrid.single                        { grid-template-rows:1fr !important; }
  #termGrid.single .term-pane             { display:none; }
  #termGrid.single .term-pane.pane-active { display:flex; }

  /* Pane switcher strip */
  #termPaneBar {
    display:flex;
    gap:6px;
    padding:6px 8px;
    background:var(--bg2);
    border-bottom:1px solid var(--line);
    overflow-x:auto;
    flex-shrink:0;
    scrollbar-width:none;
    -webkit-overflow-scrolling:touch;
  }
  #termPaneBar:empty { display:none; }
  #termPaneBar::-webkit-scrollbar { display:none; }

  .tpchip {
    flex:0 0 auto;
    min-height:44px;
    max-width:45vw;
    display:inline-flex; align-items:center;
    gap:6px; padding:0 12px;
    background:var(--bg3);
    border:1px solid var(--line);
    border-radius:8px;
    color:var(--t2);
    font-family:var(--sans);
    font-size:12px;
    white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
    cursor:pointer;
    -webkit-tap-highlight-color:transparent;
  }
  .tpchip.on { background:var(--gbg); border-color:var(--green); color:var(--gt); font-weight:600; }
  /* Connection state, mirroring the pane header dot. */
  .tpchip.ok::before   { content:'●'; color:var(--green); font-size:9px; }
  .tpchip.err::before  { content:'●'; color:var(--red);   font-size:9px; }
  .tpchip.wait::before { content:'●'; color:var(--amber); font-size:9px; }

  /* ── BATCH 8 — SFTP: full-height sheet + card rows ───────────────────────── */
  /* A 96vw x 82vh dialog floating 40px down the screen spends the one dimension a
     phone has on decoration. Full sheet instead. dvh tracks the collapsing URL bar;
     the vh pair stays as the fallback for engines without it — same duplicate-
     declaration pattern as batch 6. */
  #sftpOverlay { padding-top:0; }
  #sftpBox {
    width:100%; max-width:100%;
    height:100vh;  max-height:100vh;
    height:100dvh; max-height:100dvh;
    border:none; border-radius:0;
  }

  /* Card rows. The table display types are turned off wholesale, and that is precisely
     what retires the inline <th style="width:…"> attributes — a stylesheet cannot beat
     a style attribute on width without !important, but a hidden <thead> makes those
     widths unreachable rather than merely outranked.

     Rows become a four-column grid:

        ┌──────┬──────────────────────────────┬─────────┐
        │      │ name  (spans cols 2-3)       │         │
        │ icon ├──────────────┬───────────────┤ actions │
        │      │ size         │ modified      │         │
        └──────┴──────────────┴───────────────┴─────────┘

     Explicit grid-area placement rather than flex-wrap: the five <td>s arrive in DOM
     order icon/name/size/date/actions, and wrap order alone cannot put size+date on a
     shared second line while icon and actions span both rows. */
  #sftpTable, #sftpTable tbody { display:block; }
  #sftpTable thead { display:none; }

  #sftpTable tr {
    display:grid;
    grid-template-columns:auto auto 1fr auto;
    column-gap:10px; row-gap:2px;
    align-items:center;
    padding:8px 12px;
    border-bottom:1px solid #21262d;
  }
  #sftpTable td { display:block; padding:0; border:none; }

  .sftp-icon     { grid-area:1 / 1 / 3 / 2; font-size:20px; margin-right:0; }
  .sftp-namecell { grid-area:1 / 2 / 2 / 4; min-width:0; }
  .sftp-size     { grid-area:2 / 2 / 3 / 3; }
  .sftp-date     { grid-area:2 / 3 / 3 / 4; }

  /* Restated at ID specificity: the base .sftp-actions{display:flex} (0,1,0) loses to
     the #sftpTable td{display:block} above (1,0,1), which would stack the two buttons
     vertically and make every row ~100px tall. */
  #sftpTable td.sftp-actions { display:flex; gap:6px; justify-content:flex-end; }

  /* Long filenames are the norm on a server and there is no truncation that still lets
     you tell two builds apart — wrap rather than ellipsize. */
  .sftp-name { display:block; font-size:13px; line-height:1.35; overflow-wrap:anywhere; }

  /* The loading / empty-directory placeholder is one colspan=5 cell; span the grid. */
  #sftpTable td.sftp-empty { grid-column:1 / -1; padding:40px 12px; }

  /* Cards cannot overflow sideways, so the tablet scroller is turned back off — a
     single wide cell should reflow, not introduce a horizontal scroll on a phone. */
  .sftp-table-wrap { overflow-x:hidden; }

  /* Drag-and-drop does not exist on touch, and the strip costs ~62px in a sheet whose
     whole purpose is the file list. The toolbar's Upload picker is the touch path. */
  #sftpDropZone { display:none; }

  /* 18px side padding is desktop-scale; reclaim 12px of width per row. */
  .sftp-header   { padding:10px 12px; }
  .sftp-toolbar  { padding:8px 12px; }
  .sftp-path-bar { padding:6px 12px; }
  .sftp-status   { padding:8px 12px; }

  /* ── BATCH 9 — safe-area on the two full-bleed surfaces ──────────────────── */
  /* The SFTP sheet is 100dvh with square corners, so its header runs under the status
     bar and its status line under the home indicator. Padding is added to the existing
     values rather than replacing them, so spacing is unchanged on hardware with no
     insets. */
  .sftp-header {
    padding-top:calc(10px + env(safe-area-inset-top));
    padding-left:calc(12px + env(safe-area-inset-left));
    padding-right:calc(12px + env(safe-area-inset-right));
  }
  .sftp-status {
    padding-bottom:calc(8px + env(safe-area-inset-bottom));
    padding-left:calc(12px + env(safe-area-inset-left));
    padding-right:calc(12px + env(safe-area-inset-right));
  }

  /* The terminal is sized to var(--term-h) from visualViewport.height, which counts the
     home-indicator strip as usable — so with viewport-fit=cover the bottom row of the
     shell now paints underneath it. box-sizing:border-box is global (app.css:32), so
     padding here shrinks the content box and the fit() call gives back one fewer row
     instead of hiding it. */
  #view-terminal { padding-bottom:env(safe-area-inset-bottom); }
}
