:root{
  --accent:#e94560; --accent-dark:#c62a47; --bg:#f5f6fa; --ink:#1a1d2e; --ink2:#6b7090;
  --muted:#9396ae; --line:#e8eaf2; --line2:#e0e3f0; --ok:#1ea760; --warn:#c47d00; --bad:#d63251;
}
*{box-sizing:border-box}
body{margin:0;background:var(--bg);font-family:Inter,system-ui,sans-serif;color:var(--ink);font-size:14px}
a{color:inherit;text-decoration:none}

/* header */
.topbar{position:sticky;top:0;z-index:50;height:60px;display:flex;align-items:center;gap:22px;padding:0 22px;
  background:rgba(245,246,250,.93);backdrop-filter:blur(18px);box-shadow:0 1px 0 rgba(0,0,0,.07),0 2px 8px rgba(0,0,0,.05)}
.brand{display:flex;align-items:center;gap:11px}
.brand-mark{width:36px;height:36px;border-radius:10px;display:grid;place-items:center;color:#fff;font-size:15px;
  background:linear-gradient(135deg,var(--accent),var(--accent-dark));box-shadow:0 4px 12px rgba(233,69,96,.4)}
.brand-text strong{display:block;font-size:15px;font-weight:800;letter-spacing:.3px;line-height:1.1}
.brand-text small{display:block;font-size:9.5px;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;color:var(--accent)}
.topbar nav{display:flex;gap:4px;margin-left:6px}
.topbar nav a{padding:8px 14px;border-radius:9px;font-size:13.5px;font-weight:500;color:rgba(26,29,46,.66);display:inline-flex;gap:7px;align-items:center;transition:.15s}
.topbar nav a:hover{background:rgba(0,0,0,.05);color:var(--ink)}
.topbar nav a.active{background:rgba(233,69,96,.12);color:var(--accent);border:1px solid rgba(233,69,96,.3)}

main{min-height:calc(100vh - 130px)}
.wrap{max-width:1180px;margin:0 auto;padding:30px 22px 60px}
footer{text-align:center;padding:16px;font-size:12px;color:var(--muted);border-top:1px solid var(--line)}

.head{display:flex;justify-content:space-between;align-items:flex-end;gap:16px;flex-wrap:wrap;margin-bottom:20px}
h1{font-size:24px;font-weight:800;margin:0 0 3px}
.sub{margin:0;font-size:13px;color:var(--muted)}
.head-actions{display:flex;gap:9px;align-items:center;flex-wrap:wrap}
/* A heading whose actions stay put: the text column takes what is left and wraps inside itself,
   rather than growing until the buttons fall onto a line of their own. */
.head.steady{flex-wrap:nowrap}
.head.steady>div:first-child{flex:1;min-width:0}
.head.steady .head-actions{flex-shrink:0;flex-wrap:nowrap}
/* One line, always: a subtitle that wraps at one source and not at another moves the whole page
   down by a line when the source is changed. The full text is on the element as a title. */
