:root{
  --petrol:#17242a;      /* sidebar */
  --petrol-2:#1f3038;    /* sidebar hover */
  --paper:#f2f4f3;       /* content background */
  --card:#ffffff;
  --ink:#1e2a2b;
  --mist:#647576;
  --hair:#d9e0de;
  --line:#0e7c6b;        /* primary action */
  --line-2:#0a5f52;
  --drift:#b26a00;       /* unapplied changes */
  --down:#b3402e;
  --up:#2e7d4f;
  --field:#ffffff;       /* the inside of an input */
  --pill-up:#e4f1e9;
  --pill-down:#f7e6e2;
  --pill-warn:#f6ecdd;
  --pill-off:#eceff0;
  --note-bg:#f6ecdd;     /* the read-only banner */
  --note-edge:#e3cfa8;
  --note-ink:#7a4d00;
  --on-solid:#ffffff;    /* text on a filled button */
  --shade:rgba(15,25,30,.55);   /* behind a dialog */
  --spark-fill:rgba(62,207,168,.18);
  --spark-fill-bad:rgba(220,80,80,.22);
  --mono:ui-monospace,"SF Mono","Cascadia Code",Consolas,Menlo,monospace;
  --sans:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
}

/* Dark. The sidebar and the log view were always dark, so they are unchanged;
   what turns over is the content beside them. Applied by a data-theme on the
   root element, and by the operating system's preference when the setting is
   left at "system" -- which is what data-theme="" means here. */
:root[data-theme=dark]{
  --paper:#12191c;
  --card:#182226;
  --ink:#dbe5e4;
  --mist:#93a5a5;
  --hair:#2a373b;
  --line:#3ecfa8;
  --line-2:#2fae8d;
  --drift:#e0a44a;
  --down:#f08a72;
  --up:#5fc98d;
  --field:#0f171a;
  --pill-up:#173026;
  --pill-down:#33201d;
  --pill-warn:#332a1a;
  --pill-off:#232e31;
  --note-bg:#2a2317;
  --note-edge:#4a3c22;
  --note-ink:#e8c07d;
  --on-solid:#0f171a;
  --shade:rgba(0,0,0,.62);
  --spark-fill:rgba(62,207,168,.16);
  --spark-fill-bad:rgba(240,138,114,.20);
}
/* What the browser paints for itself -- the inside of a dropdown, scrollbars,
   a checkbox -- follows the theme too. Without this a menu opens white on a
   dark page. */
:root{color-scheme:light}
:root[data-theme=dark]{color-scheme:dark}
*{box-sizing:border-box;margin:0}
/* The hidden attribute always wins. The browser's own [hidden]{display:none}
   loses to any display a class sets -- .frm says display:grid, so a hidden
   form stayed exactly where it was, which is how the editor's cfg tab showed
   both panes at once. */
[hidden]{display:none!important}
body{font:14px/1.5 var(--sans);color:var(--ink);background:var(--paper);display:flex;min-height:100vh}
a{color:var(--line)}
button{font:inherit;cursor:pointer}

/* ---- sidebar ---- */
/* The sidebar stays put while the page scrolls: it is as tall as the window,
   sticks to the top, and scrolls its own links if the menu outgrows it. Without
   align-self it would stretch to the page height and sticky would do nothing. */
#nav{width:232px;flex:0 0 232px;background:var(--petrol);color:#c8d4d2;
  display:flex;flex-direction:column;position:sticky;top:0;align-self:flex-start;
  height:100vh;height:100dvh}
#navlinks{flex:1;min-height:0;overflow-y:auto}
#nav .brand{flex:none;display:flex;align-items:center;gap:9px;padding:16px 16px 13px;font:600 15px/1.3 var(--mono);color:#fff;letter-spacing:.02em;border-bottom:1px solid rgba(255,255,255,.08)}
#nav .brand img{width:26px;height:26px;flex:none;border-radius:6px}
#nav .brand .brand2{font-weight:400;color:#8fb9b1;letter-spacing:.01em}
#nav .brand small{display:block;font-weight:400;color:#7d918f;font-size:11px;margin-top:2px}
#nav .grp{font:600 10px/1 var(--mono);letter-spacing:.14em;color:#7d918f;padding:16px 16px 6px;text-transform:uppercase}
#nav button.grp{display:flex;align-items:center;gap:7px;width:100%;background:none;border:0;text-align:left;
  padding:14px 16px 8px;cursor:pointer;font:600 10px/1 var(--mono);letter-spacing:.14em;color:#7d918f;text-transform:uppercase}
#nav button.grp:hover{color:#c8d4d2}
#nav button.grp .caret{display:inline-block;width:8px;transition:transform .12s ease}
#nav button.grp[aria-expanded=true] .caret{transform:rotate(90deg)}
#nav button.grp .count{margin-left:auto;font-size:9px;opacity:.65}
#nav a.item{display:block;padding:7px 16px;color:#c8d4d2;text-decoration:none;font-size:13px;border-left:3px solid transparent}
#nav a.item:hover{background:var(--petrol-2)}
#nav a.item.on{background:var(--petrol-2);color:#fff;border-left-color:var(--line)}
/* The sidebar is its own scroll area: the links scroll, the account block stays
   on screen. Otherwise signing out means scrolling to the end of a long menu. */
#nav .foot{margin-top:auto;flex:none;padding:12px 16px;
  border-top:1px solid rgba(255,255,255,.08);background:var(--petrol)}
#nav .foot input{width:100%;background:#0f191e;border:1px solid #2c3e44;color:#dfe8e6;font:12px var(--mono);padding:6px 8px;border-radius:4px}
#nav .foot label{font:10px var(--mono);letter-spacing:.1em;color:#7d918f;text-transform:uppercase;display:block;margin-bottom:4px}

/* ---- main ---- */
#main{flex:1;min-width:0;display:flex;flex-direction:column}
#topbar{display:flex;align-items:center;gap:12px;padding:14px 24px;background:var(--card);border-bottom:1px solid var(--hair)}
#topbar h1{font:600 16px/1.2 var(--sans);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* The hamburger only exists where the sidebar has become a drawer. */
#navtoggle{display:none;flex:none;width:38px;height:34px;padding:8px 7px;margin-left:-6px;
  background:none;border:1px solid transparent;border-radius:5px}
#navtoggle span{display:block;height:2px;border-radius:2px;background:var(--ink)}
#navtoggle span+span{margin-top:4px}
#navtoggle:hover{border-color:var(--hair)}
#scrim{display:none;position:fixed;inset:0;background:var(--shade);z-index:15}
#nodestrip{margin-left:auto;display:flex;align-items:center;gap:8px;font:12px var(--mono)}
.chip{padding:3px 9px;border-radius:4px;border:1px solid var(--hair);background:var(--paper)}
.chip.role-active{border-color:var(--up);color:var(--up)}
.chip.role-passive{border-color:var(--mist);color:var(--mist)}
.chip.role-standalone{border-color:var(--hair);color:var(--mist)}
.chip.drift{border-color:var(--drift);color:var(--drift)}
#content{padding:22px 24px;max-width:1140px;width:100%}
#banner{padding:12px 24px 0;max-width:1140px;width:100%}
#banner .ro{background:var(--note-bg);border:1px solid var(--note-edge);color:var(--note-ink);border-radius:6px;padding:11px 14px;font-size:13px;display:flex;gap:12px;align-items:flex-start}
#banner .ro b{display:block;margin-bottom:2px}