.head.steady .sub{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

/* The heading, with a tile in front of it saying which list is being shown. The tile is a fixed
   size so the title sits at the same place on the line whichever source is picked; the text
   column takes the rest and is what ellipsises. */
.head-title{display:flex;align-items:center;gap:14px;min-width:0}
.head-text{min-width:0}
/* Title and picker read as one line: "People" then the list it is showing. The picker takes what
   the word leaves and stops well short of the buttons, so a long device name shortens itself
   rather than pushing the heading around. */
.title-row{display:flex;align-items:center;gap:12px;min-width:0;margin-bottom:4px}
/* A notch bigger than an ordinary page heading, to carry the tile and the picker
   standing either side of it. Scoped here so the other pages' headings are left alone. */
.title-row h1{margin:0;flex:none;font-size:27px;line-height:1.25}
.ctl-pick.on-title{min-width:0;flex:0 1 260px;font-size:14px;padding:10px 13px;border-radius:10px}
.page-icon{flex:none;width:52px;height:52px;border-radius:14px;display:grid;place-items:center;font-size:22px;
  background:var(--tone-soft);color:var(--tone);border:1px solid var(--tone-line);
  box-shadow:0 3px 10px var(--tone-soft)}
/* One colour per kind of list, so the tile is told apart at a glance and not only by its glyph. */
.page-icon.book{--tone:#e94560;--tone-soft:rgba(233,69,96,.13);--tone-line:rgba(233,69,96,.26)}
.page-icon.vto{--tone:#6c5ce7;--tone-soft:rgba(108,92,231,.13);--tone-line:rgba(108,92,231,.26)}
.page-icon.acs{--tone:#1187a8;--tone-soft:rgba(17,135,168,.13);--tone-line:rgba(17,135,168,.26)}

/* controls */
.btn{display:inline-flex;align-items:center;gap:7px;background:#fff;border:1px solid var(--line2);color:var(--ink);
  padding:9px 15px;border-radius:9px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;transition:.15s}
.btn:hover:not(:disabled){background:#f7f8fd;border-color:#c9cee6}
.btn:disabled{opacity:.5;cursor:not-allowed}
.btn.primary{background:var(--accent);border-color:var(--accent);color:#fff;box-shadow:0 4px 12px rgba(233,69,96,.28)}
.btn.primary:hover:not(:disabled){background:var(--accent-dark)}
.btn.danger{color:var(--bad);border-color:rgba(214,50,81,.35);background:rgba(214,50,81,.06)}
.btn.danger:hover:not(:disabled){background:rgba(214,50,81,.13)}
.btn.sm{padding:5px 10px;font-size:12px}
.icon{background:transparent;border:0;color:var(--muted);cursor:pointer;padding:6px 8px;border-radius:7px;font-size:14px}
.icon:hover{background:rgba(0,0,0,.05);color:var(--ink)}
select,input[type=text],input[type=date],input:not([type]){border:1px solid var(--line2);border-radius:9px;padding:9px 12px;
  font-size:13.5px;font-family:inherit;color:var(--ink);background:#fff;outline:none}
select:focus,input:focus{border-color:#a9b2e0;box-shadow:0 0 0 3px rgba(79,70,229,.09)}
.ctl-pick{font-weight:600;min-width:220px}
.search{flex:1;min-width:220px}
.mono{font-family:'Courier New',monospace;letter-spacing:.5px}
.spacer{flex:1}

.toolbar{display:flex;gap:12px;align-items:center;flex-wrap:wrap;margin-bottom:18px}
/* Controls that belong at the right travel together, so a pill appearing on the left cannot
   push one of them onto a line by itself. */
.toolbar-right{display:flex;gap:12px;align-items:center;flex-shrink:0;flex-wrap:nowrap;margin-left:auto}
/* A toolbar that keeps to one row however many pills the source puts in it. Nothing in it
   shrinks any more: the search, which used to be the one control with width to spare, has a
   line of its own below. */
.toolbar.steady{flex-wrap:nowrap}
.toolbar.steady>*{flex-shrink:0}

/* The search, on its own line immediately above the list. The magnifier sits inside the field
   rather than beside it, so the whole width reads as one thing to type into. */
.searchbar{position:relative;margin-bottom:14px}
.searchbar>i{position:absolute;left:14px;top:50%;transform:translateY(-50%);
  color:var(--muted);font-size:13px;pointer-events:none}
.searchbar .search{width:100%;padding-left:38px}

/* The list's controls, held at the top of the page. Everything a row is read through - the
   source, the filters, the counts, the search - stays where it is while the list scrolls under
   it, so the controls that found row forty are still there when you get to it. It comes to rest
   under the 60px topbar and carries the page background, since rows now pass behind it. The
   negative margins pull .wrap's own padding inside the block, so the background reaches the
   edges of the page and the air above the heading is kept once it sticks. The fade underneath
   is background-to-nothing: invisible over an empty page, seen only as a row passes below it. */
.sticky-head{position:sticky;top:60px;z-index:30;background:var(--bg);
  margin:-30px -22px 0;padding:30px 22px 14px}
.sticky-head .searchbar{margin-bottom:0}
.sticky-head::after{content:"";position:absolute;left:0;right:0;top:100%;height:16px;pointer-events:none;
  background:linear-gradient(rgba(245,246,250,1),rgba(245,246,250,0))}
.chk{display:inline-flex;align-items:center;gap:7px;font-size:13px;color:var(--ink2);cursor:pointer}
.chk.big{font-size:13px;color:var(--ink);padding:8px 0}
.pill{background:#fff;border:1px solid var(--line);border-radius:9px;padding:8px 13px;font-size:12.5px;color:var(--ink2)}
.pill strong{color:var(--ink);font-size:14px}

/* cards */
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(310px,1fr));gap:14px}
.card{background:#fff;border:1px solid var(--line);border-radius:14px;padding:15px 16px;box-shadow:0 2px 8px rgba(0,0,0,.04);transition:.18s}
.card:hover{box-shadow:0 8px 22px rgba(0,0,0,.08);border-color:#d6dae9}
.card.frozen{background:#fffafb;border-color:rgba(214,50,81,.25)}
.card-top{display:flex;align-items:center;gap:11px}
.avatar{width:38px;height:38px;border-radius:50%;display:grid;place-items:center;font-size:13px;font-weight:700;color:#fff;
  background:linear-gradient(135deg,var(--accent),var(--accent-dark));flex:none}
.who{flex:1;min-width:0}
.who strong{display:block;font-size:14.5px;font-weight:700;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.who small{display:block;font-size:11.5px;color:var(--muted)}
.tags{display:flex;flex-wrap:wrap;gap:6px}
.card .tags{margin:10px 0 8px}
.tag{font-size:11.5px;font-weight:600;padding:3px 9px;border-radius:20px;background:#eef1f8;color:var(--ink2);display:inline-flex;gap:5px;align-items:center}
.tag.bad{background:rgba(214,50,81,.1);color:var(--bad)}
.tag.warn{background:rgba(196,125,0,.12);color:var(--warn)}
.cards-line{display:flex;flex-wrap:wrap;gap:5px}
.chip{font-size:11.5px;background:#f3f5fa;border:1px solid var(--line2);border-radius:6px;padding:3px 8px}
.muted{color:var(--muted);font-size:12.5px}
.dot{width:11px;height:11px;border-radius:50%;flex:none}
.dot.on{background:var(--ok);box-shadow:0 0 0 3px rgba(30,167,96,.18)}
.dot.off{background:var(--bad);box-shadow:0 0 0 3px rgba(214,50,81,.15)}
.dot.unknown{background:#c3c8dc}

/* editor */
.editor{background:#fff;border:1px solid var(--line);border-radius:16px;padding:20px 22px;margin-bottom:20px;box-shadow:0 6px 22px rgba(0,0,0,.07)}
.editor-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px}
.editor-head h2{font-size:16.5px;font-weight:800;margin:0;display:flex;gap:9px;align-items:center}
.fields{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:14px;margin-bottom:14px}
.f{display:flex;flex-direction:column;gap:5px;margin-bottom:12px}
.f label{font-size:12px;font-weight:700;color:var(--ink2)}
.f label small{font-weight:500;color:var(--muted);margin-left:5px}
.hint{font-size:12px;color:var(--muted);margin-top:5px}
.hint.warn{color:var(--warn)}
.doors{display:flex;flex-wrap:wrap;gap:8px}
.door{display:inline-flex;align-items:center;gap:7px;border:1px solid var(--line2);border-radius:9px;padding:8px 13px;font-size:13px;cursor:pointer;background:#fff}
.door.on{border-color:var(--accent);background:rgba(233,69,96,.07);color:var(--accent-dark);font-weight:600}
.editor-actions{display:flex;gap:9px;align-items:center;flex-wrap:wrap;margin-top:16px;padding-top:15px;border-top:1px solid var(--line)}
.confirm{font-size:13px;color:var(--bad);font-weight:600}

.cards-block,.copy-block{margin-top:20px;padding-top:16px;border-top:1px solid var(--line)}
.cards-block h3,.copy-block h3{font-size:14px;font-weight:800;margin:0 0 10px;display:flex;gap:8px;align-items:center}
.card-rows{display:flex;flex-direction:column;gap:7px;margin-bottom:12px}
.card-row{display:flex;align-items:center;gap:11px;background:#fafbfe;border:1px solid var(--line);border-radius:9px;padding:9px 13px}
.mono.big{font-size:14px;font-weight:700}
.add-card,.copy-row{display:flex;gap:9px;flex-wrap:wrap}
.add-card input{flex:1;min-width:210px}
.copy-row select{min-width:220px}
.doorbtns{display:flex;flex-wrap:wrap;gap:7px;margin-top:11px}

/* misc */
.alert{border-radius:10px;padding:11px 15px;font-size:13px;margin:10px 0;display:flex;gap:8px;align-items:center}
.alert.good{background:rgba(30,167,96,.09);border:1px solid rgba(30,167,96,.3);color:var(--ok)}
.alert.bad{background:rgba(214,50,81,.08);border:1px solid rgba(214,50,81,.3);color:var(--bad)}
.empty{background:#fff;border:1px dashed #d8dcea;border-radius:16px;padding:46px 20px;text-align:center;color:var(--muted)}
.empty i{font-size:30px;color:#c3c8dc;display:block;margin-bottom:10px}
.loading{padding:40px;text-align:center;color:var(--muted);font-size:14px}
.tbl{width:100%;border-collapse:collapse;background:#fff;border:1px solid var(--line);border-radius:14px;overflow:hidden}
.tbl th{text-align:left;font-size:11px;text-transform:uppercase;letter-spacing:.5px;color:var(--muted);padding:11px 14px;background:#f8f9fe;border-bottom:1px solid var(--line)}
.tbl td{padding:10px 14px;border-bottom:1px solid #f1f3f9;font-size:13px}
.tbl tr.bad td{background:rgba(214,50,81,.04)}

#blazor-error-ui{display:none;position:fixed;bottom:0;left:0;right:0;background:#fffbe6;padding:12px 18px;
  box-shadow:0 -1px 8px rgba(0,0,0,.2);z-index:1000;font-size:13px}
#blazor-error-ui .dismiss{cursor:pointer;float:right}

@media (max-width:720px){
  .topbar{gap:10px;padding:0 12px}
  .brand-text{display:none}
  .topbar nav a span{display:none}
  .wrap{padding:20px 13px 44px}
  /* A phone has no height to give away: these controls wrap onto several rows, and holding
     them would leave the list a slot to read through. They scroll off with everything else. */
  .sticky-head{position:static;margin:0;padding:0}
  .sticky-head .searchbar{margin-bottom:14px}
  /* Smaller, not gone: on a phone the tile is the quickest way to see which list is open. */
  .head-title{gap:10px}
  .page-icon{width:42px;height:42px;border-radius:12px;font-size:18px}
  /* No room for both on one line at this width, so the picker drops under the title. */
  .title-row{flex-wrap:wrap;gap:8px}
  .ctl-pick.on-title{flex:1 1 100%}
  .sticky-head::after{display:none}
}

/* ── per-controller door sections in the person editor ── */
.sec{font-size:14px;font-weight:800;margin:20px 0 10px;display:flex;gap:8px;align-items:center;
  padding-top:16px;border-top:1px solid var(--line)}
.dev-list{display:flex;flex-direction:column;gap:11px}
.dev{border:1px solid var(--line);border-radius:12px;padding:12px 14px;background:#fcfdff}
.dev.notthere{background:#fafbfe;border-style:dashed}
.dev.offline{background:#fffafb;border-color:rgba(214,50,81,.25)}
.dev-head{display:flex;align-items:center;gap:9px;margin-bottom:9px;flex-wrap:wrap}
.dev-head strong{font-size:13.5px}
.dev-foot{display:flex;align-items:center;gap:8px;margin-top:9px;flex-wrap:wrap;font-size:12px}
.alert.warn{background:rgba(196,125,0,.09);border:1px solid rgba(196,125,0,.35);color:var(--warn)}

/* card matrix */
.cardmatrix td,.cardmatrix th{vertical-align:middle}
.cardmatrix .ctr{text-align:center}
.cardmatrix .yes{color:var(--ok);font-size:15px}
.cardmatrix .no{color:#c9cee0;font-size:15px}
.cardmatrix td:last-child{white-space:nowrap;display:flex;gap:6px;justify-content:flex-end;align-items:center}

/* delete confirmation: per-controller choice */
.del-panel{border:1px solid rgba(214,50,81,.35);background:rgba(214,50,81,.04);border-radius:12px;padding:14px 16px;margin-top:16px}
.del-list{display:flex;flex-direction:column;gap:7px;margin:10px 0}
.del-row{display:flex;align-items:center;gap:11px;background:#fff;border:1px solid var(--line);border-radius:9px;padding:9px 13px;cursor:pointer}
.del-row.blocked{opacity:.65;cursor:not-allowed;background:#faf9fb}
.del-main{flex:1;display:flex;flex-direction:column}
.del-main strong{font-size:13.5px}
.del-main small{font-size:11.5px;color:var(--muted)}

/* inline delete confirmation on a person card */
.icon.danger:hover{background:rgba(214,50,81,.1);color:var(--bad)}
/* Where an opened editor comes to rest on a page with nothing held at the top: clear of the
   60px topbar, with a little air. Where there is a held block, its height is whatever the
   alerts and the bulk bar have made it, so scroll-into-view.js measures that one instead. */
.editor-here{scroll-margin-top:76px}

/* a pinned backup: kept until it is unpinned, so it is marked rather than merely listed */
.icon.pinned{color:var(--warn)}
.icon.pinned:hover{color:var(--warn);background:rgba(196,125,0,.12)}
.master-backups .tbl td{vertical-align:middle}
.del-inline{margin-top:11px;padding:11px 12px;border:1px solid rgba(214,50,81,.32);background:rgba(214,50,81,.05);
  border-radius:10px;font-size:12.5px;display:flex;flex-direction:column;gap:9px}
.del-inline strong{font-weight:700}
.del-inline .warnline{display:block;color:var(--bad);font-weight:600;margin-top:3px}
.del-inline .muted{display:block;margin-top:3px}
.del-inline-btns{display:flex;gap:7px}

/* cards queued on the new-person form */
.pending-cards{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}
.chip.pend{display:inline-flex;align-items:center;gap:6px;padding:4px 6px 4px 9px;font-weight:600;
  background:rgba(233,69,96,.07);border-color:rgba(233,69,96,.3);color:var(--accent-dark)}
.chip-x{border:0;background:transparent;color:inherit;cursor:pointer;padding:0 2px;font-size:11px;opacity:.65}
.chip-x:hover{opacity:1}

/* import preview counters */
.counts{display:flex;gap:8px;flex-wrap:wrap;margin:12px 0}
.panel{background:#fff;border:1px solid var(--line);border-radius:14px;padding:16px 18px;margin-bottom:16px;box-shadow:0 2px 8px rgba(0,0,0,.04)}

/* ── cards / list switch on the people page ── */
.seg{display:inline-flex;background:#fff;border:1px solid var(--line2);border-radius:9px;padding:2px;gap:2px}
.seg button{border:0;background:transparent;color:var(--muted);cursor:pointer;font-size:13px;
  padding:6px 12px;border-radius:7px;font-family:inherit;transition:.15s}
.seg button:hover{color:var(--ink)}
.seg button.on{background:rgba(233,69,96,.12);color:var(--accent)}

/* people, as a list */
.tbl-wrap{overflow-x:auto;border-radius:14px}
.people-list td{vertical-align:middle}
.people-list tbody tr:hover td{background:#fbfcff}
.people-list tr.frozen td{background:#fffafb}
.people-list .tags{gap:5px}
.people-list .cards-line{gap:5px}
.who-line{display:inline-flex;align-items:center;gap:9px;white-space:nowrap}
.who-line strong{font-size:13.5px;font-weight:700}
.avatar.sm{width:27px;height:27px;font-size:10.5px}
.row-actions{text-align:right;white-space:nowrap}
.del-row-wide td{background:rgba(214,50,81,.04)}
.del-row-wide .del-inline{margin-top:0}

/* ── selecting people, and acting on the selection ── */
.pickcol{width:34px;text-align:center}
/* the row number: a counter for reading a long list out loud, not an id */
.tbl th.lineno,.tbl td.lineno{width:40px;min-width:40px;max-width:40px;text-align:right;
  padding-left:10px;padding-right:6px;color:var(--muted);font-size:11.5px;font-variant-numeric:tabular-nums}
/* Both leading columns are rigid, so the name column starts in the same place whatever the list
   holds: a column that measures itself against its contents moves when the source changes. */
.tbl th.pickcol,.tbl td.pickcol{min-width:34px;max-width:34px}
input[type=checkbox]{accent-color:var(--accent);width:15px;height:15px;cursor:pointer}
.card .pick{flex:none;margin-right:1px}
.card.picked{border-color:var(--accent);box-shadow:0 0 0 1px rgba(233,69,96,.35),0 2px 8px rgba(0,0,0,.04)}
.people-list tr.picked td{background:rgba(233,69,96,.055)}
.bulkbar{display:flex;align-items:center;gap:11px;flex-wrap:wrap;margin-bottom:16px;padding:10px 14px;
  border:1px solid rgba(233,69,96,.32);background:rgba(233,69,96,.06);border-radius:11px;font-size:13px}
.bulkbar i{color:var(--accent)}
.namelist{display:flex;flex-wrap:wrap;gap:6px;margin:10px 0 2px;max-height:120px;overflow:auto}

/* the batch panel: what will happen, then what is happening */
.batch{border-color:var(--line2)}
.targets{display:flex;flex-direction:column;gap:9px;margin:12px 0 2px}
.target{border:1px solid var(--line);border-radius:12px;padding:11px 13px;background:#fcfdff}
.target.on{border-color:var(--accent);background:rgba(233,69,96,.045)}
.target .chk.big{gap:9px}
.bar{height:8px;border-radius:6px;background:#eceff8;overflow:hidden;margin:2px 0 4px}
.bar span{display:block;height:100%;background:linear-gradient(90deg,var(--accent),var(--accent-dark));transition:width .2s}
.runlog{max-height:270px;overflow:auto;border:1px solid var(--line);border-radius:11px;background:#fbfcff;padding:4px 0}
.runline{display:flex;align-items:baseline;gap:9px;padding:6px 13px;font-size:12.5px;border-bottom:1px solid #f1f3f9}
.runline:last-child{border-bottom:0}
.runline i{font-size:11px;width:12px;flex:none}
.runline strong{min-width:130px}
.runline.ok i{color:var(--ok)}
.runline.skipped i{color:var(--warn)}
.runline.skipped{background:rgba(196,125,0,.05)}
.runline.failed i{color:var(--bad)}
.runline.failed{background:rgba(214,50,81,.05)}
.preview-pick{margin-top:12px;padding:9px 12px;border:1px dashed var(--line2);border-radius:10px;background:#fbfcff}

/* the access log: one merged, endlessly scrolling list of what the devices recorded */
.log td{vertical-align:top}
.log tbody tr:hover td{background:#fbfcff}
.log .nowrap{white-space:nowrap}
.log .block{display:block;margin-top:2px}
.log .yes{color:var(--ok);font-weight:600}
.log .no{color:var(--bad);font-weight:600}
.log tr.bad:hover td{background:rgba(214,50,81,.07)}
.log tr.daybreak td{background:#f8f9fe;font-size:11px;font-weight:800;letter-spacing:.6px;
  text-transform:uppercase;color:var(--ink2);padding:7px 14px}
.log tr.daybreak:hover td{background:#f8f9fe}
.scroll-end .hunted{display:flex;flex-direction:column;align-items:center;gap:9px}

/* A filter on a column heading, in the style of the one Excel puts there. The popup lives inside
   the header cell, and that cell sits inside two boxes that clip: the table rounds its corners
   with overflow:hidden, and the wrapper scrolls sideways. Both are opened up while a popup is
   showing, and the corners are put back on the header cells so the table still looks rounded
   while one is. The sheet behind the popup is what closes it when the page is clicked. */
.tbl-wrap.filtering,.tbl.filtering{overflow:visible}
.tbl.filtering thead th:first-child{border-top-left-radius:14px}
.tbl.filtering thead th:last-child{border-top-right-radius:14px}
.th-h{display:flex;align-items:center;gap:8px}
.colf{position:relative;display:inline-flex;line-height:1}
.fbtn{border:0;background:transparent;color:#c3c8dc;cursor:pointer;padding:3px 5px;border-radius:6px;font-size:11px}
.fbtn:hover{background:rgba(0,0,0,.06);color:var(--ink2)}
.fbtn.on{color:var(--accent);background:rgba(233,69,96,.13)}
.colf-back{position:fixed;inset:0;z-index:39}
.colf-menu{position:absolute;top:calc(100% + 9px);left:-9px;z-index:40;width:290px;padding:10px;
  background:#fff;border:1px solid var(--line2);border-radius:12px;box-shadow:0 14px 36px rgba(0,0,0,.17);
  font-size:13px;font-weight:400;color:var(--ink);text-transform:none;letter-spacing:normal}
.colf-menu.right{left:auto;right:-9px}
.colf-find{position:relative;margin-bottom:8px}
.colf-find>i{position:absolute;left:11px;top:50%;transform:translateY(-50%);
  color:var(--muted);font-size:12px;pointer-events:none}
.colf-find input{width:100%;padding:7px 10px 7px 31px;font-size:13px}
.colf-list{max-height:266px;overflow:auto}
.colf-row{display:flex;align-items:center;gap:8px;padding:5px 6px;border-radius:7px}
.colf-row:hover{background:#f7f8fd}
.colf-row.head{font-weight:700;border-bottom:1px solid var(--line);border-radius:0;margin-bottom:4px;padding-bottom:8px}
.colf-row.head:hover{background:transparent}
.colf-pick{flex:1;min-width:0;display:flex;align-items:center;gap:8px;cursor:pointer}
.colf-lab{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.colf-lab small{overflow:hidden;text-overflow:ellipsis}
.colf-n{flex:none;color:var(--muted);font-size:11.5px;font-variant-numeric:tabular-nums}
.colf-only{flex:none;visibility:hidden;border:0;background:transparent;color:var(--accent);
  font-family:inherit;font-size:11px;font-weight:700;cursor:pointer;padding:2px 5px;border-radius:6px}
.colf-row:hover .colf-only{visibility:visible}
.colf-only:hover{background:rgba(233,69,96,.13)}
.colf-none{padding:11px 6px;color:var(--muted);font-size:12.5px}
.colf-foot{display:flex;align-items:center;gap:8px;padding-top:9px;margin-top:6px;border-top:1px solid var(--line)}
.log tr.nomatch td{text-align:center;color:var(--muted);padding:24px 14px}
.log tr.nomatch:hover td{background:#fff}

/* fingerprints, per controller, in the person editor */
.printrow{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:10px}
.print{border:1px solid var(--line);border-radius:12px;padding:11px 13px;background:#fcfdff}
.print.on{border-color:var(--accent);background:rgba(233,69,96,.045)}
.print-head{display:flex;align-items:center;gap:9px;margin-bottom:4px}
.print-head strong{font-size:13.5px}
.print-head i{width:15px;text-align:center;color:var(--muted)}
.print.on .print-head i{color:var(--accent)}
.print .tags{margin-top:8px}
.log .tag.finger{background:rgba(233,69,96,.1);color:var(--accent-dark)}
.log .tag.finger i{font-size:11px}

/* ── settings: what the app is told, rather than what it reads ── */
.set-h{display:flex;align-items:center;gap:9px;font-size:14px;font-weight:800;margin:0 0 12px}
.set-h i{color:var(--accent)}
.panel .sub{margin:-4px 0 14px}
.withbtn{display:flex;align-items:center;gap:4px}
.withbtn input{flex:1;min-width:0}
.doorhead{display:flex;align-items:center;gap:11px;flex-wrap:wrap;margin:14px 0 9px;
  padding-top:12px;border-top:1px solid var(--line);font-size:13px}
.doorhead .pill{padding:4px 12px;min-width:42px;text-align:center}
.doornames{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:8px}
.doorname{display:flex;align-items:center;gap:8px}
.doorname input{flex:1;min-width:0}
.doorno{width:22px;height:22px;flex:none;border-radius:6px;display:grid;place-items:center;
  background:#eef1f8;color:var(--ink2);font-size:11px;font-weight:700}
.dev .fields{margin-bottom:4px}
.dev .chk.big{margin-top:4px}
input[type=number],input[type=password]{border:1px solid var(--line2);border-radius:9px;padding:9px 12px;
  font-size:13.5px;font-family:inherit;color:var(--ink);background:#fff;outline:none}
.log button.tag.copyable{border:1px solid rgba(233,69,96,.3);cursor:pointer;font-family:inherit;
  font-size:11.5px;font-weight:600;gap:6px;transition:.15s}
.log button.tag.copyable:hover:not(:disabled){background:rgba(233,69,96,.18);border-color:var(--accent)}
.log button.tag.copyable:disabled{opacity:.55;cursor:progress}
.log button.tag.copyable .fa-copy{opacity:.55;font-size:10.5px}

/* utilities: a pasted block of card numbers, and the converted ones beside it */
textarea.numbers{width:100%;border:1px solid var(--line2);border-radius:9px;padding:10px 12px;font-size:13.5px;
  line-height:1.6;color:var(--ink);background:#fff;outline:none;resize:vertical;margin-top:5px}
textarea.numbers:focus{border-color:#a9b2e0;box-shadow:0 0 0 3px rgba(79,70,229,.09)}
.linkish{border:0;background:transparent;padding:0;font-size:13px;color:inherit;cursor:pointer;
  border-bottom:1px dashed transparent}
.linkish:hover{color:var(--accent);border-bottom-color:rgba(233,69,96,.45)}
.linkish.dim{color:var(--muted)}

/* fingerprints on the people list: one chip per enrolled finger, each of them a copy button */
.fingers{display:inline-flex;gap:5px;flex-wrap:wrap;align-items:center}
.chip.finger{display:inline-flex;align-items:center;gap:5px;font-family:inherit;font-weight:600;
  color:var(--ink2);cursor:pointer;transition:.15s}
.chip.finger:hover:not(:disabled){background:rgba(233,69,96,.1);border-color:rgba(233,69,96,.35);color:var(--accent)}
.chip.finger:disabled{opacity:.5;cursor:not-allowed}
.fingers .unread{color:#c3c8dc;font-size:11px}

/* what just happened, over the corner of the page, gone again on its own */
.bubble{position:fixed;right:22px;bottom:22px;z-index:80;max-width:380px;display:flex;gap:9px;align-items:flex-start;
  background:#fff;border:1px solid var(--line2);border-left:3px solid var(--ok);border-radius:11px;padding:11px 15px;
  font-size:13px;color:var(--ink);box-shadow:0 10px 30px rgba(0,0,0,.16);animation:bubble-in .18s ease-out}
.bubble.bad{border-left-color:var(--bad)}
.bubble i{color:var(--ok);margin-top:1px}
.bubble.bad i{color:var(--bad)}
@keyframes bubble-in{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}

/* who is signed in */
.topbar-user{margin-left:auto;display:flex;align-items:center;gap:10px}
.topbar-user .who-in{font-size:13px;font-weight:600;color:var(--ink2);display:inline-flex;gap:7px;align-items:center;white-space:nowrap}
.topbar-user .role-pill{font-size:9.5px;font-weight:800;letter-spacing:1px;text-transform:uppercase;
  padding:3px 8px;border-radius:999px;background:rgba(233,69,96,.12);color:var(--accent)}
@media(max-width:900px){.topbar-user .who-in,.topbar-user .role-pill{display:none}}

/* signing in, and the other pages seen before there is a signed-in user */
.wrap.narrow{max-width:460px}
.panel.signin{padding:26px 26px 24px;margin-top:40px}
.panel.signin h1{margin:0 0 6px;font-size:22px}
.panel.signin .sub{margin:0 0 18px;line-height:1.5}
.panel.signin .hint a{color:var(--accent);font-weight:600}
.btn.wide{width:100%;justify-content:center;padding:11px;font-size:14px;margin-top:6px}

/* ── how it works: the flow map ────────────────────────────────────────────────────────────────
   A hub and its spokes in plain SVG. The lines mean one thing and say it with colour and
   direction: green runs up into the book (reading a device changes nothing), red runs down onto
   the hardware (writing to one changes a door). The dots are SMIL, so there is no script behind
   the picture at all. */
.asks{display:flex;gap:9px;flex-wrap:wrap;margin:0 0 16px}
.ask{background:#fff;border:1px solid var(--line2);color:var(--ink);padding:10px 17px;border-radius:999px;
  font-size:13.5px;font-weight:600;cursor:pointer;font-family:inherit;transition:.15s;
  display:inline-flex;align-items:center;gap:9px}
.ask i{font-size:10px;color:var(--muted)}
.ask:hover{background:#f7f8fd;border-color:#c9cee6}
.ask.on{background:var(--accent);border-color:var(--accent);color:#fff;box-shadow:0 4px 12px rgba(233,69,96,.28)}
.ask.on i{color:#fff}

.board{position:relative;background:#fff;border:1px solid var(--line);border-radius:16px;
  padding:10px 12px 0;box-shadow:0 2px 10px rgba(0,0,0,.05);margin-bottom:26px}
/* The drawing gets wider as devices are added, and the browser scales it to the width it has. The
   min-width the page puts on it is the floor: past that this scrolls rather than shrinking the
   names away to nothing. The scrolling is here rather than on the board so that the step counter,
   which is pinned to the board's corner, does not slide off with the picture. */
.stage{overflow-x:auto}
.flowmap{display:block;width:100%;height:auto}

/* Which step of the walkthrough is on screen. Over the corner of the drawing and big enough to
   read from where somebody is actually sitting: it was a line of small print in the caption strip
   and nobody found it. */
.stepper{position:absolute;left:22px;top:16px;z-index:2;pointer-events:none;max-width:38%}
.stepper .count{display:flex;align-items:baseline;gap:8px}
.stepper .count b{font-size:26px;font-weight:800;color:var(--accent);letter-spacing:-.2px}
.stepper .count span{font-size:15px;font-weight:600;color:var(--muted)}
/* What is being done, over the picture and under the number: short enough to take in at a glance,
   with the sentence that explains it waiting in the strip underneath. */
.stepper .what{margin-top:2px;font-size:17px;font-weight:700;color:var(--ink);line-height:1.25}

/* the wires */
.flow .wire{fill:none;stroke:#dfe3f2;stroke-width:2}
.flow .pip{fill:#c6cce0}
.flow.load .wire{stroke:rgba(30,167,96,.3)}
.flow.load .pip{fill:var(--ok)}
.flow.post .wire{stroke:rgba(233,69,96,.28)}
.flow.post .pip{fill:var(--accent)}
.flow.keep .wire{stroke:rgba(196,125,0,.3)}
.flow.keep .pip{fill:var(--warn)}
.flow .pip{opacity:.45;transition:opacity .25s}
.flow.on .pip{opacity:1}
.flow.on .wire{stroke-width:3}
.flow.load.on .wire{stroke:var(--ok)}
.flow.post.on .wire{stroke:var(--accent)}
.flow.keep.on .wire{stroke:var(--warn)}

/* the boxes */
.box rect{fill:#fff;stroke:var(--line2);stroke-width:1.5;transition:.2s}
.box text{text-anchor:middle;font-family:Inter,system-ui,sans-serif}
.box .ttl{font-size:15px;font-weight:800;fill:var(--ink)}
.box .ttl.small{font-size:13px;text-anchor:start}
.box .sub{font-size:11px;fill:var(--muted)}
.box .num{font-size:11.5px;font-weight:700;fill:var(--ink2)}
.box .ip{font-size:12px;font-family:'Courier New',monospace;fill:var(--ink2);text-anchor:start;letter-spacing:.3px}
.box .tagtext{font-size:9.5px;font-weight:800;letter-spacing:1px;fill:var(--muted);text-anchor:end}
.box.book rect{fill:#fff6f7;stroke:rgba(233,69,96,.45);stroke-width:2}
.box.copies rect{fill:#fffaf0;stroke:rgba(196,125,0,.4)}
.box.vto rect{stroke:rgba(108,92,231,.4)}
.box.acs rect{stroke:rgba(17,135,168,.4)}
.box.on rect{stroke-width:3;filter:drop-shadow(0 4px 10px rgba(0,0,0,.13))}
.box.book.on rect{stroke:var(--accent)}
.box.copies.on rect{stroke:var(--warn)}
.box.vto.on rect{stroke:#6c5ce7}
.box.acs.on rect{stroke:#1187a8}
/* Still on the map, but not part of the question being asked: no line reaches it. */
.box.faded{opacity:.32}
.box .light{fill:#c3c8dc}
.box .light.on{fill:var(--ok)}
.box .light.off{fill:var(--bad)}
.box .ip .port{fill:var(--muted)}

/* the doors, inside the device that has them: a column of four before a second column starts, so
   eight doors are two neat columns rather than a long list nobody reads to the end of */
.box .rule{stroke:var(--line);stroke-width:1}
.box .door-no{font-size:10px;font-weight:800;fill:var(--muted);text-anchor:start}
.box .door{font-size:11.5px;fill:var(--ink2);text-anchor:start}
.box.on .door{fill:var(--ink)}

/* the line under the picture: what is happening, and how to drive it */
.caption{display:flex;align-items:center;gap:6px;padding:12px 6px;border-top:1px solid var(--line);margin-top:4px}
.caption.idle{color:var(--muted);font-size:12.5px}
.caption.idle p{margin:0;display:flex;gap:6px;align-items:center;flex-wrap:wrap}
.caption.idle i{color:var(--accent);margin-right:3px}
.caption.idle .chk{margin-left:auto;white-space:nowrap}
.key-out{color:var(--accent);font-weight:700}
.key-in{color:var(--ok);font-weight:700}
.caption .line{flex:1;margin:0 8px;font-size:14px;line-height:1.5;color:var(--ink)}
.caption .line strong{color:var(--accent);margin-right:5px}


@media(max-width:700px){
  .caption{flex-wrap:wrap}
  .caption .line{flex:1 0 100%;order:-1;margin:0 0 8px}
}