/* ---- generic ---- */
.card{background:var(--card);border:1px solid var(--hair);border-radius:6px;margin-bottom:18px}
.card>.hd{display:flex;align-items:center;gap:10px;padding:12px 16px;border-bottom:1px solid var(--hair)}
.card>.hd h2{font:600 12px/1 var(--mono);letter-spacing:.08em;text-transform:uppercase;color:var(--mist)}
.card>.hd .sp{flex:1}
.card>.bd{padding:16px}
.hint{color:var(--mist);font-size:12px;margin-top:4px}
table{width:100%;border-collapse:collapse}
th{font:600 11px var(--mono);letter-spacing:.06em;text-transform:uppercase;color:var(--mist);text-align:left;padding:8px 12px;border-bottom:1px solid var(--hair)}
td{padding:9px 12px;border-bottom:1px solid var(--hair);vertical-align:top}
tr:last-child td{border-bottom:0}
td .sub{color:var(--mist);font-size:12px}
.mono{font-family:var(--mono);font-size:12.5px}
.empty{padding:26px 16px;color:var(--mist);text-align:center}
.btn{border:1px solid var(--hair);background:var(--card);color:var(--ink);padding:6px 12px;border-radius:4px;font-size:13px}
.btn:hover{border-color:var(--mist)}
.btn.pri{background:var(--line);border-color:var(--line);color:var(--on-solid)}
.btn.pri:hover{background:var(--line-2)}
.btn.warn{background:var(--drift);border-color:var(--drift);color:var(--on-solid)}
.btn.dngr{color:var(--down);border-color:var(--hair)}
.btn.sm{padding:3px 8px;font-size:12px}
.btn[disabled]{opacity:.5;cursor:default}
.pill{display:inline-block;padding:2px 8px;border-radius:10px;font:12px var(--mono)}
.pill.up{background:var(--pill-up);color:var(--up)}
.pill.down{background:var(--pill-down);color:var(--down)}
.pill.off{background:var(--pill-off);color:var(--mist)}
.pill.warn{background:var(--pill-warn);color:var(--drift)}
.logbar{display:flex;flex-wrap:wrap;gap:10px 16px;align-items:center;padding:12px 16px;border-bottom:1px solid var(--hair)}
.logbar label{display:flex;gap:5px;align-items:center;font-size:13px;white-space:nowrap}
.logview{margin:0;max-height:62vh;overflow:auto;background:#10191c;padding:10px 0}
.logview table{width:100%;border-collapse:collapse;font:12px/1.55 var(--mono)}
.logview td{padding:1px 10px;vertical-align:top;border:0;color:#c8d4d6}
.logview td.t{white-space:nowrap;color:#6d8085;width:1%}
.logview td.s{white-space:nowrap;width:1%;font-size:11px;letter-spacing:.4px;text-transform:uppercase}
.logview td.m{white-space:pre-wrap;word-break:break-word}
.logview tr.ERROR td.m{color:#ff9c88}
.logview tr.WARNING td.m{color:#f0c46a}
.logview tr.DEBUG td.m{color:#8296997d;color:#7e9195}
.logview tr:hover td{background:#1b282c}
.src-manager{color:#7ec9b8}.src-haproxy{color:#8fb8e8}.src-acme{color:#d9a7dd}.src-keepalived{color:#e8c07d}
.logempty{padding:26px 16px;color:#6d8085;font:13px var(--mono)}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:14px}
.grid.row5{grid-template-columns:repeat(5,1fr)}
@media (max-width:1000px){.grid.row5{grid-template-columns:repeat(auto-fill,minmax(150px,1fr))}}
.stat{border:1px solid var(--hair);border-radius:6px;padding:12px 14px;background:var(--card)}
.stat .k{font:600 10px var(--mono);letter-spacing:.1em;text-transform:uppercase;color:var(--mist)}
.stat .v{font:600 16px/1.3 var(--mono);margin-top:6px}
pre{font:12px/1.5 var(--mono);background:#0f191e;color:#d5e2e0;padding:14px;border-radius:6px;overflow:auto;max-height:60vh;white-space:pre-wrap}

/* ---- forms ---- */
.frm{display:grid;grid-template-columns:190px 1fr;gap:12px 16px;align-items:start}
.frm label.fl{font:600 12px var(--mono);color:var(--mist);padding-top:7px;text-align:right}
/* A section heading in a form: across both columns, a hairline above all
   but the first, so a long form reads as a few short ones. */
.frm .fsec{grid-column:1/-1;font:600 11px/1 var(--mono);letter-spacing:.1em;text-transform:uppercase;
  color:var(--mist);border-top:1px solid var(--hair);padding-top:14px;margin-top:4px}
.frm .fsec:first-child{border-top:0;padding-top:0;margin-top:0}
.frm input[type=text],.frm input[type=number],.frm input[type=password],.frm select,.frm textarea{
  width:100%;max-width:460px;padding:7px 9px;border:1px solid var(--hair);border-radius:4px;font:13px var(--sans);background:var(--field);color:var(--ink)}
.frm textarea{font-family:var(--mono);font-size:12.5px;min-height:74px}
.frm input:focus,.frm select:focus,.frm textarea:focus,button:focus-visible{outline:2px solid var(--line);outline-offset:1px}
/* A field that is not part of a .frm grid -- the wizard's recipe picker --
   still looks like a field rather than like whatever the browser draws. */
#dlgbody select,#content .bd select{width:100%;max-width:460px;padding:7px 9px;
  border:1px solid var(--hair);border-radius:4px;font:13px var(--sans);
  background:var(--field);color:var(--ink)}
.ckbox{display:flex;align-items:center;gap:8px;padding-top:7px}
.cklist{border:1px solid var(--hair);border-radius:4px;max-width:460px;max-height:170px;overflow:auto;padding:6px 8px;background:var(--field)}
.cklist label{display:flex;gap:8px;align-items:center;padding:3px 0;font-size:13px}
.cklist .none{color:var(--mist);font-size:12px;padding:2px}

/* ---- sign-in ---- */
#login{position:fixed;inset:0;background:var(--petrol);display:none;align-items:center;justify-content:center;padding:24px;z-index:30}
#login.show{display:flex}
#loginbox{background:var(--card);border-radius:8px;padding:26px 28px;width:100%;max-width:380px;display:flex;flex-direction:column}
#loginbox h2{font:600 17px var(--sans);margin-bottom:2px}
#loginbox .loginmark{width:204px;height:auto;margin:0 0 16px -4px}
#loginbox .hint{margin-bottom:18px}
#loginbox label{font:600 11px var(--mono);letter-spacing:.06em;text-transform:uppercase;color:var(--mist);margin-bottom:5px}
#loginbox input{padding:8px 10px;border:1px solid var(--hair);border-radius:4px;font:14px var(--sans);margin-bottom:14px;background:var(--field);color:var(--ink)}
#loginbox button{margin-top:4px}
#loginbox .err{margin:12px 0 0;text-align:center}

/* ---- user footer ---- */
#nav .foot .who{font:12px var(--mono);color:#dfe8e6;overflow:hidden;text-overflow:ellipsis}
#nav .foot .who small{display:block;color:#7d918f;font-size:10px;letter-spacing:.1em;text-transform:uppercase;margin-bottom:3px}
#nav .foot .lo{margin-top:9px;width:100%;background:#0f191e;border:1px solid #2c3e44;color:#c8d4d2;padding:5px;border-radius:4px;font-size:12px}
#nav .foot .lo:hover{border-color:#4a6068;color:#fff}

/* ---- modal ---- */
#ovl{position:fixed;inset:0;background:var(--shade);display:none;align-items:flex-start;justify-content:center;padding:6vh 16px;z-index:20}
#ovl.show{display:flex}
#dlg{background:var(--card);border-radius:8px;width:100%;max-width:760px;max-height:86vh;display:flex;flex-direction:column}
#dlg .hd{display:flex;align-items:center;padding:14px 18px;border-bottom:1px solid var(--hair)}
#dlg .hd h3{font:600 14px var(--sans)}
#dlg .hd button{margin-left:auto;border:0;background:none;font-size:18px;color:var(--mist)}
#dlg .bd{padding:18px;overflow:auto}
#dlg .ft{padding:12px 18px;border-top:1px solid var(--hair);display:flex;gap:10px;justify-content:flex-end}
.err{color:var(--down);font-size:13px;margin-right:auto;align-self:center}

/* ==========================================================================
   Narrow screens
   ========================================================================== */
@media (max-width:840px){
  /* The sidebar becomes a drawer: off screen until the menu button is pressed,
     then over the page rather than above it. Stacking it as a header instead
     meant scrolling past twenty links to reach the page they lead to. */
  #nav{position:fixed;top:0;left:0;z-index:16;width:270px;flex-basis:270px;
    height:100vh;height:100dvh;transform:translateX(-100%);
    box-shadow:0 0 24px rgba(0,0,0,.28)}
  .navopen #nav{transform:none}
  .navopen #scrim{display:block}
  #navtoggle{display:block}
  /* The bar with the menu button stays reachable however far the page scrolls;
     on a phone the alternative is scrolling back to the top to change page.
     Apply keeps the top line -- it is the one button that matters -- and the
     node chips take a line of their own underneath. */
  #topbar{position:sticky;top:0;z-index:10;padding:11px 14px;flex-wrap:wrap;gap:8px}
  #topbar h1{flex:1}
  #nodestrip{order:3;flex-basis:100%;margin-left:0;flex-wrap:wrap;font-size:11px;gap:6px}
  #content{padding:14px}
  #banner{padding:12px 14px 0}
  .frm{grid-template-columns:1fr;gap:10px}
  .frm label.fl{text-align:left;padding-top:0}
  .frm input[type=text],.frm input[type=number],.frm input[type=password],
  .frm select,.frm textarea,.cklist{max-width:none}
  .card>.hd{flex-wrap:wrap}
  .card>.bd{padding:14px}
  /* A table too wide for the screen scrolls inside its card. The page itself
     never scrolls sideways: on a phone that is answered by zooming the whole
     UI out until the widest thing on it fits. */
  .tscroll{overflow-x:auto}
  /* Sized for a thumb rather than a pointer. */
  #nav a.item{padding:11px 16px;font-size:14px}
  #nav button.grp{padding:16px 16px 10px}
  .frm input[type=checkbox],.cklist input[type=checkbox]{width:18px;height:18px}
  .cklist label{padding:6px 0}
}
@media (prefers-reduced-motion:no-preference){
  #nav{transition:transform .18s ease}
}

@media (max-width:680px){
  /* A table of five columns cannot be made to fit a phone by narrowing it, so
     each row becomes a block: one line per field, the column name in front of
     the value. The label comes from the heading, put on each cell as it is
     rendered. */
  #content table,#content thead,#content tbody,#content tr,#content td{display:block;width:auto}
  #content thead{display:none}
  #content table{padding:12px 12px 2px}
  #content .bd table{padding:0}          /* that card already has padding */
  #content tbody tr{border:1px solid var(--hair);border-radius:6px;padding:2px 0;margin-bottom:10px}
  /* The label goes above the value rather than beside it. A cell is not one
     string -- it holds several addresses separated by line breaks, a note under
     a name, a chart -- and laying the cell out in columns puts all of that side
     by side instead, which is how two host names ended up in two columns. */
  #content td{border-bottom:0;padding:6px 12px;min-width:0}
  /* A URL is one long word. Left to itself it sets the width of the whole page,
     which the phone then answers by zooming out -- every other page shrinks to
     make room for one address. It breaks instead. */
  #content td,#content td .mono,#content td .sub{overflow-wrap:anywhere}
  #content td[data-label]::before{content:attr(data-label);display:block;
    font:600 10px var(--mono);letter-spacing:.06em;text-transform:uppercase;
    color:var(--mist);margin-bottom:2px}
  /* The buttons at the end of a row are the row's actions, not another field. */
  #content td[data-actions]{display:flex;flex-wrap:wrap;gap:8px;
    text-align:left!important;padding:8px 12px;
    border-top:1px solid var(--hair);margin-top:4px}
  /* The log stays a stream: it is already narrow, and reads as lines. */
  #content .logview table,#content .logview tbody,#content .logview tr{display:table;width:100%}
  #content .logview tr{display:table-row;border:0;margin:0}
  #content .logview td{display:table-cell;padding:1px 10px}
  /* Anything below 16px makes a phone zoom in when the field is focused, and
     it does not zoom back out afterwards. */
  .frm input,.frm select,.frm textarea,#loginbox input{font-size:16px}
  /* A dialog is the whole screen: a centred box with 6vh above it wastes the
     little room there is, and its buttons ended up below the fold. */
  #ovl{padding:0;align-items:stretch}
  #dlg{max-width:none;max-height:none;height:100%;border-radius:0}
  #dlg .ft{flex-wrap:wrap}
  #dlg .ft .btn{flex:1}
  .err{flex-basis:100%;margin:0 0 4px;text-align:center}
  .btn{padding:8px 12px}
  .btn.sm{padding:6px 10px}
  #topbar h1{font-size:15px}
  /* minmax(0,...) rather than a plain 1fr, which will not shrink below the
     widest word inside it: one long value in one card widened the whole page,
     and the phone answered by zooming the entire UI out to fit it. */
  .grid{grid-template-columns:minmax(0,1fr);gap:10px}
  .grid.row5{grid-template-columns:repeat(2,minmax(0,1fr))}
  .stat{overflow-wrap:anywhere}
}
@media (prefers-reduced-motion:no-preference){
  #dlg{animation:pop .12s ease-out}
  @keyframes pop{from{transform:translateY(6px);opacity:.6}to{transform:none;opacity:1}}
}

/* sortable tables */
th.sortable{cursor:pointer;user-select:none;position:relative;padding-right:18px}
th.sortable:hover{color:var(--ink)}
th.sortable::after{content:"";position:absolute;right:6px;top:50%;margin-top:-2px;
  border:4px solid transparent;border-top-color:var(--hair)}
th.sorted-asc::after{border-top-color:transparent;border-bottom-color:var(--line);margin-top:-6px}
th.sorted-desc::after{border-top-color:var(--line)}

/* The gear sits next to the name, not as another full-width button: it is an
   icon you reach for occasionally, so it stays quiet until hovered. */
#nav .foot .whorow{display:flex;align-items:center;gap:8px}
#nav .foot .whorow .who{flex:1;min-width:0}
#nav .foot .lo.gear{margin-top:0;width:auto;flex:none;padding:6px;line-height:0;
  background:transparent;border-color:transparent;color:#9db2ae}
#nav .foot .lo.gear:hover{background:#0f191e;border-color:#2c3e44;color:#fff}
#nav .foot .lo.gear svg{display:block}

/* Sparklines: two lines occupy the same box, so errors sit over requests. */
.sparkwrap{position:relative;display:inline-block;line-height:0}
.sparkwrap .spark+.spark{position:absolute;left:0;top:0}
.spark{display:inline-block;vertical-align:middle}
