:root{
  --ground:#F2F4F6; --panel:#fff; --panel-2:#E9ECEF;
  --ink:#161A1F; --ink-2:#454C55; --ink-3:#6B747E;
  --rule:#CFD5DB; --rule-2:#E2E6EA; --accent:#0C6B75;
  --fire:#D0021B; --warn:#9A6A12; --ok:#2C7350; --idle:#7C8593; --unknown:#6B5B95;
  --sea:#DCE6EC; --land:#EFF2F4; --land-2:#E4E9ED;
  /* The journal's own three: a command, a link, a change to the setup. */
  --cmd:#1D5FA8; --link:#6B5B95; --cfg:#3F6B4A;
}
/**
 * DARK, THREE WAYS.
 *
 * The page followed the operating system and offered no say in it. A control
 * room is a dark room and an office at 09:00 is not, and it is the same person
 * on the same machine — so there is a switch, and it wins over the system.
 *
 * Written the way a themeable page has to be: every colour has a value on bare
 * `:root`, the dark values are repeated once for "system says dark and the
 * user has not said otherwise" and once for "the user chose dark". Miss either
 * and the toggle only works in one direction.
 */
@media (prefers-color-scheme: dark){:root:not([data-theme="light"]){
  --ground:#0E1114; --panel:#171B21; --panel-2:#1E242B;
  --ink:#E7EBEF; --ink-2:#B3BCC5; --ink-3:#848E99;
  --rule:#2C343D; --rule-2:#232A31; --accent:#4FB3BE;
  --fire:#F2564B; --warn:#D9A648; --ok:#63B98C; --idle:#7C8593; --unknown:#A08FD0;
  --sea:#12171C; --land:#1B2229; --land-2:#161C22;
  --cmd:#6FA8DC; --link:#A08FD0; --cfg:#7FB98F;
}}
:root[data-theme="dark"]{
  --ground:#0E1114; --panel:#171B21; --panel-2:#1E242B;
  --ink:#E7EBEF; --ink-2:#B3BCC5; --ink-3:#848E99;
  --rule:#2C343D; --rule-2:#232A31; --accent:#4FB3BE;
  --fire:#F2564B; --warn:#D9A648; --ok:#63B98C; --idle:#7C8593; --unknown:#A08FD0;
  --sea:#12171C; --land:#1B2229; --land-2:#161C22;
  --cmd:#6FA8DC; --link:#A08FD0; --cfg:#7FB98F;
}
*{box-sizing:border-box}
/* `hidden` loses to display:flex on the panels, so say it louder once. */
[hidden]{display:none!important}
html,body{height:100%}
body{margin:0;background:var(--ground);color:var(--ink);overflow:hidden;
  font:14px/1.5 "IBM Plex Sans",-apple-system,Segoe UI,system-ui,sans-serif}
.mono{font-family:"IBM Plex Mono",ui-monospace,SFMono-Regular,Consolas,monospace}

/* ---------- chrome ---------- */
header{height:52px;display:flex;align-items:center;gap:10px;padding:0 14px;overflow:hidden;
  border-bottom:1px solid var(--rule);background:var(--panel);position:relative;z-index:30}
h1{font-size:15px;margin:0;font-weight:600;letter-spacing:-.01em;white-space:nowrap}
.crumbs{display:flex;align-items:center;gap:6px;font-size:13px;color:var(--ink-3);
  flex:1;min-width:240px;overflow:hidden;white-space:nowrap;justify-content:flex-end}
/* When the header is full the START of the path goes, never the end: the
   current place is the crumb somebody actually reads. */
.crumbs>:first-child{margin-right:auto}
.crumbs button{flex:none}
.crumbs button{border:none;background:none;color:var(--accent);cursor:pointer;font:inherit;padding:2px 4px;border-radius:2px}
.crumbs button:hover{background:var(--panel-2)}
.crumbs .sep{color:var(--rule)}
.crumbs .cur{color:var(--ink);font-weight:500}
/**
 * VÄGEN TILL ETT LARM LYSER SÅ LÄNGE LARMET STÅR. Brödsmulorna bär det —
 * site, byggnad, plan i larmfärg — och pulserar tills någon kvitterat. Se
 * larmPlatser() i larmvägen.
 */
.crumbs .larm,.crumbs .cur.larm{color:var(--fire);font-weight:600}
.crumbs .larm::before,#floornav .fn.larm::before{content:"";display:inline-block;width:7px;height:7px;border-radius:50%;
  background:var(--fire);margin-right:5px;vertical-align:middle}
.crumbs .larm.puls::before,#floornav .fn.larm.puls::before{animation:larmprick 1.1s ease-out infinite}
@keyframes larmprick{0%{box-shadow:0 0 0 0 color-mix(in srgb,var(--fire) 60%,transparent)}
  70%{box-shadow:0 0 0 7px color-mix(in srgb,var(--fire) 0%,transparent)}100%{box-shadow:0 0 0 0 transparent}}
#floornav .fn.larm{color:var(--fire);border-color:color-mix(in srgb,var(--fire) 45%,transparent)}
#floornav .fn.larmchip{background:color-mix(in srgb,var(--fire) 12%,var(--panel));font-weight:600}
/** Symbolen som just bytte tillstånd — en ring som slår ut två gånger, i symbolens EGEN nya färg (currentColor). Se drawMarks(). */
.mark.bytte svg.sym{animation:bytte 1.3s ease-out 2;border-radius:50%}
/* Kameran som ser ett larm: en ring i larmets färg som pulserar utåt, så länge larmet står (24 sep). */
.mark.kamlarm svg.sym{border-radius:50%;animation:kamlarm 1.5s ease-out infinite;
  filter:drop-shadow(0 0 5px var(--fire)) drop-shadow(0 0 2px var(--fire))}
@keyframes kamlarm{0%{outline:3px solid var(--fire);outline-offset:3px}100%{outline:3px solid transparent;outline-offset:26px}}
@keyframes bytte{0%{outline:2.5px solid currentColor;outline-offset:2px}100%{outline:2.5px solid transparent;outline-offset:22px}}
@media (prefers-reduced-motion:reduce){
  .crumbs .larm.puls::before,#floornav .fn.larm.puls::before{animation:none}
  .mark.bytte svg.sym{animation:none;outline:2.5px solid currentColor;outline-offset:6px}
  .mark.kamlarm svg.sym{animation:none;outline:3px solid var(--fire);outline-offset:5px}}
/*
  TVÅ YTOR, INTE EN PANEL OVANPÅ EN KARTA.
  Kartan är driften; personer och behörigheter är administration, och de två
  delar ingenting utom skärmen. Så IAM är en egen yta bredvid Karta i
  toppraden: kartan, rälsen och zoomen går undan helt, och panelen fyller
  ytan. Samma mekanik som ett popat fönster (body.panelonly), men här inne.
*/
.crumbs .tabs{display:flex;gap:2px;margin-right:auto;margin-left:18px}
.crumbs .tabs button{font-weight:500;padding:3px 10px;border-radius:4px}
.crumbs .tabs button.on{background:var(--panel-2);color:var(--ink)}
/*
  LISTAN ÄR EN LISTA, INTE KORT PÅ RAD.
  Frågorna en behörighetsadministratör ställer — vem går ut, vem saknar kort,
  vem finns bara i ett system — ska besvaras i kolumner, inte genom att öppna
  ett kort i taget. Tät rad, klickbara rubriker, och märken per system.
*/
.pviews{display:flex;flex-wrap:wrap;gap:2px;align-items:center;margin:0 0 8px;
  border-bottom:1px solid var(--rule);padding-bottom:6px}
.pviews .pv{border:none;background:none;color:var(--ink-3);cursor:pointer;font:inherit;font-size:12.5px;
  padding:5px 10px;border-radius:5px;display:inline-flex;gap:6px;align-items:baseline;white-space:nowrap}
.pviews .pv:hover{background:var(--panel-2);color:var(--ink-2)}
.pviews .pv.on{background:var(--panel-2);color:var(--ink);font-weight:600;box-shadow:inset 0 -2px 0 var(--accent)}
.pviews .pv b{font-size:11px;font-weight:600;color:var(--ink-3)}
.pviews .pv.on b{color:var(--accent)}
.pviews .pvsum{margin-left:auto;font-size:11.5px}
.ptable{width:100%;border-collapse:collapse;font-size:12.5px}
.ptable th{position:sticky;top:0;z-index:1;background:var(--panel);text-align:left;font-weight:600;
  color:var(--ink-3);font-size:11.5px;padding:6px 8px;border-bottom:1px solid var(--rule);
  cursor:pointer;white-space:nowrap;user-select:none}
.ptable th:hover{color:var(--ink)}
.ptable th.sorted{color:var(--ink)}
.ptable th .ar{font-size:8px;margin-left:4px;color:var(--accent)}
.ptable th.sel,.ptable td.sel{width:26px;cursor:default;padding-left:4px;padding-right:0}
.ptable th.num,.ptable td.num{text-align:right}
.ptable td{padding:5px 8px;border-bottom:1px solid var(--rule-2);vertical-align:middle}
.ptable tr.prow2{cursor:pointer}
.ptable tr.prow2:hover td{background:var(--panel-2)}
.ptable tr.prow2.picked td{background:var(--panel-2);box-shadow:inset 2px 0 0 var(--accent)}
.ptable tr.prow2.off{opacity:.6}
.ptable td.who{display:flex;align-items:center;gap:8px;min-width:0}
.ptable td.who .nm{font-weight:600;color:var(--ink);display:block}
.ptable td.who .sub{font-size:11px;color:var(--ink-3);display:block}
.ptable td.sys{white-space:nowrap}
.sysb{display:inline-block;font-size:10.5px;padding:1px 5px;margin-right:3px;border-radius:3px;
  border:1px solid var(--rule);color:var(--ink-3);background:var(--panel-2)}
.sysb.own{border-color:var(--link);color:var(--link)}
.ptable .warn{color:var(--warn)}
.ptable .bad{color:var(--fire)}
/* Object page: fast huvud, flikrad, ett panelinnehåll i taget. */
.pcard .phead{position:sticky;top:0;z-index:2;background:var(--panel);padding-bottom:8px}
.ptabs{display:flex;gap:2px;position:sticky;top:var(--pheadh,64px);z-index:2;background:var(--panel);
  border-bottom:1px solid var(--rule);margin:0 0 10px;padding-top:2px}
.ptabs button{border:none;background:none;color:var(--ink-3);cursor:pointer;font:inherit;font-size:12.5px;
  padding:6px 12px;border-radius:5px 5px 0 0;display:inline-flex;gap:6px;align-items:baseline;white-space:nowrap}
.ptabs button:hover{background:var(--panel-2);color:var(--ink-2)}
.ptabs button.on{color:var(--ink);font-weight:600;box-shadow:inset 0 -2px 0 var(--accent)}
.ptabs button b{font-size:11px;font-weight:600;color:var(--ink-3)}
.ptabs button.on b{color:var(--accent)}
.ptabs button i{font-style:normal;font-size:10.5px;padding:0 5px;border-radius:8px;
  background:var(--warn);color:#fff;font-weight:600}
.ptabs button i.bad{background:var(--fire)}
.ptabpanel[hidden]{display:none}
.ptabpanel > section:first-child{margin-top:0}
.pill.pend{margin-right:5px}
.pcand{border:1px solid var(--rule);border-radius:4px;padding:7px 9px;margin:6px 0;
  display:flex;flex-wrap:wrap;gap:6px;align-items:center;font-size:12.5px;color:var(--ink-2)}
.pcand.bad{border-color:var(--fire)}
.pcand .ct{flex-basis:100%;margin:0}
/*
  KARTAN GÖMS, MEN TAS INTE UR LAYOUTEN.
  `display:none` på #stage gav den bredd och höjd noll, och kameran räknade
  fram NaN — sidan ritade om sig i all oändlighet med <circle cx="NaN"> och
  hängde sig. `visibility` behåller geometrin, och personytan ligger ändå
  fast ovanpå.
*/
body.iamview #stage{visibility:hidden}
body.iamview #rail,body.iamview .zoomer,body.iamview #floornav,
body.iamview #mapTools,body.iamview #mapAttr,body.iamview #selbar,body.iamview #delbar,
body.iamview .pal,body.iamview #hint{display:none!important}
body.iamview #iamPanel{position:fixed;left:0;right:0;bottom:0;top:53px;
  width:auto;max-height:none;border-radius:0;border:0;box-shadow:none;z-index:20}
body.iamview #iamPanel .body{max-height:none;height:calc(100% - 42px)}
body.iamview #iamPanel h2 #iamClose{display:none}
/* VIDEOVÄGGEN. Samma ytlogik som personytan — kartan göms med visibility,
   aldrig display, av exakt samma skäl (se stycket ovan om NaN). */
body.cctvview #stage{visibility:hidden}
body.cctvview #rail,body.cctvview .zoomer,body.cctvview #floornav,
body.cctvview #mapTools,body.cctvview #mapAttr,body.cctvview #selbar,body.cctvview #delbar,
body.cctvview .pal,body.cctvview #hint{display:none!important}
body.cctvview #cctvPanel{position:fixed;left:0;right:0;bottom:0;top:53px;
  width:auto;max-height:none;border-radius:0;border:0;box-shadow:none;z-index:20}
/* Väggen är en kolumn — rad överst, rutnät som tar resten — i BÅDA lägena:
   som yta i huvudfönstret och som eget fönster (body.panelonly .solo). Regeln
   satt först bara på ytan, och då kollapsade rutnätet i det popade fönstret. */
#cctvPanel .body{display:flex;flex-direction:column}
body.cctvview #cctvPanel .body{max-height:none;height:calc(100% - 42px)}
body.cctvview #cctvPanel h2 #cctvClose{display:none}
/* Med demo-/licensraden uppe börjar ytorna under den, som kartan gör (body.demo #stage{top:76px}). */
body.demo.iamview #iamPanel,body.demo.cctvview #cctvPanel{top:77px}
body.panelonly #cctvPanel.solo .body{max-height:none;height:calc(100% - 42px)}
/* Helskärm på rutnätet självt: då ska det finnas en botten att rita mot. */
#cctvWall:fullscreen{background:#0b0d0f;padding:8px}
#cctvBar{display:flex;gap:8px;align-items:center;flex-wrap:wrap;padding:6px 10px;border-bottom:1px solid var(--rule)}
#cctvBar .grp{display:flex;gap:4px;align-items:center}
#cctvWall{flex:1;display:grid;gap:6px;padding:8px;min-height:0;overflow:auto}
.wcell{position:relative;background:#0b0d0f;border:1px solid var(--rule);border-radius:3px;
  display:flex;flex-direction:column;justify-content:space-between;min-height:120px;overflow:hidden}
.wcell.empty{background:var(--panel-2);border-style:dashed;align-items:center;justify-content:center;cursor:pointer}
.wcell.empty:hover{border-color:var(--accent)}
.wcell .wtop{display:flex;gap:6px;align-items:center;padding:6px 8px;color:#e8eaec;
  background:linear-gradient(rgba(0,0,0,.55),rgba(0,0,0,0));font-size:12px}
.wcell .wtop b{font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.wcell .wtop .x{margin-left:auto;cursor:pointer;opacity:.6}
.wcell .wtop .x:hover{opacity:1}
.wcell .wmid{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  color:#7b838a;font-size:12px;text-align:center;padding:0 14px;pointer-events:none}
.wcell .wfoot{padding:6px 8px;font-size:11px;color:#9aa2a8;
  background:linear-gradient(rgba(0,0,0,0),rgba(0,0,0,.55))}
/* Livebilden fyller rutan och står BAKOM namnraden och täcker-raden.
   En <img> med position:absolute och inset:0 sträcks inte — den behåller sin
   egen storlek och lägger sig i hörnet, ovanpå kamerans namn (24 sep). */
.wcell img.wmid{width:100%;height:100%;padding:0;object-fit:contain;background:#000;display:block}
.wcell .wtop,.wcell .wfoot{position:relative;z-index:1}
.wcell .wtop .lossa{margin-left:auto}
.wcell .wtop .lossa + .x{margin-left:6px}
/* VÄGGEN PÅ TELEFONEN (25 sep) — se telefonVagg i 34-videovagg.js.
   16:9-bilder i full bredd, namnet ovanpå bilden, NORMAL syns inte, och
   knapparna utom väggen, + kamera och ⋯ ligger bakom ⋯. */
/* Rubriken "CCTV" står redan i fliken; på telefonen tar den bara en rad. */
body.cctvview #cctvPanel.telvagg > h2{display:none}
body.cctvview #cctvPanel.telvagg .body{height:100%;padding:0}
#cctvBar.tel{flex-wrap:wrap;gap:6px;padding:6px 8px}
#cctvBar.tel .teltopp{width:100%}
#cctvBar.tel .teltopp select{flex:1 1 auto;min-width:0;max-width:none}
#cctvBar.tel .telx{display:none}
#cctvBar.tel.mer .telx{display:flex;flex-wrap:wrap}
#cctvBar.tel.mer .telgom{display:none}
#cctvBar.tel.mer .grp[style]{margin-left:0!important}
/* Flex i stället för grid: rader i ett rutnät med fast höjd krymper, och en
   16:9-ruta med overflow:hidden har ingen minsta höjd att hålla emot med. */
.telvagg #cctvWall{display:flex;flex-wrap:wrap;align-content:flex-start;gap:8px;padding:8px}
.telvagg .wcell{flex:0 0 100%;aspect-ratio:16/9;min-height:0;cursor:pointer}
.telvagg.kol2 .wcell{flex-basis:calc(50% - 4px)}
.telvagg .wcell.empty{aspect-ratio:auto;min-height:60px}
.telvagg .wcell .wtop{position:absolute;left:0;right:0;top:0;padding:5px 7px;font-size:12.5px}
.telvagg .wcell .wfoot{position:absolute;left:0;right:0;bottom:0;padding:4px 7px;font-size:10.5px;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.telvagg .wcell .wtop b{flex:1 1 auto;min-width:0}
.telvagg .wcell .wstate.ok,.telvagg .wcell .lossa,.telvagg .wcell .wfoot.tom{display:none}
.telvagg .wcell .wtop .x{margin-left:0;padding:2px 6px;font-size:14px}
.telvagg .wcell .wstate{flex:0 0 auto}
/* Två kolumner: bara namnet och ✕, täcker-raden får inte plats. */
.telvagg.kol2 .wcell .wfoot{display:none}
.telvagg.kol2 .wcell .wtop{font-size:11px;padding:3px 5px}
/* En kamera i hela skärmen. */
#kamHel{position:fixed;inset:0;z-index:260;background:#000;display:flex;flex-direction:column;color:#e8eaec}
#kamHel[hidden]{display:none}
#kamHel .khtopp{display:flex;gap:8px;align-items:center;padding:8px 10px;padding-top:max(8px,env(safe-area-inset-top));
  background:linear-gradient(rgba(0,0,0,.8),rgba(0,0,0,0));position:absolute;left:0;right:0;top:0;z-index:2}
#kamHel .khtopp b{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:15px}
#kamHel .khtopp .x{background:rgba(255,255,255,.12);border:0;color:#fff;font-size:18px;width:38px;height:38px;border-radius:19px;cursor:pointer}
#kamHel .wstate{font-size:10.5px;letter-spacing:.07em;text-transform:uppercase;padding:2px 6px;border-radius:2px;background:rgba(255,255,255,.14)}
#kamHel .wstate.ok{display:none}
#kamHel .wstate.bad{background:rgba(176,58,58,.85);color:#fff}
#kamHel .wstate.warn{background:rgba(184,134,11,.85);color:#fff}
#kamHel.hot{box-shadow:0 0 0 3px rgba(176,58,58,.9) inset}
#kamHel .khbild{flex:1;display:flex;align-items:center;justify-content:center;min-height:0;color:#9aa2a8;padding:0 20px;text-align:center}
#kamHel .khbild img{width:100%;height:100%;object-fit:contain;display:block}
#kamHel .khfot{position:absolute;left:0;right:0;bottom:0;padding:8px 12px;padding-bottom:max(8px,env(safe-area-inset-bottom));
  font-size:12px;color:#c3c8cc;background:linear-gradient(rgba(0,0,0,0),rgba(0,0,0,.75))}
#kamHel .khpil{position:absolute;top:50%;transform:translateY(-50%);width:44px;height:64px;border:0;
  background:rgba(0,0,0,.35);color:#fff;font-size:30px;cursor:pointer;z-index:2}
#kamHel .khpil.v{left:0;border-radius:0 6px 6px 0}
#kamHel .khpil.h{right:0;border-radius:6px 0 0 6px}
#kamHel .khpil[hidden]{display:none}
body.kamhel{overflow:hidden}
/* Kameran på ritningen: förhandsbild i sidopanelen och livebild i ett litet fönster (24 sep). */
.kamblock{margin:8px 0 10px}
.larmkam .kamrad{display:flex;gap:6px}
.larmkam .kamrad>div{flex:1 1 0;min-width:0}
.larmbild{position:relative;aspect-ratio:16/9;background:#0b0d0f;border:1px solid var(--alarm, #c62828);border-radius:3px;overflow:hidden}
.larmbild img{width:100%;height:100%;object-fit:contain;display:block;cursor:zoom-in}
.larmbild .lbcap{position:absolute;left:0;right:0;bottom:0;padding:2px 6px;font-size:10.5px;color:#fff;background:rgba(0,0,0,.55)}
.larmbild .kamfel{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;text-align:center;padding:6px;font-size:11px;color:#bbb}
.kamblock .row{display:flex;gap:6px;flex-wrap:wrap;margin-top:6px}
.kamprev{position:relative;aspect-ratio:16/9;background:#0b0d0f;border:1px solid var(--rule);border-radius:3px;overflow:hidden}
.kamprev.klick{cursor:zoom-in}
.kamprev img{width:100%;height:100%;object-fit:contain;display:block}
.kamprev .kamfel{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  padding:0 12px;text-align:center;color:#9aa2a8;font-size:12px}
.kamfonster{position:fixed;width:420px;height:270px;min-width:220px;min-height:150px;
  display:flex;flex-direction:column;background:#0b0d0f;border:1px solid var(--rule);border-radius:4px;
  box-shadow:0 8px 28px rgba(0,0,0,.45);resize:both;overflow:hidden}
.kamfonster.hot{border-color:var(--bad,#b03a3a);box-shadow:0 0 0 2px rgba(176,58,58,.55),0 8px 28px rgba(0,0,0,.45)}
.kamfonster .kfh{display:flex;gap:6px;align-items:center;padding:5px 8px;color:#e8eaec;font-size:12px;
  background:#15191c;cursor:move;user-select:none;touch-action:none}
.kamfonster .kfh b{font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:pointer}
.kamfonster .kfh .wstate{font-size:10.5px;letter-spacing:.07em;text-transform:uppercase;padding:2px 6px;border-radius:2px;background:rgba(255,255,255,.08)}
.kamfonster .kfh .wstate.bad{background:rgba(176,58,58,.75);color:#fff}
.kamfonster .kfh .wstate.warn{background:rgba(184,134,11,.75);color:#fff}
.kamfonster .kfh .x{margin-left:auto;background:none;border:0;color:#c9cfd4;cursor:pointer;font-size:13px;padding:0 3px;opacity:.7}
.kamfonster .kfh .x + .x{margin-left:4px}
.kamfonster .kfh .x:hover{opacity:1}
.kamfonster .kfb{position:relative;flex:1;min-height:0}
.kamfonster .kfb .wmid{position:absolute;inset:0;width:100%;height:100%;object-fit:contain;display:flex;
  align-items:center;justify-content:center;color:#7b838a;font-size:12px;text-align:center;padding:0}
.kamfonster.helsida{inset:0;width:auto;height:auto;border:0;border-radius:0;resize:none;box-shadow:none;z-index:230}
.kamfonster.helsida .kfh{cursor:default}
body.kamerasolo #cctvPanel{display:none!important}
.wcell.hot{border-color:var(--bad,#b03a3a);box-shadow:0 0 0 2px rgba(176,58,58,.45) inset}
.wcell.hot .wtop{background:linear-gradient(rgba(176,58,58,.75),rgba(0,0,0,0))}
.wcell .wstate{font-size:10.5px;letter-spacing:.07em;text-transform:uppercase;padding:2px 6px;border-radius:2px;
  background:rgba(255,255,255,.12)}
.wcell .wstate.bad{background:rgba(176,58,58,.75);color:#fff}
.wcell .wstate.warn{background:rgba(184,134,11,.75);color:#fff}
#cctvPick{position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:60;display:flex;align-items:center;justify-content:center}
#cctvPick .box{background:var(--panel);border:1px solid var(--rule);border-radius:4px;padding:14px;
  width:min(560px,92vw);max-height:80vh;overflow:auto}
#cctvPick .cams{display:flex;flex-direction:column;gap:4px;margin-top:8px}
#cctvPick .cams button{text-align:left}
.pill{font-size:10.5px;letter-spacing:.07em;text-transform:uppercase;font-weight:600;
  padding:3px 8px;border-radius:2px;background:var(--panel-2);color:var(--ink-3);white-space:nowrap}
.pill.up{background:rgba(44,115,80,.15);color:var(--ok)}
.pill.down{background:rgba(208,2,27,.15);color:var(--fire)}
.pill.idle{background:var(--panel-2);color:var(--ink-3)}
.xform label{display:grid;grid-template-columns:74px 1fr 52px;align-items:center;gap:8px;
  font-size:12.5px;color:var(--ink-2);margin:0 0 7px}
.xform input[type=range]{width:100%;accent-color:var(--accent)}
.xform .v{font-size:12px;color:var(--ink-3);text-align:right;font-variant-numeric:tabular-nums}
.mark .glyph{display:block;transform-origin:50% 50%}
.mark .addr .mult{display:inline-block;min-width:13px;padding:0 3px;border-radius:7px;
  background:var(--accent);color:#fff;font-size:9px;line-height:13px;text-align:center;
  vertical-align:1px}
#behind{position:fixed;left:50%;transform:translateX(-50%);bottom:18px;z-index:40;
  max-width:560px;background:var(--panel);border:1px solid var(--warn,#B8860B);
  border-left:4px solid var(--warn,#B8860B);border-radius:4px;padding:11px 15px;
  font-size:13px;line-height:1.5;box-shadow:0 8px 30px rgba(0,0,0,.25)}
#behind button{margin-left:8px;font:inherit;font-size:12.5px;padding:3px 10px;
  border-radius:3px;border:1px solid var(--rule);background:var(--panel-2);
  color:var(--ink);cursor:pointer}
#firstRun{position:absolute;inset:0;display:grid;place-items:center;pointer-events:none;z-index:6}
#firstRun .card{pointer-events:auto;max-width:560px;background:var(--panel);
  border:1px solid var(--rule);border-radius:5px;padding:22px 24px;
  box-shadow:0 10px 40px rgba(0,0,0,.18);line-height:1.55;font-size:13.5px}
#firstRun h3{margin:0 0 6px;font-size:16px}
#firstRun p{margin:0 0 12px;color:var(--ink-2)}
#firstRun ol{margin:0 0 12px;padding-left:20px;color:var(--ink-2)}
#firstRun li{margin:5px 0}
#firstRun .subs{display:flex;flex-wrap:wrap;gap:6px;margin:0 0 14px}
#firstRun .sub-chip{font-size:11.5px;border:1px solid var(--rule-2);border-radius:99px;
  padding:2px 9px;color:var(--ink-3)}
#firstRun .fine{font-size:12px;color:var(--ink-3);margin:0}
.tog{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--ink-3);white-space:nowrap;cursor:pointer}
.tog input{accent-color:var(--accent)}

/* ---------- the top row: two clusters and one menu ----------
 *
 * `.hact` is what you do, `.hwho` is who you are; the breadcrumb between them
 * takes the slack. Both clusters are flex:none so a long site name shortens
 * the path rather than pushing the gear off the edge — the old row lost its
 * last buttons to `overflow:hidden` instead.
 */
.hact,.hwho{display:flex;align-items:center;gap:8px;flex:none}
.hwho{gap:7px}
/* Lägesbild and logg are one control: two halves of "what is going on". */
header button.btn{border-radius:6px}
.seg{display:flex;border:1px solid var(--rule);border-radius:6px;overflow:hidden}
header .seg button.btn{border:none;border-radius:0;background:var(--panel-2)}
header .seg button.btn+button.btn{border-left:1px solid var(--rule)}
header .seg button.btn:hover:not(:disabled){background:var(--panel);color:var(--accent)}
header button.btn.ico{font-size:14px;line-height:16px;padding:4px 9px}
button.btn.hasmenu::after{content:"▾";margin-left:5px;font-size:9px;opacity:.55;vertical-align:1px}
button.btn.hasmenu[aria-expanded="true"]{border-color:var(--accent);color:var(--accent)}
/* A dot when something behind the menu is switched on — edit mode above all,
   because a station left in edit mode is a station that is not watching. */
button.btn.hasmenu.hot::before{content:"";display:inline-block;width:6px;height:6px;border-radius:50%;
  background:var(--warn);margin-right:6px;vertical-align:1px}
.hmenu{position:fixed;z-index:300;background:var(--panel);border:1px solid var(--rule);border-radius:9px;
  box-shadow:0 14px 36px rgba(0,0,0,.24);min-width:248px;padding:6px;display:flex;flex-direction:column}
.hmenu .mh{font-size:10px;letter-spacing:.09em;text-transform:uppercase;color:var(--ink-3);
  font-weight:600;padding:9px 9px 4px}
.hmenu .mh:first-child{padding-top:5px}
.hmenu .mi{display:flex;align-items:center;gap:9px;width:100%;text-align:left;font-size:13px;
  padding:7px 9px;border:none;border-radius:5px;background:none;color:var(--ink);cursor:pointer}
.hmenu button.mi:hover:not(:disabled){background:var(--panel-2);color:var(--ink)}
.hmenu label.mi:hover{background:var(--panel-2)}
.hmenu .mi .k{margin-left:auto;font-size:10px;color:var(--ink-3);border:1px solid var(--rule-2);
  border-radius:3px;padding:1px 5px}
.hmenu .mi .v{margin-left:auto;font-size:12px;color:var(--ink-3)}
/**
 * ETT VAL MAN KAN SE. En rad som bytte värde vid varje klick och stängde
 * menyn svarade inte på någon av de tre frågor den väckte: har jag valt? vad
 * valde jag? vad är vad? Alternativen står därför framme, det valda är märkt,
 * och menyn står kvar medan man byter.
 */
.hmenu .mval{cursor:default;flex-wrap:wrap;padding:6px 9px}
.hmenu .mval .ml{flex:none}
.hmenu .mval .msegs{margin-left:auto;display:flex;gap:2px;background:var(--panel-2);
  border:1px solid var(--rule-2);border-radius:6px;padding:2px}
.hmenu .mval .mseg{font:inherit;font-size:11.5px;line-height:1.35;padding:3px 8px;border:0;border-radius:4px;
  background:none;color:var(--ink-3);cursor:pointer;white-space:nowrap}
.hmenu .mval .mseg:hover{color:var(--ink);background:var(--panel)}
.hmenu .mval .mseg.on{background:var(--panel);color:var(--ink);font-weight:600;
  box-shadow:0 1px 2px rgba(0,0,0,.18)}
/** Ett val som inte spelar någon roll just nu säger det, i stället för att se aktivt ut. */
.hmenu .mval.dim .ml,.hmenu .mval.dim .msegs{opacity:.45}
/** Samma segmentval i rutan Larmbeteende — en rad per disciplin, tre val. */
.bet table th{font-size:10.5px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-3);text-align:left;padding:6px 6px 4px;font-weight:500}
.bet table td{padding:6px 6px;border-top:1px solid var(--rule-2);vertical-align:middle}
.bet .msegs{display:inline-flex;gap:2px;background:var(--panel-2);border:1px solid var(--rule-2);border-radius:6px;padding:2px}
.bet .mseg{font:inherit;font-size:11.5px;line-height:1.35;padding:3px 8px;border:0;border-radius:4px;background:none;color:var(--ink-3);cursor:pointer;white-space:nowrap}
.bet .mseg:hover{color:var(--ink);background:var(--panel)}
.bet .mseg.on{background:var(--panel);color:var(--ink);font-weight:600;box-shadow:0 1px 2px rgba(0,0,0,.18)}
@media (max-width:560px){.bet table,.bet tbody,.bet tr,.bet td{display:block} .bet table th{display:none}
  .bet td:first-child{border-top:1px solid var(--rule);padding-top:10px} .bet td{border-top:0;padding:3px 0}
  .bet .msegs{width:100%} .bet .mseg{flex:1;text-align:center}}
@media (max-width:560px){
  .hmenu .mval{flex-direction:column;align-items:stretch;gap:4px}
  .hmenu .mval .msegs{margin-left:0}
  .hmenu .mval .mseg{flex:1;text-align:center}
}
.hmenu .msep{height:1px;background:var(--rule-2);margin:5px 4px}

/* ---------- left rail ---------- */
#rail{position:absolute;left:0;top:52px;bottom:0;width:268px;z-index:24;background:var(--panel);
  border-right:1px solid var(--rule);display:flex;flex-direction:column;overflow:hidden}
body.norail #rail{display:none}
body.norail #stage{left:0}
body.norail .plan-panel,body.norail .pal,body.norail .zoomer{left:12px}
/* The plan panel and the zoom buttons would otherwise stack on each other. */
.plan-panel{bottom:52px}
.rail-search{padding:9px 10px;border-bottom:1px solid var(--rule-2)}
.rail-search input{width:100%;font:inherit;font-size:12.5px;padding:6px 9px;border-radius:3px;
  border:1px solid var(--rule);background:var(--panel-2);color:var(--ink)}
.rail-search input:focus{outline:none;border-color:var(--accent)}
.rail-body{overflow:auto;flex:1;padding-bottom:14px}
#rail section{border-bottom:1px solid var(--rule-2)}
.sec-h{font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3);
  font-weight:600;padding:9px 12px 5px;display:flex;align-items:center;gap:6px}
.row{display:flex;align-items:center;gap:8px;padding:5px 12px;cursor:pointer;font-size:12.5px}
.row:hover{background:var(--panel-2)}
.row.on{background:color-mix(in srgb,var(--accent) 14%,transparent);color:var(--ink)}
.row .dot{width:8px;height:8px;border-radius:50%;background:var(--ok);flex:none}
.row .dot.fire{background:var(--fire)} .row .dot.warn{background:var(--warn)}
.row .dot.idle{background:var(--idle)} .row .dot.unk{background:var(--unknown)}
.row .dot.acc{background:var(--accent)}
.row .nm{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.row .ct{font-size:11px;color:var(--ink-3);flex:none}
.row.l1{padding-left:6px;font-weight:600}
.row.l2{padding-left:20px}
.row.l3{padding-left:34px}
/* The fold chevron. A button, so the keyboard reaches it; a fixed width, so
   rows with and without one line up. */
.row .fold{width:14px;height:14px;flex:none;border:0;background:none;padding:0;margin:0;
  color:var(--ink-3);font-family:inherit;font-size:14px;font-weight:600;line-height:1;
  cursor:pointer;border-radius:2px;
  transform:rotate(90deg);transition:transform .12s}
.row .fold.shut{transform:rotate(0)}
.row .fold:hover{background:var(--rule-2);color:var(--ink)}
.row .fold.none{pointer-events:none}
.sec-h>span{margin-left:auto;display:flex;gap:4px}
.sec-h>span .btn.tiny{margin-left:0}
/* Which sheet this is. Small, because it is a property of the drawing, not a
   state — but present, because opening the wrong one wastes a site visit. */
.disc{font:600 8.5px/1.5 "IBM Plex Mono",monospace;letter-spacing:.06em;padding:1px 4px;
  border-radius:2px;margin-left:6px;border:1px solid currentColor;opacity:.75}
.disc.d-fire{color:var(--fire)}
.disc.d-security{color:var(--accent)}
/* Offered, but already drawn somewhere else. */
.pitem.elsewhere{opacity:.5}
.pitem.elsewhere .d{font-style:italic}
.pitem .demotag{font:600 9px/1 system-ui;text-transform:uppercase;letter-spacing:.04em;color:var(--ink-3);
  border:1px solid var(--rule-2);border-radius:3px;padding:1px 4px;margin-left:5px;vertical-align:middle}
.palplaced{margin:6px 2px 2px;padding:6px 7px;font-size:11.5px;color:var(--ink-3);cursor:pointer;
  border-top:1px dashed var(--rule-2);text-align:center}
.palplaced:hover{color:var(--ink-1)}
.row.alarm{color:var(--fire);font-weight:600}
.row .mono{font-size:11.5px}
.empty{font-size:12px;color:var(--ink-3);padding:8px 12px}
button.ghost{border:none;background:none;color:var(--ink-3);font-size:15px;padding:2px 6px}
button.ghost:hover{color:var(--accent)}
.assign{padding:8px 12px;border-bottom:1px solid var(--rule-2);background:color-mix(in srgb,var(--warn) 10%,transparent)}
.assign .t2{font-size:11.5px;color:var(--ink-2);margin-bottom:6px}
.assign select{width:100%;font:inherit;font-size:12px;padding:4px 6px;margin-bottom:5px;
  border:1px solid var(--rule);border-radius:2px;background:var(--panel);color:var(--ink)}

/* ---------- the detail panel can be moved and docked ---------- */
.side{cursor:default}
.side h2{cursor:move;user-select:none}
.side.free{top:auto;right:auto}

/* ---------- stage ---------- */
/* touch-action:none — the page claims the gestures, or the browser takes them
   first and the drawing cannot be panned with a finger at all. */
#stage{position:absolute;inset:52px 0 0 268px;overflow:hidden;background:var(--sea);
  touch-action:none}
/* `will-change: transform` BARA MEDAN KAMERAN RÖR SIG. Står den där permanent
   låser Chrome lagrets rasterskala vid 1 och skalar sedan upp pixlarna när man
   zoomar — symbolerna blir suddiga vid 5× fast de är vektorer, medan
   etiketterna (som ligger utanför #world) är knivskarpa. Tomas: "the symbol is
   very blurry." Klassen sätts av apply() under rörelsen och tas bort strax
   efter, så lagret rastreras om i den slutliga skalan. Se 23-kamera.js. */
/* `isolation: isolate` GER VÄRLDEN SIN EGEN STAPLING — det will-change tyst
   gjorde förut. Utan det sträckte kartans nålar (svg.pins, z-index 50) upp
   genom panelerna (z-index 22): inställningspanelen gick inte att rulla och
   musen visade "grab" ovanpå den. Tomas: "det är som att musen inte känner av
   panelen." Isolation kostar ingen rastrering, bara ordning. */
#world{position:absolute;transform-origin:0 0;isolation:isolate}
#world.rorlig{will-change:transform}
/**
 * ADDRESS LABELS LIVE OUTSIDE THE ZOOMED WORLD.
 *
 * They used to sit inside it, counter-scaled by 1/zoom to keep a constant
 * screen size. Arithmetically that is right and visually it is not: the
 * browser rasterises the scaled layer once and then transforms the bitmap, so
 * 8.5 px text inside a 3× world is drawn at 8.5 px and blown up, or drawn
 * large and squeezed down. Either way it is soft, and Tomas read it as
 * unreadable at ordinary zoom — correctly.
 *
 * This layer is never transformed. Positions are computed in screen
 * coordinates on every pan and zoom, in the same loop that sizes the symbols,
 * so the text is laid out at its real size and stays crisp at any zoom.
 */
#labels{position:absolute;inset:0;pointer-events:none;overflow:hidden;z-index:6}
/* The estate summary, floating over the map. */
#mapsum{position:absolute;left:14px;top:14px;z-index:8;width:330px;max-width:calc(100% - 28px);
  background:color-mix(in srgb,var(--panel) 92%,transparent);border:1px solid var(--rule-2);
  border-radius:5px;padding:12px 14px;box-shadow:0 6px 22px rgba(0,0,0,.10);backdrop-filter:blur(3px)}
#mapsum .ms-h{font-size:10.5px;letter-spacing:.09em;text-transform:uppercase;color:var(--ink-3);
  margin-bottom:7px;display:flex;align-items:center;gap:8px}
#mapsum .ms-h>span{flex:1 1 auto;min-width:0}
/**
 * Fällknappen. Stor nog för ett finger (se regeln om träffytor längre ned) och
 * diskret nog att inte tävla med en röd rad. Se drawMapSummary i del 25.
 */
#mapsum .ms-vik{flex:none;border:1px solid var(--rule-2);background:var(--panel-2);color:var(--ink-3);
  font:inherit;font-size:12px;line-height:1;padding:4px 9px;border-radius:3px;cursor:pointer}
#mapsum .ms-vik:hover{background:var(--panel);color:var(--ink)}
/**
 * HOPFÄLLD DÖLJER FAKTA, ALDRIG VARNINGAR. Brickraden står kvar därför att den
 * ÄR enradssvaret, och `.ms-warn.bad` — en drivrutin som inte svarar — går
 * inte att vika undan alls.
 */
#mapsum.ihop .ms-facts,
#mapsum.ihop .ms-ok,
#mapsum.ihop .ms-warn:not(.bad){display:none}
#mapsum.ihop .ms-h{margin-bottom:6px}
#mapsum .ms-ok{font-size:12.5px;color:var(--ok);margin-bottom:4px}
#mapsum .ms-warn{font-size:12.5px;color:var(--fire);line-height:1.45;margin-bottom:4px}
/**
 * En drivrutin som inte svarar är kortets värsta rad och ska läsas först.
 * Ramen finns för att den inte ska gå att svepa förbi som en mening bland
 * andra — samma tanke som .gonebar i lägesbilden.
 */
#mapsum .ms-warn.bad{font-weight:600;border:1px solid var(--fire);border-left-width:3px;
  border-radius:3px;padding:6px 8px;margin-bottom:7px;background:rgba(208,2,27,.07)}
#mapsum .ms-facts{font-size:12.5px;color:var(--ink-2);margin-bottom:9px}
#mapsum .chip{cursor:pointer}
#stage.grab{cursor:grab} #stage.grabbing{cursor:grabbing}

/* ---------- map ---------- */
.map-land{fill:var(--land);stroke:var(--rule);stroke-width:.8;vector-effect:non-scaling-stroke}
.map-land.other{fill:var(--land-2);opacity:.75}
/* The map is three layers: the outline (always), the tiles the server has
   (when it has them), and the pins. The outline shows through every gap the
   tiles leave — a tile that never came is a patch of plain land, not a hole. */
.mapwrap{position:relative;overflow:visible}
.mapwrap>svg{position:absolute;left:0;top:0;display:block;overflow:visible}
.mapwrap>svg.land{pointer-events:none;z-index:0}
.mapwrap>svg.pins{z-index:50}
/* ---------- the map's own ground ----------
   Dark or light independently of the page. Only the GROUND changes: the
   coastline, the borders and the labels keep their own contrast against it,
   because a dark map exists to make the drawn things stand out and not to
   make the whole picture quieter. */
body.mapdark{--sea:#05070A;--land:#12171D;--land-2:#0C1116}
body.mapdark .map-land{stroke:#4A5560;stroke-width:1}
body.mapdark .map-land.other{opacity:.85}
body.mapdark .bpin .lbl,body.mapdark .pin .sub,body.mapdark .pin text{stroke:#05070A}
body.mapdark .bpin .lbl{fill:#E7EBEF}
body.maplight{--sea:#DCE6EC;--land:#EFF2F4;--land-2:#E4E9ED}
body.maplight .map-land{stroke:#8B99A6}
body.maplight .bpin .lbl,body.maplight .pin .sub,body.maplight .pin text{stroke:#DCE6EC}
body.maplight .bpin .lbl{fill:#161A1F}
/* Street tiles darkened rather than fetched from a second source: one
   provider, one cache, one thing to keep alive. */
/**
 * "SOM SIDAN" HAS TO MEAN SOM SIDAN, TILES INCLUDED.
 *
 * The map's ground is an SVG painted from --sea and --land, and those already
 * follow the page's theme — so on a dark page the ground was dark and nobody
 * ever saw it, because the street tiles lie on top of it in full daylight
 * colour and only `mapdark` was darkening them. Tomas, three times, on two
 * machines: *"kartan ändrar sig inte"*, *"still not dark"* — and his very
 * first screenshot of the map was light too, on a dark page, with the theme
 * on its default. The default was quietly the one setting that did nothing.
 *
 * So the filter is declared once and applied in three places: the explicit
 * dark map, and "som sidan" under each of the two ways the page can be dark
 * (the OS's preference, and the page's own switch).
 */
body.mapdark #tiles img,
:root[data-theme="dark"] body:not(.mapdark):not(.maplight) #tiles img{
  filter:invert(1) hue-rotate(180deg) brightness(.82) contrast(1.06) saturate(.65)}
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]) body:not(.mapdark):not(.maplight) #tiles img{
    filter:invert(1) hue-rotate(180deg) brightness(.82) contrast(1.06) saturate(.65)}
}
#tiles{position:absolute;left:0;top:0;overflow:visible;pointer-events:none;z-index:1}
#tiles img{position:absolute;display:block;image-rendering:auto;opacity:0;transition:opacity .18s}
#tiles img.in{opacity:1}
.mapwrap.tiled .city,.mapwrap.tiled .city-dot{display:none}
#mapTools{position:absolute;right:8px;bottom:60px;z-index:6;display:flex;gap:6px}
#mapTools .btn{font-size:11.5px;padding:4px 8px;background:color-mix(in srgb,var(--panel) 88%,transparent)}
body.panelonly #mapTools{display:none}
#mapAttr{position:absolute;right:8px;bottom:36px;font-size:10.5px;color:var(--ink-3);
  background:color-mix(in srgb,var(--panel) 80%,transparent);padding:2px 6px;border-radius:3px;z-index:5;pointer-events:none}
.bpin{cursor:pointer}
.bpin .body{fill:var(--panel);stroke:var(--accent);stroke-width:2}
.bpin .lbl{fill:var(--ink);font-size:12px;font-weight:600;paint-order:stroke;stroke:var(--sea);stroke-width:4px}
.bpin.p-fire .body{stroke:var(--fire);fill:var(--fire)} .bpin.p-warn .body{stroke:var(--warn)}
.geo{margin:6px 0 10px}
.geo .row2{display:flex;gap:6px}
.geo .row2 input{flex:1;min-width:0}
.geo .hits{margin-top:6px;border:1px solid var(--rule);border-radius:4px;overflow:hidden}
.geo .hit{padding:6px 9px;font-size:12.5px;cursor:pointer;border-top:1px solid var(--rule-2);line-height:1.35}
.geo .hit:first-child{border-top:0}
.geo .hit:hover{background:var(--panel-2)}
.geo .hit .d{display:block;font-size:11px;color:var(--ink-3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.geo .msg{font-size:11.5px;color:var(--ink-3);margin-top:5px;line-height:1.45}
.geo .msg.bad{color:var(--warn)}
.city{fill:var(--ink-3)}
.city-dot{fill:var(--ink-3);opacity:.5}
.pin{cursor:pointer}
.pin .halo{fill:var(--accent);opacity:.16}
.pin .body{fill:var(--accent);stroke:#fff;stroke-width:2}
.pin.alarm .body{fill:var(--fire)}
.pin.alarm .halo{fill:var(--fire);opacity:.22}
/* The worst state at the site, in the same four colours the dots use. */
.pin.p-fire .body{fill:var(--fire)} .pin.p-fire .halo{fill:var(--fire);opacity:.2}
.pin.p-warn .body{fill:var(--warn)} .pin.p-warn .halo{fill:var(--warn);opacity:.2}
.pin.p-idle .body{fill:var(--idle)} .pin.p-idle .halo{fill:var(--idle);opacity:.18}
.pin.p-unk  .body{fill:var(--unknown)} .pin.p-unk .halo{fill:var(--unknown);opacity:.18}
.pin.picked .body{fill:var(--accent)} .pin.picked .halo{fill:var(--accent);opacity:.25}
.pin.picked{pointer-events:none}
.pin.p-acc  .body{fill:var(--accent)} .pin.p-acc .halo{fill:var(--accent);opacity:.18}
.pin.p-ok   .body{fill:var(--ok)} .pin.p-ok .halo{fill:var(--ok);opacity:.16}
.pin .lbl{fill:var(--ink);font-size:13px;font-weight:600;paint-order:stroke;
  stroke:var(--sea);stroke-width:4px;stroke-linejoin:round}
.pin .sub{fill:var(--ink-3);font-size:11px;paint-order:stroke;stroke:var(--sea);stroke-width:4px}

/* ---------- plan ---------- */
#planWrap{position:relative}
#planCanvas{display:block;box-shadow:0 2px 30px rgba(0,0,0,.18);background:#fff}
#marks{position:absolute;inset:0}
/**
 * A MARK IS A POINT, NOT A BOX.
 *
 * It used to be a flex column holding the glyph and the label, centred as a
 * whole with translate(-50%,-50%). That anchors the middle of glyph-plus-label
 * on the coordinate — so the glyph itself sits ABOVE the point by half the
 * label's height. Fine, until the label stopped scaling with the drawing: its
 * height in drawing units then changed with every zoom step, and the symbol
 * slid up and down the plan as you zoomed. Measured at 0.8 % of the sheet
 * between the extremes, which on a hospital wing is metres.
 *
 * This is the drift Tomas has been bitten by before, and it is worth being
 * precise about: the symbol must sit on its coordinate at every zoom, on every
 * screen. So the mark is a zero-size point, the glyph is centred on it, and
 * the label hangs below without ever moving it.
 */
.mark{position:absolute;width:0;height:0;cursor:pointer}
/* line-height:0 so the span hugs the SVG. Without it the inline box adds a
   descender's worth of height, the glyph is not centred on its coordinate, and
   the label sits that much too high — visibly overlapping it at high zoom. */
.mark .glyph{position:absolute;left:0;top:0;display:block;line-height:0;transform-origin:50% 50%}
.mark.sel .sym{outline:2px solid var(--accent);outline-offset:3px;border-radius:2px}
/* A schedule has its hands on this point. The clock sits off the symbol's
   shoulder rather than on it, because a symbol is a standardised drawing and
   nothing we add may be mistaken for part of it. Faint while the schedule is
   only waiting; solid while it is actually holding the point.

   THE CLASS IS `hassch`, NOT `sched`, AND THAT IS THE WHOLE POINT.
   `.sched` was already taken — it is the schedule CARD in the calendar panel,
   `border:1px solid; border-radius:4px; padding:9px 11px; background:var(--panel)`
   — and a mark that also carried `sched` picked all of it up. Every scheduled
   symbol on every drawing grew a 24×20 dark rounded box out of its anchor
   point, sitting over the plan next to the symbol. Tomas, three times: *"jag
   fattar fortfarande inte vad rektangeln fyller för funktion? Den kommer
   blocka detaljer på ritningar med många symboler."* He was right that it
   blocked detail and right that it meant nothing — it was a calendar card
   with no text in it, painted on a floor plan.

   Twice I guessed at it from a screenshot and twice I was wrong. It was found
   by rendering the page in headless Chromium and reading getComputedStyle off
   the mark: `background: rgb(23,27,33)` on an element whose own rule says
   `width:0;height:0`. A global class name is a global variable. */
.mark .schmark{position:absolute;left:6px;top:-14px;width:13px;height:13px;cursor:pointer;overflow:visible}
.mark .schmark:hover .rim,.mark .schmark:focus-visible .rim{fill:var(--accent)}
/* What a passersystem answered when the driver asked it — see shared/pacs.js.
   A capability that is off is not greyed out, it is a cross with the reason
   on hover, because "we cannot do that here" is an answer and a dimmed
   button is not. */
.drv .caps{margin:5px 0 2px 16px;font-size:11.5px}
.drv .caps .cs{color:var(--ink-3);margin-bottom:4px}
.drv .caps .cg{display:flex;flex-wrap:wrap;gap:4px 6px}
.drv .caps .cap{padding:1px 7px;border-radius:9px;font-size:10.5px;
  background:var(--panel-2);color:var(--ink-3);border:1px solid var(--rule-2);cursor:help}
.drv .caps .cap.on{background:color-mix(in srgb,var(--ok) 14%,transparent);color:var(--ok);border-color:transparent;cursor:default}
.drv .caps .cn{margin-top:4px;color:var(--ink-3);line-height:1.45}
.mark .schmark .rim{fill:var(--panel)}
.mark .schmark .face{fill:var(--idle)}
.mark .schmark .hands{stroke:var(--panel);stroke-width:2.4;stroke-linecap:round;fill:none}
/* Waiting: smaller and quieter — it is a note, not a state. */
.mark.hassch:not(.on) .schmark{width:11px;height:11px;top:-12px;opacity:.75}
/* Holding: full size, in the command colour, and the symbol gets a ring. */
.mark.hassch.on .schmark{width:14px;height:14px;top:-15px}
.mark.hassch.on .schmark .face{fill:var(--cmd)}
.mark.hassch.on .sym{outline:1.5px dashed var(--cmd);outline-offset:2px;border-radius:2px}
/* provning: not tested within the interval */
.mark.untested .sym{outline:2.5px dashed var(--warn);outline-offset:4px;border-radius:50%}
/* provning: tested within the period — a small tick, filled (the panel or a test round saw it) or hollow (marked by hand) */
.mark .provbock{position:absolute;left:5px;top:3px;width:13px;height:13px;border-radius:50%;background:var(--ok);color:#fff;
  font:700 9px/13px system-ui,sans-serif;text-align:center;pointer-events:none;box-shadow:0 0 0 1.5px var(--panel)}
.mark .provbock.hand{background:var(--panel);color:var(--ok);box-shadow:inset 0 0 0 1.5px var(--ok)}
#covBtn.on{background:var(--warn);color:#3B2A00;border-color:var(--warn)}
/* One size, never scaled — see #labels. 10px rather than 8.5: it is an address
   somebody has to read off a screen two metres away, not a footnote. */
#labels .addr{position:absolute;font:600 10px/1 "IBM Plex Mono",monospace;color:var(--ink-2);
  border:1px solid var(--rule-2);background:var(--panel);padding:1.5px 4px;border-radius:2px;
  white-space:nowrap;transform:translate(-50%,0)}
#labels .addr .mult{display:inline-block;min-width:13px;padding:0 3px;border-radius:7px;
  background:var(--accent);color:#fff;font-size:9px;line-height:13px;text-align:center;margin-left:3px}
#labels .addr.sel{border-color:var(--accent);color:var(--ink)}
/* Zoomed far enough out that the glyph is at its minimum: labels would be
   overlapping mush, so they go. The symbols stay clickable. */
#labels .addr.tiny{display:none}
/* För tätt för etiketter vid den här zoomen — se etikettMedianAvstand i 23-kamera.js. */
#labels.trangt .addr{display:none}

/* symbol states — one geometry, styling carries the state */
svg.sym{overflow:visible}
svg.sym .geo{fill:none;stroke:currentColor;stroke-width:6;stroke-linecap:round;stroke-linejoin:round}
svg.sym .geo .solid{fill:currentColor;stroke:currentColor}
svg.sym .geo .fillq{fill:currentColor;stroke:none}
svg.sym .geo .dash{stroke-dasharray:10 8}
svg.sym .geo .thin{stroke-width:4.5}
/* the door: a filled leaf; the knob and a blocking cross are cut out of it */
svg.sym .geo .hole{fill:var(--panel);stroke:none}
svg.sym .geo .cut{stroke:var(--panel);fill:none;stroke-width:8}
svg.sym .geo text.lbl{fill:currentColor;stroke:none;font:600 34px/1 "IBM Plex Sans",sans-serif;text-anchor:middle}
svg.sym .geo text.lbl.sm{font-size:26px}
svg.sym .badge{stroke:currentColor;stroke-width:7;stroke-linecap:round;fill:none}
/*
 * Colour carries STATE, not equipment type.
 *
 * SB 20.1 draws fire devices in red, and that is right for paper: on a printed
 * drawing colour says what kind of equipment this is. On a live screen colour
 * has to say what state it is in, and it cannot say both. A screen full of red
 * detectors that are all perfectly healthy trains an operator to ignore red,
 * which is the one thing that must never happen.
 *
 * So: green healthy, yellow needs attention, red alarm, grey out of service.
 */
svg.sym.s-normal{color:var(--ok)}
svg.sym.s-fire{color:var(--fire)}
svg.sym.s-fire .geo{stroke-width:7.5}
svg.sym.s-prealarm{color:var(--warn)}
svg.sym.s-prealarm .geo{stroke-width:8}
svg.sym.s-tech{color:var(--warn)}
svg.sym.s-fault{color:var(--warn)}
svg.sym.s-test{color:var(--unknown)}
/**
 * OUT OF SERVICE MUST STILL BE READABLE.
 *
 * These carried opacity .6–.7 on top of an already muted grey, on the
 * reasoning that a disabled device should recede. Zoomed out on a real
 * drawing that is a smudge: Tomas could see something was there and not what.
 * A disabled detector is not less important than a healthy one — it is a hole
 * in the cover, and it has to be identifiable.
 *
 * So no transparency. Grey says out of service, the slash says which kind, and
 * the geometry stays as legible as any other state's.
 */
svg.sym.s-disabled{color:var(--idle)}
svg.sym.s-derived{color:var(--idle)}
svg.sym.s-offline{color:var(--idle)}
svg.sym.s-unknown{color:var(--unknown)}
/* The badge is what tells these apart at a glance, so it is drawn heavier than
   the geometry rather than lighter — at 12 px it is the only thing that reads. */
svg.sym.s-disabled .badge,svg.sym.s-derived .badge,svg.sym.s-offline .badge{stroke-width:9}
/* Derived — disabled because something upstream is — is a thinner slash, so
   "I turned this off" and "something else turned this off" are distinguishable
   without reading the panel. */
svg.sym.s-derived .badge{stroke-dasharray:20 9}
/* Armed is healthy, but "quiet" and "quiet because nobody is watching" must
   not look identical — same green, plus a ring. */
svg.sym.s-armed{color:var(--ok)}
/* En nödljusarmatur som lyser gör sitt jobb. Nödbelysningens egen gröna, inte
   larmets röda och inte felets gula — se `active` i web/symbols.js. */
svg.sym.s-active{color:#00843D}
/* Uppställd: deliberate, not broken, and not green. */
svg.sym.s-unlocked{color:var(--accent)}
.tags .tag .src{text-transform:none;font-weight:500;opacity:.6;margin-left:5px;letter-spacing:0}
/* Blockerad: also deliberate, and the opposite problem — nobody gets through. */
svg.sym.s-blocked{color:var(--accent);opacity:.8}
svg.sym .badge.ring{stroke-width:5;stroke-dasharray:14 10;opacity:.8}
/* A halo so the symbol survives being dropped on a dark zone fill. */
svg.sym{filter:drop-shadow(0 0 1.5px var(--panel)) drop-shadow(0 0 3px var(--panel))}
/**
 * TWO SPEEDS OF ATTENTION.
 *
 * `beat` is an alarm: a second, big, impossible to sit next to. It is meant to
 * pull an eye across a room.
 *
 * `breathe` is everything else that is not normal — fel, frånkopplat, test,
 * offline. Tomas asked for "some discrete blinking effect" on those, and
 * discreet is the whole specification: a plan can carry two hundred disabled
 * points after a service visit, and if they flashed like alarms the screen
 * would be unusable and the alarms would stop meaning anything. Slow, shallow,
 * no movement — you notice it when you look at the plan, not from the corridor.
 */
@keyframes beat{0%,100%{opacity:1;transform:scale(1.2)}50%{opacity:.35;transform:scale(1.4)}}
@keyframes breathe{0%,100%{opacity:1}50%{opacity:.55}}
/**
 * An alarm is DRAWN BIGGER, blinking or not. The blink is the demand for
 * attention and stops when somebody acknowledges; the size and the colour are
 * the fact and stay until the panel resets. `.acked` also gets a ring, so a
 * screen full of red says which ones have already been dealt with.
 */
svg.sym.alarm{transform:scale(1.2);transform-origin:50% 50%}
svg.sym.pulse{animation:beat 1s ease-in-out infinite;transform-origin:50% 50%}
svg.sym.acked{filter:drop-shadow(0 0 0 var(--panel)) drop-shadow(0 0 2.5px var(--panel))}
.pselbar{display:flex;align-items:center;gap:6px;padding:6px 8px;margin:6px 0;
  border:1px solid var(--accent);border-radius:4px;background:color-mix(in srgb,var(--accent) 8%,transparent);flex-wrap:wrap}
.pselbar .n{font-weight:600}
.prow .psel{display:flex;align-items:center;padding:0 6px 0 2px;cursor:pointer}
.prow.picked{background:color-mix(in srgb,var(--accent) 10%,transparent)}
.iamw{border:1px solid var(--rule);border-radius:4px;padding:8px 10px;margin:6px 0}
.iamw .iamwrow{display:flex;gap:6px;align-items:center;margin:6px 0;flex-wrap:wrap}
.iamw .iamwrow select,.iamw .iamwrow input{flex:1;min-width:160px}
.iamw .iamwq{margin-top:6px;padding-top:6px;border-top:1px solid var(--rule)}
.iamw .iamwq ul{margin:4px 0;padding-left:16px}
.iamw .iamwq li{line-height:1.5}
.iamw .iamwnote{margin-left:8px}
/* VAD GRUPPEN ÖPPNAR — raden under gruppnamnet i behörighetsvyn. */
.opens{font-size:11.5px;color:var(--ink-2);margin-top:2px}
.opens .ct{color:var(--ink-3)}
/* "nästan" — samma grupp, annan stavning. Ska läsa som något att åtgärda. */
.opens.nearly{display:flex;gap:8px;align-items:center;flex-wrap:wrap;color:var(--warn,#b8860b)}
.opens.nearly b{color:var(--ink)}
/* RAPPORTMOTORN: en tabell, ett formulär, en förhandsvisning. */
.repform{border-top:1px solid var(--rule);margin-top:10px;padding-top:10px}
.repform .fld{display:flex;flex-direction:column;gap:3px;font-size:11.5px;color:var(--ink-3);min-width:150px;flex:1}
/**
 * VIT TEXT PÅ VIT BAKGRUND.
 *
 * Den här raden satte FÄRGEN men aldrig bakgrunden. I ljust tema gick det
 * ihop av en slump: webbläsarens vita förval under mörk text. I mörkt tema
 * blev `--ink` nästan vit — på webbläsarens fortfarande vita bakgrund.
 * Tomas, 15 september: *"kan inte se datan ja lägger in vid skapa rapport."*
 * Uppmätt kontrast 1,2:1. Fälten var inte tomma; de var osynliga.
 *
 * Fyra fält i huset hade felet, alla här — de andra 68 sätter sin bakgrund.
 * `test/kontrast.js` mäter numera varenda ett i båda temana.
 */
.repform .fld input,.repform .fld select{font-size:13px;color:var(--ink);background:var(--panel);
  border:1px solid var(--rule);border-radius:3px;padding:5px 8px}
.repout{border:1px solid var(--rule);border-radius:4px;padding:10px;margin-top:10px}
/* INCIDENTLISTAN SOM ARBETSLISTA: filter, markering, förslag. */
.incbar{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin:6px 0 8px}
.incbar .incsel{display:flex;gap:6px;align-items:center;padding-left:8px;border-left:1px solid var(--rule)}
.inctab tr.inc-closed{opacity:.62}
.incprop{border:1px solid var(--rule);border-radius:4px;padding:8px 10px;margin:0 0 10px;
  background:color-mix(in srgb, var(--accent) 5%, transparent)}
.incprop .incprow{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:6px}
.incprop .tier{width:8px;height:8px;border-radius:50%;background:var(--ink-3);flex:none}
.incprop .tier.green{background:var(--ok,#2c7350)}
.incprop .tier.yellow{background:var(--warn,#b8860b)}
#incClosePick{position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:60;display:flex;align-items:center;justify-content:center}
#incClosePick .box{background:var(--panel);border:1px solid var(--rule);border-radius:4px;padding:14px;width:min(520px,92vw)}
/* VILKEN KAMERA SER VAD. Samma block från båda hållen; redigerbart bara på kameran. */
.cov{margin:10px 0;padding-top:8px;border-top:1px solid var(--rule)}
.cov .covchips{display:flex;gap:6px;flex-wrap:wrap;margin:6px 0}
.cov .chip{cursor:default}
.cov .chip[data-gocam]{cursor:pointer}
.cov .chip.gone{opacity:.65;text-decoration:line-through}
.cov .chip b{cursor:pointer;margin-left:4px;opacity:.6}
.cov .chip b:hover{opacity:1}
.cov .covhits{display:flex;gap:6px;flex-wrap:wrap;margin-top:4px}
/* VÄLJ — SE — SKICKA. Planen innan massutdelningen skickas. */
.brplan{border:1px solid var(--rule);border-radius:4px;padding:10px 12px;margin-top:8px}
.brplan .brsum{font-weight:600;margin-bottom:8px}
.brplan .brsys{display:flex;gap:8px;align-items:center;padding:4px 0;border-top:1px solid var(--rule)}
.brplan .brsys.down{opacity:.6}
.brplan .brsys .brn{margin-left:auto;font-variant-numeric:tabular-nums}
.brplan .brblock{margin-top:10px;padding:8px 10px;border-radius:4px;
  background:color-mix(in srgb, var(--warn,#b8860b) 10%, transparent)}
.brplan .brwhy{margin-top:6px;line-height:1.45}
.sambar{border:1px solid var(--warn,#b8860b);border-radius:4px;padding:8px 10px;margin:8px 0;
  background:color-mix(in srgb, var(--warn,#b8860b) 8%, transparent)}
.sambar>b{display:block;margin-bottom:4px}
.sambar .sl{margin:4px 0}
.sambar .s1{padding:2px 0;line-height:1.35}
.sambar .s1 .nm{font-weight:600;margin-right:6px}
.row.alarm .ord{display:inline-flex;align-items:center;justify-content:center;min-width:16px;height:16px;
  border-radius:8px;background:var(--rule);color:var(--ink-2);font-size:10px;font-weight:700;margin-right:6px;flex:none}
.row.alarm .alarmwhen{display:block;font-size:10.5px;opacity:.85}
.row.alarm.ackd .dot.fire.still{animation:none;opacity:.75}
.row.alarm.ackd{opacity:.9}
.dpol{border:1px solid var(--rule);border-radius:4px;padding:8px 10px;margin:8px 0;background:var(--panel-2,transparent)}
.dpol .dpolm{display:flex;flex-direction:column;gap:2px;margin:4px 0}
.dpol .dpolp{margin:6px 0 2px;padding-left:8px;border-left:2px solid var(--rule)}
.dpol .tog.on{font-weight:600}
.dpol .acts{display:flex;align-items:center;gap:8px;margin-top:6px}
.mark.ackd::after{content:"";position:absolute;left:50%;top:50%;width:calc(100% + 12px);height:calc(100% + 12px);
  transform:translate(-50%,-50%);border:1.5px dashed var(--ok,#3a9a5c);border-radius:50%;pointer-events:none;opacity:.85}
svg.sym.s-fault,svg.sym.s-tech,svg.sym.s-disabled,svg.sym.s-derived,
svg.sym.s-offline,svg.sym.s-test,svg.sym.s-masked{
  animation:breathe 2.6s ease-in-out infinite}
@media (prefers-reduced-motion: reduce){svg.sym.pulse,svg.sym[class*="s-"]{animation:none}}

/* ---------- område: vilka byggnader ett konto ser ---------- */
.omrbox { margin: 4px 0 10px; padding: 10px 12px; border: 1px solid var(--line, rgba(128,128,128,.3));
          border-radius: 8px; background: rgba(128,128,128,.05); }
.omrtree { display: flex; flex-direction: column; gap: 10px; }
.omrsite .chk.site { font-size: 12.5px; }
.omrbs { display: flex; flex-wrap: wrap; gap: 4px 18px; padding: 2px 0 0 20px; }
.omrbs .chk { font-size: 12.5px; }
.omr .omrsum { max-width: 46ch; }

/* ---------- panels ---------- */
.side{position:absolute;top:64px;right:12px;width:310px;max-height:calc(100% - 80px);
  background:var(--panel);border:1px solid var(--rule);border-radius:4px;z-index:20;
  display:flex;flex-direction:column;overflow:hidden;box-shadow:0 8px 30px rgba(0,0,0,.14)}
.side h2,.pal h2{font-size:11px;letter-spacing:.09em;text-transform:uppercase;color:var(--ink-3);
  margin:0;padding:11px 14px;border-bottom:1px solid var(--rule-2);font-weight:600;
  display:flex;justify-content:space-between;align-items:center;gap:8px}
.side .body{overflow:auto;padding:12px 14px}
.pal h2 .tag{font-size:10px}
.side .body p{margin:0 0 9px;font-size:13px;color:var(--ink-2)}
.tags{display:flex;flex-wrap:wrap;gap:5px;margin-bottom:9px}
.tag{font-size:10px;letter-spacing:.05em;text-transform:uppercase;font-weight:600;
  padding:2px 6px;border-radius:2px;background:var(--panel-2);color:var(--ink-3)}
.tag.fire{background:rgba(208,2,27,.16);color:var(--fire)}
.tag.warn{background:rgba(154,106,18,.16);color:var(--warn)}
.tag.idle{background:rgba(124,133,147,.2);color:var(--ink-2)}
.kv{display:grid;grid-template-columns:auto 1fr;gap:3px 12px;font-size:12px;margin:0 0 10px}
.kv dt{color:var(--ink-3)} .kv dd{margin:0;color:var(--ink-2)}
.acts{display:flex;flex-wrap:wrap;gap:6px}
button.btn{font:inherit;font-size:12px;padding:4px 10px;border-radius:2px;cursor:pointer;
  border:1px solid var(--rule);background:var(--panel-2);color:var(--ink)}
button.btn:hover:not(:disabled){border-color:var(--accent);color:var(--accent)}
button.btn:disabled{opacity:.4;cursor:not-allowed}
button.btn.warn{border-color:var(--warn);color:var(--warn)}
button.btn.on{background:var(--accent);border-color:var(--accent);color:#fff}
/*
 * EN FILUPPLADDNING ÄR EN ETIKETT, INTE EN KNAPP.
 *
 * En dold <input type=file> öppnas genom sin <label>, och den skrevs
 * `<label class="btn">` på tre ställen — men hela knappstilen var skriven
 * `button.btn`, så etiketterna renderades som naken text. "Ladda upp
 * kartpaket…" har sett ut som en mening att läsa i stället för något att
 * trycka på sedan kartpaketen kom.
 */
label.btn{display:inline-block;font:inherit;font-size:12px;padding:4px 10px;border-radius:2px;cursor:pointer;
  border:1px solid var(--rule);background:var(--panel-2);color:var(--ink)}
label.btn:hover{border-color:var(--accent);color:var(--accent)}
.note{font-size:11.5px;margin-top:9px;padding:6px 8px;border-radius:2px;background:var(--panel-2);color:var(--ink-2)}
.note.good{background:rgba(44,115,80,.13);color:var(--ok)}
.note.bad{background:rgba(208,2,27,.13);color:var(--fire)}
.note{position:relative}.note:has(>.x){padding-right:24px}.note .x{position:absolute;right:4px;top:3px;border:0;background:none;color:inherit;opacity:.55;cursor:pointer;font-size:14px;line-height:1;padding:2px 4px}.note .x:hover{opacity:1}
/* The way out of a dead end, on the page where the dead end is felt. Bigger
   than .note because it is the next thing to do, not a footnote. */
.route{font-size:13px;line-height:1.55;margin:0 0 16px;padding:11px 14px;border-radius:3px;
  background:var(--panel-2);border-left:3px solid var(--accent);color:var(--ink-2);max-width:640px}
.route b{color:var(--ink)}
.route .btn{margin-top:2px}
.confirm{background:rgba(154,106,18,.13);border:1px solid var(--warn);border-radius:2px;
  padding:9px 10px;margin-top:9px;font-size:12px;color:var(--ink-2)}
.confirm .acts{margin-top:8px}


/* ---------- demo mode ----------
 * Loud on purpose. A simulated point that reads as a live one is the worst
 * thing this software could do, so demo mode changes the chrome rather than
 * adding a discreet label somebody stops noticing after ten minutes.
 */
#demoBar{position:absolute;left:0;right:0;top:52px;height:24px;z-index:60;
  display:flex;align-items:center;gap:10px;padding:0 14px;font-size:11px;font-weight:600;
  letter-spacing:.07em;text-transform:uppercase;color:#3B2A00;
  white-space:nowrap;overflow:hidden;
  background:repeating-linear-gradient(135deg,#F5C542 0 14px,#E3B02F 14px 28px);
  border-bottom:1px solid #B98F1E}
#demoBar .lite{font-weight:500;letter-spacing:.02em;text-transform:none;color:#4A3608;
  overflow:hidden;text-overflow:ellipsis;min-width:0}
#demoBar>span:first-child{flex:none}
#demoBar button{flex:none;background:rgba(255,255,255,.75);border-color:#8A6A10;color:#3B2A00;
  font-size:11px;padding:2px 8px;white-space:nowrap}
#demoBar button:hover{background:#fff;border-color:#3B2A00;color:#000}
#demoBar.lic{background:#F2D9A0;border-bottom-color:#B98F1E}
#demoBar.lic.bad{background:#E8B4B0;color:#3A0A06;border-bottom-color:#8E2A22}
#demoBar.lic.bad .lite{color:#3A0A06}
#demoBar.lic.bad button{border-color:#8E2A22;color:#3A0A06}
#demoBar button{margin-left:auto}
body.demo #rail,body.demo .plan-panel,body.demo .pal,body.demo .side{top:76px}
/**
 * THE ALARM BAR MUST NOT COVER A CONTROL.
 *
 * It is centred at the top with z-index 25, and a panel's header — including
 * its close button — sat right underneath it whenever an alarm was up. Nobody
 * had noticed because the panels are usually opened on a quiet system; the
 * moment there were three alarms on screen, the lägesbild could not be closed.
 * Panels start below the bar while it is there.
 */
body.alarmbar .plan-panel{top:112px;max-height:none}
body.demo #rail{top:76px}
body.demo #stage{top:76px}
body.demo header{box-shadow:none}
/* a simulated device carries the mark on the symbol itself */
/* Anchored to the SYMBOL, not to the mark box — the address label makes the
   box far wider than the symbol, so "top right of the mark" was a dot floating
   in the middle of the next room. */
.mark.sim svg.sym{outline:2px solid #E3B02F;outline-offset:2px;border-radius:50%}
.tag.sim{background:rgba(227,176,47,.25);color:#8A6A10}
.tag.inv{background:rgba(107,91,149,.18);color:var(--unknown)}
.ctr.hot b{color:var(--fire)}
.ctr.hot{color:var(--fire)}

/* server settings */
#cogPanel{left:auto;right:12px;bottom:12px;width:340px}
/* ---- login and users */
#login{position:fixed;inset:0;z-index:1000;background:var(--ground);display:flex;align-items:center;justify-content:center}
#login .box.wide{width:min(760px,94vw);max-height:92vh;overflow:auto}
.wrule{border:1px solid var(--rule);border-radius:3px;padding:8px 10px 4px;margin-bottom:8px;background:var(--panel-2)}
.wgrid{display:grid;grid-template-columns:1.35fr 1fr 1.4fr;gap:12px;font-size:12px}
/* A discipline and the events under it. Folded shut until it is on, so the
   column is five lines when nothing is watched and open where it matters. */
.wdis{border:1px solid var(--line);border-radius:6px;padding:5px 7px;margin-bottom:5px;background:var(--panel-2)}
.wdis.on{border-color:var(--accent)}
.wdis .dh{font-size:12px}
.wdis .dh .ct{margin-left:auto;font-size:10.5px;color:var(--ink-3)}
.wdis .dh{display:flex;align-items:center;gap:6px}
.wev{display:none;margin:4px 0 0 18px;padding-top:4px;border-top:1px dashed var(--line)}
.wdis.on .wev{display:block}
.wev .tog{font-size:11.5px}
.wgrid .lbl{font-size:10.5px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-3);margin-bottom:3px}
.wgrid .tog{display:block;font-size:12px;margin:0 0 2px}
.wgrid .days{display:flex;flex-wrap:wrap;gap:2px 8px;font-size:11.5px;margin:2px 0 4px}
.wgrid .ct{font-size:10.5px;color:var(--ink-3)}
#login .box{width:min(380px,92vw);background:var(--panel);border:1px solid var(--rule);border-radius:4px;padding:22px 24px 20px;box-shadow:0 12px 40px rgba(0,0,0,.25)}
#login h2{margin:0 0 2px;font-size:18px;font-weight:600}
#login .sub{font-size:12px;color:var(--ink-3);margin:0 0 14px;line-height:1.5}
#login .fld{display:block;font-size:11px;color:var(--ink-3);margin:0 0 10px}
#login .fldin{display:block;width:100%;box-sizing:border-box;margin-top:3px;font-size:14px;padding:7px 9px}
#login .acts{margin-top:6px;display:flex;align-items:center;gap:8px;flex-wrap:wrap}
#login .ct{font-size:11.5px;color:var(--ink-3)}
#login .btn.primary{background:var(--accent,#2c7350);color:#fff;border-color:transparent;padding:7px 16px}
#openBtn{background:#b8860b;color:#fff;border-color:#8a6a10;white-space:nowrap}
#openBtn:hover{background:#9a7009}
/* ---------- accounts ---------- */
.users{margin:0 0 16px}
.urow{border:1px solid var(--rule-2);border-radius:6px;padding:9px 11px;margin:0 0 8px;background:var(--panel)}
.urow.off{opacity:.62;border-style:dashed}
.urow .l1{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
.urow .l1 .nm{font-weight:600;font-size:13.5px}
.urow .l1 .sp{flex:1}
.urow .l3{font-size:11px;color:var(--ink-3);margin-top:5px}
.urow .grants{margin:6px 0 4px}
.urow .l2{display:flex;gap:10px;margin-top:4px}
.urow .l2 label{flex:1;font-size:10.5px;letter-spacing:.05em;text-transform:uppercase;color:var(--ink-3);font-weight:600}
.urow .l2 select{display:block;width:100%;margin-top:3px;font:inherit;font-size:12.5px;font-weight:400;
  text-transform:none;letter-spacing:0;padding:4px 7px;border:1px solid var(--rule);border-radius:4px;
  background:var(--panel-2);color:var(--ink)}
/* ---------- who is watching ---------- */
.stations{border:1px solid var(--rule-2);border-radius:6px;overflow:hidden;margin:0 0 4px}
.strow{display:flex;align-items:center;gap:10px;padding:7px 11px;font-size:12.5px;border-bottom:1px solid var(--rule-2)}
.strow:last-child{border-bottom:0}
.strow .nm{font-weight:600;min-width:110px}
.strow .ip{color:var(--ink-3);font-size:11.5px;min-width:120px}
.strow .vw{flex:1;display:flex;gap:4px;flex-wrap:wrap}
.strow .ct{color:var(--ink-3);font-size:11px;white-space:nowrap}
/* ---------- measurement ---------- */
.mgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:8px;margin:0 0 14px}
.mtile{border:1px solid var(--rule-2);border-left:3px solid var(--rule);border-radius:8px;padding:9px 12px;background:var(--panel)}
.mtile .k{font-size:10.5px;letter-spacing:.05em;text-transform:uppercase;color:var(--ink-3)}
.mtile .v{font-size:20px;font-weight:600;font-variant-numeric:tabular-nums;letter-spacing:-.01em;margin-top:1px}
.mtile .u{font-size:11px;color:var(--ink-3);font-weight:400}
.mtile.warn{border-left-color:var(--warn)}
.mtile.bad{border-left-color:var(--fire)}
.mtile.ok{border-left-color:var(--ok)}
/* One row per measured series. The sparkline is an inline SVG rather than a
   library: it is forty numbers and a polyline, and a chart library on a page
   whose problem is that it is slow would be its own joke. */
.mrow{display:flex;align-items:center;gap:10px;padding:5px 0;border-bottom:1px solid var(--rule-2);font-size:12.5px}
.mrow .nm{width:190px;flex:0 0 190px;color:var(--ink-2)}
.mrow .spark{flex:1;min-width:120px;height:26px}
.mrow .spark path{fill:none;stroke:var(--accent);stroke-width:1.5;vector-effect:non-scaling-stroke}
.mrow .spark .fillp{fill:color-mix(in srgb,var(--accent) 14%,transparent);stroke:none}
.mrow .now{width:96px;text-align:right;font-variant-numeric:tabular-nums;font-weight:600}
.mrow .peak{width:96px;text-align:right;font-variant-numeric:tabular-nums;color:var(--ink-3);font-size:11.5px}
.mhead{display:flex;gap:10px;font-size:10px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-3);
  padding-bottom:4px;border-bottom:1px solid var(--rule)}
.mhead .nm{width:190px;flex:0 0 190px}.mhead .spark{flex:1;min-width:120px}
.mhead .now,.mhead .peak{width:96px;text-align:right}
.mhead.mprat{font-size:12px;letter-spacing:0;text-transform:none;color:var(--ink-2)}.mhead.mprat .nm{flex:1 1 auto;width:auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mhead.mprat .spark{flex:0 0 0;min-width:0}
.mverdict{border:1px solid var(--rule-2);border-left:4px solid var(--link);border-radius:8px;padding:11px 14px;
  margin:0 0 14px;background:color-mix(in srgb,var(--link) 7%,var(--panel));font-size:12.5px;line-height:1.6}
.mverdict b{color:var(--link)}
.mverdict.bad{border-left-color:var(--fire);background:color-mix(in srgb,var(--fire) 8%,var(--panel))}
.mverdict.bad b{color:var(--fire)}
/* ---------- the person register (IAM) ---------- */
/* A person is not a settings row. The list is SCANNED — "is this the Anna I
   mean" — so it is built to be read at a glance rather than parsed: an
   initials disc whose hue is derived from the person's own id, so the same
   human is the same colour on every screen and in every session; the name
   leading; and the two facts that change how a row must be READ — whose
   person it is, and whether they still work here — as coloured pills at the
   end, where the eye lands last and stays.

   The colours are not decoration. Blue dashed = somebody else's register.
   Grey = gone. Amber = blocked. Nothing = ours and fine, which is the
   overwhelming majority and should therefore be the quietest thing here. */
.iamwrap{--p1:var(--accent)}
.pcount{display:flex;gap:8px;flex-wrap:wrap;margin:0 0 14px}
.pcount .c{flex:1 1 116px;border:1px solid var(--rule-2);border-radius:8px;padding:9px 12px 8px;
  background:var(--panel);font-size:11px;color:var(--ink-3);cursor:pointer;white-space:nowrap;
  border-left:3px solid var(--rule);transition:border-color .12s,background .12s}
.pcount .c:hover{border-color:var(--rule);background:var(--panel-2)}
.pcount .c b{display:block;font-size:21px;font-weight:600;color:var(--ink);line-height:1.15;
  font-variant-numeric:tabular-nums;letter-spacing:-.01em}
.pcount .c.on{border-color:var(--accent);border-left-color:var(--accent);
  background:color-mix(in srgb,var(--accent) 9%,var(--panel))}
.pcount .c.k-vara{border-left-color:var(--ok)}
.pcount .c.k-lasta{border-left-color:var(--link)}
.pcount .c.k-avreg{border-left-color:var(--idle)}
.pcount .c.k-cred{border-left-color:var(--cmd);cursor:default}
.pbar{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin:0 0 12px}
.pbar input[type=search]{flex:1;min-width:200px;font:inherit;font-size:13px;padding:7px 11px;
  border:1px solid var(--rule);border-radius:6px;background:var(--panel-2);color:var(--ink)}
.pbar input[type=search]:focus{outline:none;border-color:var(--accent);
  box-shadow:0 0 0 2px color-mix(in srgb,var(--accent) 22%,transparent)}

/* The disc. Hue from the id, fixed saturation and lightness so twelve of them
   in a column read as one family rather than a paint chart. */
.pav{width:34px;height:34px;border-radius:50%;flex:0 0 34px;display:grid;place-items:center;
  font-size:12.5px;font-weight:600;letter-spacing:.02em;color:#fff;
  background:hsl(var(--h) 42% 42%);text-transform:uppercase;user-select:none}
@media (prefers-color-scheme: dark){:root:not([data-theme="light"]) .pav:not(.gone){background:hsl(var(--h) 38% 47%)}}
:root[data-theme="dark"] .pav:not(.gone){background:hsl(var(--h) 38% 47%)}
/* Still their own colour — a person is the same person whoever holds the
   record — with a dashed ring saying the record is somebody else's. */
.pav.obs{outline:2px dashed var(--link);outline-offset:2px}
.pav.gone{background:var(--panel-2)!important;color:var(--ink-3);border:1px solid var(--rule)}
.pav.big{width:54px;height:54px;flex-basis:54px;font-size:19px;border-radius:14px}

.prow{border:1px solid var(--rule-2);border-radius:8px;padding:9px 12px;margin:0 0 7px;background:var(--panel);
  cursor:pointer;display:flex;align-items:center;gap:11px;transition:border-color .12s,transform .06s}
.prow:hover{border-color:var(--accent);transform:translateX(1px)}
.prow.off{opacity:.62}
.prow.obs{border-left:3px solid var(--link)}
.prow .who{flex:1;min-width:0}
.prow .nm{font-weight:600;font-size:13.5px;color:var(--ink)}
.prow .sub{font-size:11px;color:var(--ink-3);margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.prow .tags{display:flex;gap:5px;flex-wrap:wrap;justify-content:flex-end;align-items:center}
.pill{display:inline-flex;align-items:center;gap:4px;font-size:10.5px;font-weight:600;letter-spacing:.02em;
  padding:2px 8px;border-radius:11px;white-space:nowrap;text-transform:uppercase}
.pill.obs{color:var(--link);background:color-mix(in srgb,var(--link) 14%,transparent);
  border:1px dashed color-mix(in srgb,var(--link) 55%,transparent);text-transform:none;letter-spacing:0}
.pill.gone{color:var(--ink-3);background:var(--panel-2);border:1px solid var(--rule-2)}
.pill.warn{color:var(--warn);background:color-mix(in srgb,var(--warn) 15%,transparent)}
.pill.ok{color:var(--ok);background:color-mix(in srgb,var(--ok) 14%,transparent)}
.pill.cred{color:var(--cmd);background:color-mix(in srgb,var(--cmd) 13%,transparent);
  text-transform:none;letter-spacing:0;font-variant-numeric:tabular-nums}

/* The card. A coloured band at the top so a person page never reads as a
   settings page, and so "read from ARX" is visible before anything else. */
.pcard .phead{display:flex;gap:14px;align-items:center;padding:14px 16px;border-radius:10px;margin:0 0 16px;
  background:linear-gradient(100deg,color-mix(in srgb,var(--accent) 13%,var(--panel)),var(--panel) 78%);
  border:1px solid var(--rule-2);border-left:4px solid var(--accent)}
.pcard .phead.obs{background:linear-gradient(100deg,color-mix(in srgb,var(--link) 14%,var(--panel)),var(--panel) 78%);
  border-left-color:var(--link)}
.pcard .phead.gone{background:var(--panel-2);border-left-color:var(--idle)}
.pcard .phead h3{margin:0;font-size:19px;letter-spacing:-.01em}
.pcard .phead .sub{color:var(--ink-3);font-size:12px;margin:3px 0 0;display:flex;gap:6px;align-items:center;flex-wrap:wrap}
.pcard section{margin:0 0 18px}
.pcard h4{margin:0 0 8px;font-size:10.5px;letter-spacing:.07em;text-transform:uppercase;color:var(--ink-3);
  display:flex;align-items:center;gap:8px}
.pcard h4::after{content:"";flex:1;height:1px;background:var(--rule-2)}
.ptab{width:100%;border-collapse:collapse;font-size:12.5px}
.ptab th{text-align:left;font-size:10px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-3);
  font-weight:600;padding:0 8px 5px 0;border-bottom:1px solid var(--rule-2)}
.ptab td{padding:7px 8px 7px 0;border-bottom:1px solid var(--rule-2);vertical-align:middle}
.ptab td.num{font-variant-numeric:tabular-nums;letter-spacing:.02em}
.ptab tr.dead td{color:var(--ink-3)}
.ptab tr.dead td.num{text-decoration:line-through;text-decoration-thickness:1px}
/* A credential's type, coloured by what it is: a card and a PIN are not the
   same risk and should not look the same in a list of forty. */
.ctype{display:inline-block;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;
  padding:2px 7px;border-radius:4px;background:var(--panel-2);color:var(--ink-2);border:1px solid var(--rule-2)}
.ctype.t-card{color:var(--cmd);border-color:color-mix(in srgb,var(--cmd) 40%,transparent)}
.ctype.t-pin{color:var(--warn);border-color:color-mix(in srgb,var(--warn) 40%,transparent)}
.ctype.t-mobile{color:var(--ok);border-color:color-mix(in srgb,var(--ok) 40%,transparent)}
.ctype.t-biometric{color:var(--unknown);border-color:color-mix(in srgb,var(--unknown) 40%,transparent)}
/* What a vendor system gives them: chips, not a table. A membership is a
   label somebody chose, and it reads like one. */
.gsys{display:flex;align-items:baseline;gap:8px;margin:0 0 6px}
.gsys .nm{font-size:12px;font-weight:600;color:var(--link)}
.gsys .at{font-size:10.5px;color:var(--ink-3)}
.gchips{display:flex;gap:6px;flex-wrap:wrap;margin:0 0 12px}
.gchip{font-size:12px;padding:4px 11px;border-radius:13px;background:color-mix(in srgb,var(--link) 11%,transparent);
  color:var(--ink);border:1px solid color-mix(in srgb,var(--link) 32%,transparent)}
/* A command that sets something off, or takes protection away. "May reset
   the panel" and "may sound the sirens" must not be read at the same speed. */
.gchip.profile{font-weight:600;display:inline-flex;gap:6px;align-items:center;padding-right:5px}
.gchip.profile .btn.tiny{font-weight:400;padding:1px 7px}
.gchip.profile.warn{border-color:color-mix(in srgb,var(--warn,orange) 55%,transparent)}
.gchip.danger{background:color-mix(in srgb,var(--fire) 12%,transparent);
  border-color:color-mix(in srgb,var(--fire) 45%,transparent);color:var(--fire);font-weight:600}
.pcount .c.k-aktiva{border-left-color:var(--ok)}
.pcount .c.k-tillfalliga{border-left-color:var(--cmd)}
.pcount .c.k-utgangna{border-left-color:var(--idle)}
.pcount .c.k-inne{border-left-color:var(--accent)}
#accBody .ptab input[type=datetime-local],#accBody .ptab input[type=text],#accBody .ptab input:not([type]),
#accBody .ptab select{font:inherit;font-size:12.5px;padding:5px 8px;border:1px solid var(--rule);
  border-radius:5px;background:var(--panel-2);color:var(--ink)}
/* The gap between "we recorded it" and "the door knows". Loud, because that
   gap is where somebody who left on Friday still gets in on Monday. */
.pwork{border:1px solid var(--warn);border-left-width:4px;border-radius:8px;padding:11px 14px;margin:0 0 14px;
  background:color-mix(in srgb,var(--warn) 11%,var(--panel));font-size:12.5px;line-height:1.5}
.pwork b{display:block;margin-bottom:3px;color:var(--warn)}
.pwork ul{margin:6px 0 0;padding-left:18px}
.pwork li{margin:2px 0;font-variant-numeric:tabular-nums}
.pnote{border:1px solid var(--rule-2);border-left:4px solid var(--link);border-radius:8px;padding:11px 14px;
  margin:0 0 14px;background:color-mix(in srgb,var(--link) 7%,var(--panel));font-size:12.5px;line-height:1.55}
.pnote b{color:var(--link)}
.pempty{color:var(--ink-3);font-size:12.5px;padding:20px 0;text-align:center;
  border:1px dashed var(--rule-2);border-radius:8px;background:var(--panel-2)}
/* The form that makes a NEW person is not another row in the list of people
   who exist — Tomas read it as one and could not tell where he ended. */
.newuser{border:1px dashed var(--rule);border-radius:6px;padding:11px 12px;background:var(--panel-2)}
.newuser h4{margin:0 0 8px}
.invite{margin-top:12px;border:1px solid var(--ok);border-left-width:3px;border-radius:5px;
  padding:10px 12px;background:color-mix(in srgb,var(--ok) 10%,transparent)}
.invite b{font-size:13px}
.invite dl.kv{margin:8px 0}
.invite .t2{font-size:11px;color:var(--ink-3);line-height:1.5;margin-top:6px}
.tag.ok{background:rgba(44,115,80,.16);color:var(--ok)}
.userchip{display:inline-flex;align-items:center;gap:6px;font-size:12px;padding:3px 9px;border-radius:12px;background:var(--panel-2);border:1px solid var(--rule);cursor:pointer;max-width:220px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.userchip .r{color:var(--ink-3);font-size:11px}
#userMenu{position:fixed;right:12px;top:44px;z-index:300;background:var(--panel);border:1px solid var(--rule);border-radius:3px;box-shadow:0 8px 24px rgba(0,0,0,.2);min-width:220px;padding:6px 0;font-size:12.5px}
#userMenu .it{padding:6px 14px;cursor:pointer}#userMenu .it:hover{background:var(--panel-2)}
#userMenu .hd{padding:6px 14px 8px;color:var(--ink-3);font-size:11px;border-bottom:1px solid var(--rule);margin-bottom:4px;line-height:1.5}
.urow{border-bottom:1px solid var(--rule);padding:7px 0 8px;font-size:12px}
.urow.off{opacity:.55}
.urow .l1{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
.urow .l1 .nm{font-weight:600;flex:1}
.urow .l1 .btn{padding:1px 7px;font-size:11px}
.urow .l2{display:flex;gap:6px;margin-top:5px}
.urow .l2 label{flex:1;font-size:10.5px;color:var(--ink-3)}
.urow .l2 select,.urow .l2 input{display:block;width:100%;box-sizing:border-box;font-size:12px;margin-top:2px}
table.imp{border-collapse:collapse;font-size:12px;margin-top:8px;width:100%}
table.imp th{text-align:left;font-weight:500;color:var(--ink-3);font-size:10.5px;letter-spacing:.06em;text-transform:uppercase;padding:3px 5px;border-bottom:1px solid var(--rule)}
table.imp td{padding:3px 5px;border-bottom:1px solid var(--rule-2);vertical-align:middle}
table.imp tr.off td{opacity:.5}
table.imp input:not([type=checkbox]),table.imp select{font:inherit;font-size:12px;padding:3px 5px;border:1px solid var(--rule);border-radius:3px;background:var(--panel);color:var(--ink);width:100%;box-sizing:border-box}
.tabs{display:flex;gap:2px;border-bottom:1px solid var(--rule);margin:0 0 12px;align-items:center}
.tabs .tab{border:none;background:none;font:inherit;font-size:12.5px;padding:6px 12px;cursor:pointer;color:var(--ink-3);border-bottom:2px solid transparent;margin-bottom:-1px}
.tabs .tab.on{color:var(--ink);border-bottom-color:var(--accent);font-weight:600}
.cal2col{display:grid;grid-template-columns:1fr;gap:16px}
/* A grid item's default min-width is its content, so one long line inside the
   form pushed the whole column past the panel's right edge — which is most of
   what "villkor behöver snygga till" was looking at. */
.cal2col>.col{min-width:0}
body.panelonly .cal2col{grid-template-columns:minmax(360px,1fr) minmax(420px,1.2fr)}
.calform h3{font-size:15px;margin:0 0 10px}
.calform .sec{padding:10px 0 4px;border-top:1px solid var(--rule-2)}
.calform .sec:first-of-type{border-top:none}
.calform .st{font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--ink-3);margin:0 0 8px;font-weight:600}
.calform .hnt{font-size:11.5px;color:var(--ink-3);line-height:1.5;margin:2px 0 8px}
.calform .fld{display:block}
.calform .fld .fldin,.calform .fld input,.calform .fld select{display:block;width:100%;box-sizing:border-box;margin-top:3px;font:inherit;font-size:13px;padding:5px 8px;border:1px solid var(--rule);border-radius:3px;background:var(--panel);color:var(--ink)}
.calform .row2{display:flex;gap:10px}.calform .row2 .fld{flex:1}
.calform .fldin.big{font-size:14px;padding:7px 9px}
/**
 * THE CONDITION FORM.
 *
 * Tomas: *"även villkor behöver snygga till"* — and the screenshot showed
 * why: rows ran off the right edge of the panel, every field label shouted in
 * uppercase because the panel's own `.fld` rule reached in over the form's,
 * and twenty state tick boxes sat in a grey wall three rows deep. Nothing
 * about the form's logic was wrong; it was three CSS accidents.
 *
 * So: the form can never be wider than what holds it, labels are sentences,
 * and the states are chips you can see the state of from across the desk.
 */
.calform{max-width:100%;overflow-wrap:anywhere}
.calform .fld{text-transform:none;letter-spacing:0;font-size:11.5px;font-weight:500;color:var(--ink-2)}
.calform .row2{flex-wrap:wrap}
.calform .row2 .fld{min-width:150px}
/* Who the condition watches: the list gets its own line, the two ways of
   choosing sit under it, so a long "3 valda punkter — …" cannot push the
   selector off the edge. */
.calform .trig{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin:2px 0 6px;
  border:1px solid var(--rule-2);border-radius:5px;padding:8px 10px;background:var(--panel)}
.calform .trig .who{flex:1 1 100%;font-size:12.5px;line-height:1.45}
.calform .trig .or{font-size:11.5px;color:var(--ink-3)}
.calform .trig select{font:inherit;font-size:12px;padding:3px 6px;border-radius:4px;
  border:1px solid var(--rule);background:var(--panel-2);color:var(--ink);max-width:100%}
.calform .hnt.st2{margin-top:10px;color:var(--ink-2)}
.calform .days{display:flex;flex-wrap:nowrap;gap:8px;font-size:12px;margin:4px 0 6px;align-items:flex-start}
.calform .days .dd{display:flex;flex-wrap:wrap;gap:5px;flex:1;min-width:0}
.calform .days .gl{font-size:10.5px;color:var(--ink-3);text-transform:uppercase;letter-spacing:.06em;
  min-width:38px;flex:none;padding-top:5px}
/* A state you have picked should be legible as picked without hunting for a
   tick — the same reason the operator page colours a symbol. */
.calform .days label{display:inline-flex;align-items:center;gap:6px;cursor:pointer;
  border:1px solid var(--rule);border-radius:99px;padding:3px 11px 3px 8px;
  background:var(--panel);color:var(--ink-2);white-space:nowrap}
.calform .days label:hover{border-color:var(--accent)}
.calform .days label:has(input:checked){border-color:var(--accent);color:var(--ink);
  background:color-mix(in srgb,var(--accent) 17%,transparent)}
.calform .days input{accent-color:var(--accent);margin:0;flex:none}
/* The caution above the form: one line folded, everything still there. */
.calform details.warn{margin:0 0 12px;border:1px solid var(--warn);border-left-width:3px;
  border-radius:4px;background:rgba(154,106,18,.10);padding:7px 10px}
.calform details.warn>summary{list-style:none;cursor:pointer;font-size:12px;font-weight:600;
  color:var(--warn);display:flex;align-items:center;gap:6px;user-select:none}
.calform details.warn>summary::-webkit-details-marker{display:none}
.calform details.warn>summary::before{content:"›";font-size:14px;line-height:1;transition:transform .12s}
.calform details.warn[open]>summary::before{transform:rotate(90deg)}
.calform details.warn>div{font-size:11.5px;color:var(--ink-2);line-height:1.5;margin-top:6px}
/* `.tog` is nowrap for the header, where it must never wrap; in the form the
   labels are sentences and have to. */
.calform .tog{display:flex;align-items:flex-start;gap:8px;white-space:normal;line-height:1.45;
  font-size:12.5px;margin:0 0 6px;color:var(--ink-2)}
.calform .tog input{margin:2px 0 0;flex:none}
.calform .fld{margin-bottom:10px}
.pvs{margin:6px 0 2px}
.pv{font-size:12.5px;padding:3px 0;line-height:1.5}
.pv .n{font-weight:600}
.pv .act{padding:1px 6px;border-radius:3px;background:var(--panel-2);border:1px solid var(--rule);font-size:12px}
.pv .arr{color:var(--ink-3)}
.pv.arow{display:flex;gap:6px;align-items:center;flex-wrap:wrap;border:1px solid var(--rule-2);border-radius:3px;padding:5px 7px;margin-bottom:4px}
.ni{color:var(--ink-3)}
.sched{border:1px solid var(--rule);border-radius:4px;padding:9px 11px;margin-bottom:8px;background:var(--panel)}
.sched.off{border-color:var(--idle)}
/* The schedule you arrived at by clicking a clock on a symbol, so the eye lands
   on it. Scoped to the panel: see the note at .mark .schmark for why. */
#calBody .sched.lit{outline:2px solid var(--accent);outline-offset:2px}
.sched .l1{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.sched .l1 b{font-size:13.5px}
.sched .l1 .btn{padding:1px 7px;font-size:11px}
.sched .l2{font-size:12px;color:var(--ink-2);margin-top:3px;line-height:1.5}
.sched .l3{font-size:11.5px;color:var(--ink-3);margin-top:2px}
.sched .l3 .bad{color:var(--fire)}
.sched .ph{display:inline-block;padding:1px 7px;border-radius:9px;font-size:10.5px;letter-spacing:.04em;text-transform:uppercase;background:var(--panel-2);color:var(--ink-3)}
.sched .ph.off{background:rgba(120,120,120,.22);color:var(--ink)}
.sched .ph.on{background:rgba(44,115,80,.16);color:var(--ok)}
.calform{border:1px solid var(--rule);border-radius:4px;padding:10px 12px;margin:6px 0 12px;background:var(--panel-2)}
.calform .grp{margin:8px 0}
.calform .prev{font-size:12px;margin:6px 0;line-height:1.5}
.calform .prev .ni{color:var(--ink-3)}
.hol{display:flex;align-items:center;gap:8px;font-size:12px;padding:2px 0}
.hol .d{font-family:"IBM Plex Mono",monospace;color:var(--ink-3)}
#helpBody .hq{width:100%;box-sizing:border-box;font:inherit;font-size:13px;padding:7px 10px;border:1px solid var(--rule);border-radius:3px;background:var(--panel-2);color:var(--ink);margin-bottom:10px}
#helpBody .topic{padding:8px 0;border-bottom:1px solid var(--rule-2);cursor:pointer}
#helpBody .topic:hover{background:var(--panel-2)}
#helpBody .topic b{font-weight:600}
#helpBody .topic .ct{display:block;font-size:11px;color:var(--ink-3);margin-top:2px}
#helpBody article{font-size:13px;line-height:1.55}
#helpBody article p{margin:0 0 10px}
#helpBody article ul{margin:0 0 10px 18px;padding:0}
#helpBody article li{margin:0 0 5px}
#helpBody article h3{font-size:15px;margin:0 0 10px}
#helpBody .back{margin-bottom:10px}
#helpBody mark{background:rgba(255,200,0,.35);color:inherit}
#cogBody .row2{display:flex;gap:8px}
#cogBody .row2 .fld{flex:1}
#cogBody dl.kv{margin-bottom:12px}
/* The settings panel is a page of forms, not a sidebar of numbers — it gets
   the width to put two fields beside each other without either being 90 px. */
#cogPanel{width:400px}
#cogBody .cogtabs{position:sticky;top:-12px;z-index:2;background:var(--panel);display:flex;
  flex-wrap:wrap;gap:3px;padding:12px 0 8px;margin:-12px 0 12px;border-bottom:1px solid var(--rule-2)}
#cogBody .cogtabs button{font:inherit;font-size:12px;padding:4px 11px;border-radius:99px;cursor:pointer;
  border:1px solid transparent;background:none;color:var(--ink-3);white-space:nowrap}
#cogBody .cogtabs button:hover{background:var(--panel-2);color:var(--ink)}
#cogBody .cogtabs button.on{background:var(--accent);border-color:var(--accent);color:#fff}
#cogBody .cogsec>.sec-h:first-child{margin-top:0}
/* Spara belongs to the whole page, so it stays put while the tabs change. */
#cogBody .cogfoot{position:sticky;bottom:-12px;background:var(--panel);margin:18px 0 -12px;
  padding:11px 0 12px;border-top:1px solid var(--rule-2)}
#cogBody .cogfoot .acts{margin-top:0!important}
/**
 * The fields themselves. `.fld` only ever made <select> a block, so every
 * text input in this panel sat on the same line as its own label and took
 * whatever width was left — which is most of what read as clutter: nothing
 * lined up with anything. Here every control is a block under its label, one
 * width, one border radius.
 */
#cogBody .fld{margin:0 0 11px}
/* A hint tucks up under the field it explains — the negative margin eats the
   field's bottom margin. But it must not tuck up under a NOTE: .note is
   positioned and paints on top, so the hint's first line vanished under the
   box (Tomas, 18 Sep: "texten ligger omlott"). After a note it stands off. */
#cogBody .cogtip{font-size:11.5px;color:var(--ink-3);margin:-6px 0 12px}
#cogBody .note+.cogtip{margin-top:9px}
/* Belt and braces for the hints still styled inline: nothing tucks up under a note. */
#cogBody .note+.t{margin-top:9px!important}
#cogBody .fld input,#cogBody .fld select,#cogBody .fld textarea{display:block;width:100%;
  box-sizing:border-box;margin-top:4px;font:inherit;font-size:12.5px;font-weight:400;
  text-transform:none;letter-spacing:0;color:var(--ink);background:var(--panel);
  border:1px solid var(--rule);border-radius:4px;padding:5px 8px}
#cogBody .fld input:focus,#cogBody .fld select:focus,#cogBody .fld textarea:focus{
  outline:none;border-color:var(--accent)}
#cogBody .chk{display:flex;gap:8px;align-items:flex-start;font-size:12.5px;color:var(--ink-2);
  margin:0 0 11px;line-height:1.45;cursor:pointer}
#cogBody .chk input{margin:2px 0 0;accent-color:var(--accent);flex:none}
#cogBody .sec-h{margin-top:20px}
#cogBody .t{font-size:11.5px;color:var(--ink-3);line-height:1.5}
/**
 * The long explanations, folded.
 *
 * Every one of them is worth reading once and never again, and together they
 * were most of the panel's height. They stay in full, one click away, with a
 * summary that says what they answer — so the page is fields, and the prose
 * is there for the afternoon somebody needs it.
 */
#cogBody details.more{margin:0 0 12px}
#cogBody details.more>summary{list-style:none;cursor:pointer;font-size:11.5px;color:var(--accent);
  display:inline-flex;align-items:center;gap:5px;padding:1px 0;user-select:none}
#cogBody details.more>summary::-webkit-details-marker{display:none}
#cogBody details.more>summary::before{content:"›";font-size:13px;line-height:1;transition:transform .12s}
#cogBody details.more[open]>summary::before{transform:rotate(90deg)}
#cogBody details.more>summary:hover{text-decoration:underline}
#cogBody details.more>.t{margin:5px 0 0!important}
/* The firewall list: something to read off to IT, so it lines up in columns. */
#cogBody table.ports{border-collapse:collapse;width:100%;font-size:12px}
#cogBody table.ports td{padding:3px 0;vertical-align:top;border-bottom:1px solid var(--rule-2)}
#cogBody table.ports td.w{color:var(--ink-2);white-space:nowrap;padding-right:10px}
#cogBody table.ports td.h{color:var(--ink);word-break:break-all}
#cogBody table.ports td.p{color:var(--ink-2);text-align:right;white-space:nowrap;padding-left:10px}
#cogBody table.ports tr.n td{border:none;padding:0 0 4px;font-size:11px;color:var(--ink-3)}
#cogBody table.ports tr.n.bad td{color:var(--warn)}
.machine{display:flex;align-items:center;gap:8px;font-size:12.5px;padding:6px 10px;margin:4px 0 8px;
  border:1px solid var(--rule);border-radius:8px;background:var(--panel-2,transparent);flex-wrap:wrap}
.machine.none{border-style:dashed}
.machine.down .nm{color:var(--ink-2)}
.machine .nm{font-weight:600}
.machine .acts{margin-left:auto;display:flex;gap:6px}
.machine .mnote{font-size:11.5px;color:var(--ink-3);flex-basis:100%}
.machine .mnote:empty{display:none}
.machine .mtail{flex-basis:100%;margin:2px 0 0;padding:6px 8px;max-height:220px;overflow:auto;border-radius:6px;background:var(--panel,rgba(128,128,128,.08));
  font:11px/1.4 ui-monospace,Menlo,Consolas,monospace;white-space:pre-wrap;word-break:break-word;color:var(--ink-2)}
.machine .mtail .ts{color:var(--ink-3)} .machine .mtail .el{color:var(--warn)}
.machine .btn.tiny.on{background:var(--accent,#3b82f6);color:#fff;border-color:transparent}
.machine-h{font-size:12.5px;font-weight:600;margin:8px 0 2px}
.machine-h .ct{font-weight:400}
#cogBody .drv{display:flex;align-items:center;gap:7px;font-size:12.5px;padding:3px 0;
  border-bottom:1px solid var(--rule-2)}
#cogBody .drv:last-child{border-bottom:none}
#cogBody .drv.gone .nm{color:var(--fire)}
#cogBody .drv .dot.fire{background:var(--fire)}
#cogBody .drv .nm{flex:1;font-family:"IBM Plex Mono",monospace}
#cogBody .path{font-family:"IBM Plex Mono",monospace;font-size:11px;color:var(--ink-3);
  word-break:break-all;margin:-4px 0 12px}

/* the authority picker */
#who{font:inherit;font-size:11.5px;padding:3px 6px;border-radius:2px;cursor:pointer;
  border:1px solid var(--rule);background:var(--panel-2);color:var(--ink-2);max-width:190px}
#who:hover{border-color:var(--accent);color:var(--accent)}
.grants{display:flex;flex-wrap:wrap;gap:4px;margin:0 0 10px}
.grant{font-size:10px;letter-spacing:.04em;text-transform:uppercase;font-weight:600;
  padding:2px 6px;border-radius:2px;background:var(--panel-2);color:var(--ink-3);
  display:flex;align-items:center;gap:4px}
.grant i{width:7px;height:7px;border-radius:2px;display:inline-block}
.grant.r-viewer{opacity:.62}
.grant.r-engineer,.grant.r-admin{background:rgba(44,115,80,.15);color:var(--ok)}
.grant.r-operator{background:rgba(12,107,117,.14);color:var(--accent)}

/* subsystem filter chips */
#subs .chips{display:flex;flex-wrap:wrap;gap:5px;padding:0 12px 10px}
.chip{font-size:11px;padding:3px 8px;border-radius:11px;cursor:pointer;user-select:none;
  border:1px solid var(--rule);background:var(--panel-2);color:var(--ink-2);
  display:flex;align-items:center;gap:5px}
.chip .n{font:600 10px/1 "IBM Plex Mono",monospace;color:var(--ink-3)}
.chip.off{opacity:.42;text-decoration:line-through}
.chip.hot{border-color:var(--fire);color:var(--fire)}
.chip .sw{width:8px;height:8px;border-radius:2px}
.sw-fire{background:#D0021B} .sw-intrusion{background:#1F5FA8}
.sw-access{background:#1F7A46} .sw-cctv{background:#6B3FA0} .sw-meta{background:#E3B02F}
/* Nödbelysning: grön som utrymningsskylten, inte brandens röda. Disciplinen
   är egen — batteritest och funktionstest, inte larm — och ska synas som det. */
.sw-emergency{background:#00843D}
/* Teknik: grå-blå som en apparatskåpsdörr — varken larm eller liv, drift. */
.sw-technical{background:#4F6D8A}

/* the simulation drawer */
#simPanel{left:auto;right:12px;bottom:12px;width:300px}
#simBody h4{margin:12px 0 6px;font-size:10px;letter-spacing:.09em;text-transform:uppercase;
  color:var(--ink-3);font-weight:600}
#simBody h4:first-child{margin-top:0}
#simBody .acts{margin-bottom:4px}
#simBody .clock{font:600 15px/1.2 "IBM Plex Mono",monospace;color:var(--ink)}
#simBody .scen{font-size:12px;color:var(--ink-2);margin:2px 0 10px}
button.btn.sim{border-color:#C79A22;color:#8A6A10;background:rgba(227,176,47,.12)}
button.btn.sim:hover:not(:disabled){border-color:#8A6A10;color:#6A5008}

/* plan panel */
.plan-panel{position:absolute;left:280px;bottom:12px;width:330px;max-height:calc(100% - 140px);
  background:var(--panel);border:1px solid var(--rule);border-radius:4px;z-index:22;
  display:flex;flex-direction:column;overflow:hidden;box-shadow:0 8px 30px rgba(0,0,0,.16)}
/* Wider, not taller: the extra height ran the header up under the alarm bar,
   which put the close button behind it. */
.plan-panel.wide{width:520px}
/* On its own screen: no header, no rail, no drawing — just the list, filling
   the window. Everything else is still in the DOM and still live; it is simply
   not this window's job. */
body.panelonly #rail,body.panelonly header,body.panelonly #stage,body.panelonly .zoomer,
body.panelonly #alarms,body.panelonly #demoBar,body.panelonly .pal,body.panelonly .side,
body.panelonly #firstRun,body.panelonly #mapAttr{display:none!important}
body.panelonly .plan-panel.solo{position:fixed;inset:0;width:auto;max-height:none;
  border-radius:0;border:0;box-shadow:none}
/* OPLACERADE PUNKTER SOM EGET FÖNSTER. Tomas: "vill kunna snappa loss den."
   Listan fyller fönstret; dragningen till ritningen går över fönstergränsen
   (dataTransfer följer med i Chrome), och listan lever på sin egen socket. */
body.panelonly .pal.solo{display:flex!important;position:fixed;inset:0;width:auto;height:auto;
  max-height:none;border-radius:0;border:0;box-shadow:none;transform:none!important}
body.panelonly .pal.solo #palDock,body.panelonly .pal.solo #palShrink,body.panelonly .pal.solo #palPop{display:none}
body.panelonly .plan-panel.solo .body{max-height:none;height:calc(100% - 42px)}
body.panelonly .plan-panel.solo h2 .btn{display:none}
/* #simPanel pins itself to the bottom right corner by id, which outranks the
   .solo rule above — so say it again, louder, for the popped-out window. */
body.panelonly #simPanel.solo{left:0;right:0;top:0;bottom:0;width:auto}
/* Same reason: an id rule for the panel's own position outranks `.solo`. */
body.panelonly #cogPanel.solo,body.panelonly #iamPanel.solo,
body.panelonly #perfPanel.solo,body.panelonly #accPanel.solo{left:0;right:0;top:0;bottom:0;width:auto}
@media (max-width:1100px){.plan-panel.wide{width:calc(100% - 24px)}}
.btn.tiny{font-size:10.5px;padding:1px 7px;margin-left:auto}
.pinrow{font-size:11.5px;color:var(--ink-3);line-height:1.5;margin:2px 0 8px;
  border:1px dashed var(--rule);border-radius:4px;padding:7px 9px;background:var(--panel-2)}
.pinrow.set{border-style:solid;border-color:var(--accent);color:var(--ink-2)}
.nsadv summary{font-size:11.5px;color:var(--ink-3);cursor:pointer;margin-bottom:4px}
body.picking #stage{cursor:crosshair}

.diag .dg{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:12px 18px;
  margin-bottom:12px}
.diag .f-l{font-size:10.5px;letter-spacing:.07em;text-transform:uppercase;color:var(--ink-3)}
.diag .f-v{font-size:16px;font-weight:500;margin-top:1px}
.diag .f-v.warn{color:var(--warn)}
.evtab{width:100%;border-collapse:collapse;font-size:12px;margin-top:6px}
.evtab th{text-align:left;font-size:10.5px;letter-spacing:.07em;text-transform:uppercase;
  color:var(--ink-3);font-weight:500;padding:0 10px 5px 0;border-bottom:1px solid var(--rule-2)}
.evtab td{padding:4px 10px 4px 0;border-bottom:1px solid var(--rule-2);vertical-align:top}
.evtab .pl{color:var(--ink-3)}
.evtab tr.unmapped td{background:color-mix(in srgb,var(--warn) 8%,transparent)}

/* ---------- trends ---------- */
/**
 * The trends board is a DOCUMENT, not a place on a map.
 *
 * Everything else in #world is pan-and-zoom furniture — a plan, a set of
 * cards, all of it laid out in a space you move around in. A report is read
 * top to bottom, so on this level the stage stops being a stage: it scrolls,
 * fills the window, and the zoom controls get out of the way rather than
 * hovering over a chart.
 */
body.trends #stage{overflow:auto;touch-action:pan-y}
/**
 * TAVLORNA PÅ EN TELEFON: RULLADE, INTE NEDSKALADE. Se boardScroll() i
 * kameran. Utan det här skalades hela byggnadssidan ner för att rymmas och
 * plankorten blev 98 px breda med namnen avklippta.
 */
body.bladvy #stage{overflow:auto;touch-action:pan-y;-webkit-overflow-scrolling:touch}
body.bladvy #world{position:static;transform:none!important;width:100%;box-sizing:border-box;padding:10px 8px 28px}
body.bladvy .board,body.bladvy .board.wide{width:auto;max-width:100%;box-sizing:border-box;padding:16px 14px}
body.bladvy .cards{grid-template-columns:1fr}
body.bladvy .pgrid{grid-template-columns:repeat(auto-fill,minmax(148px,1fr))}
body.bladvy .bphoto{width:100%;height:150px}
body.bladvy .bmeta{min-width:0}
/** Zoomknapparna styr ingenting när sidan rullas — då ska de inte stå där och se klickbara ut. */
body.bladvy .zoomer{display:none}
body.trends #world{position:static;transform:none!important;padding:18px 0 40px;
  display:flex;justify-content:center}
body.trends .zoomer,body.trends #hint{display:none}
body.trends .board{width:min(1180px,calc(100vw - 60px))}
.tr-head{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin:0 0 16px}
.tr-head h3{margin:0 12px 0 0}
.tr-head select,.tr-head button{font:inherit;font-size:12px;padding:4px 8px;
  border:1px solid var(--rule);border-radius:3px;background:var(--panel);color:var(--ink);cursor:pointer}
.kpis{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px;margin-bottom:18px}
.kpi{background:var(--panel);border:1px solid var(--rule-2);border-radius:5px;padding:10px 12px}
.kpi .k-l{font-size:11px;letter-spacing:.07em;text-transform:uppercase;color:var(--ink-3)}
.kpi .k-v{font:600 26px/1.15 "IBM Plex Sans",sans-serif;margin:4px 0 2px;letter-spacing:-.02em}
.kpi .k-d{font-size:11.5px;color:var(--ink-3)}
.kpi .k-d.up{color:var(--fire)} .kpi .k-d.down{color:var(--ok)}
.kpi svg{display:block;margin-top:6px;width:100%;height:22px;overflow:visible}
/**
 * TRENDERNA RITAS IN NÄR PANELEN ÖPPNAS — en gång, aldrig vid liveuppdatering.
 *
 * Riktningen på en kurva är det första ögat vill veta, och den syns snabbare
 * om linjen dras än om den står färdig. Men rörelse vid varje omritning vore
 * rörelse utan innehåll: klassen `rita` sätts när panelen öppnas och tas bort
 * efter drygt en sekund (se drawTrends). Bara transform och stroke-dashoffset
 * — inget som tvingar fram en ny layout.
 */
.board.rita .kpi .spark{stroke-dasharray:200;stroke-dashoffset:200;animation:sparkin .7s ease-out .1s forwards}
@keyframes sparkin{to{stroke-dashoffset:0}}
.board.rita svg.chart .bar{transform-origin:50% 100%;transform-box:fill-box;
  animation:barin .42s cubic-bezier(.2,.8,.3,1) both;animation-delay:calc(var(--i) * 9ms)}
@keyframes barin{from{transform:scaleY(0);opacity:.4}to{transform:scaleY(1);opacity:1}}
.board.rita .hm i{animation:hmin .5s ease-out both}
@keyframes hmin{from{opacity:0}}
@media (prefers-reduced-motion:reduce){
  .board.rita .kpi .spark,.board.rita svg.chart .bar,.board.rita .hm i{animation:none;stroke-dashoffset:0}}
/**
 * KORSHÅRET. Den inbyggda `title` dröjer en sekund och försvinner om musen
 * rör sig — värdelös när man följer en kurva med blicken. Se wireCross().
 */
svg.chart{touch-action:none}
svg.chart .cx{stroke:var(--ink-3);stroke-width:1;stroke-dasharray:3 3;vector-effect:non-scaling-stroke}
svg.chart .cd{fill:var(--ink);stroke:var(--panel);stroke-width:1.5;vector-effect:non-scaling-stroke}
.crossbox{position:absolute;z-index:5;pointer-events:none;white-space:nowrap;
  background:var(--panel);border:1px solid var(--rule);border-radius:4px;padding:3px 8px;
  font-size:11.5px;color:var(--ink);box-shadow:0 4px 14px rgba(0,0,0,.18)}
.crossbox b{margin-right:5px}
.cards2{display:grid;grid-template-columns:repeat(auto-fit,minmax(330px,1fr));gap:14px}
.chart{background:var(--panel);border:1px solid var(--rule-2);border-radius:5px;padding:12px 14px 10px}
.chart h4{margin:0 0 2px;font-size:13px;font-weight:600}
.chart .sub{font-size:11.5px;color:var(--ink-3);margin-bottom:10px;line-height:1.45}
.chart svg{display:block;width:100%;height:auto}
.chart .ax{fill:var(--ink-3);font-size:10px;font-family:"IBM Plex Mono",monospace}
.chart .val{fill:var(--ink-2);font-size:9.5px;font-family:"IBM Plex Mono",monospace}
.chart svg{max-width:100%}
.chart .gl{stroke:var(--rule-2);stroke-width:1}
.chart .bl{stroke:var(--rule);stroke-width:1}
.hbars{display:grid;grid-template-columns:1fr auto;gap:3px 10px;align-items:center}
.hbars .nm{font-size:12px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:pointer}
.hbars .nm:hover{text-decoration:underline}
.hbars .tr-b{grid-column:1/-1;height:7px;border-radius:0 3px 3px 0;background:var(--accent);min-width:2px}
.hbars .ct{font-size:11.5px;color:var(--ink-3);font-family:"IBM Plex Mono",monospace}
.tr-note{font-size:11.5px;color:var(--ink-3);line-height:1.5;margin-top:10px}
.inctab{width:100%;border-collapse:collapse;font-size:12.5px}
.inctab th{text-align:left;font-size:10.5px;letter-spacing:.07em;text-transform:uppercase;
  color:var(--ink-3);font-weight:500;padding:0 10px 5px 0;border-bottom:1px solid var(--rule-2)}
.inctab td{padding:6px 10px 6px 0;border-bottom:1px solid var(--rule-2);vertical-align:top}
.inctab .pl{font-size:11px;color:var(--ink-3)}
.inctab .btn{font-size:11px;padding:2px 9px}
/*
 * TRÄFFYTAN ÄR HELA CELLEN, inte de tretton pixlar webbläsaren ritar.
 * Tomas: "man måste vara superexakt i vita checken."
 */
.inctab td.pick{padding:0}
.inctab td.pick label{display:flex;align-items:flex-start;justify-content:center;
  padding:7px 10px 7px 4px;cursor:pointer;min-height:28px}
.inctab td.pick input{width:15px;height:15px;cursor:pointer;margin:0}
.inctab .pl.warn{color:var(--warn)}
/* Inställningen sägs där listan syns — annars måste man öppna ⚙ för att svara
   på "varför ligger inte dörrlarmen här". */
.incpol{display:flex;flex-wrap:wrap;gap:4px 14px;align-items:baseline;font-size:11.5px;
  color:var(--ink-3);margin:2px 0 8px;padding:6px 9px;border-radius:3px;background:var(--panel-2)}
.incpol .p b{color:var(--ink-2);font-weight:600}
/* Inställningsraden i ⚙: alla tre lägen syns, det som gäller är markerat.
   Samma form som segRow i mer-menyn — ett val ska aldrig gömma sina alternativ. */
#cogWork .wrow{display:flex;align-items:center;gap:10px;padding:5px 0;
  border-bottom:1px solid var(--rule-2);flex-wrap:wrap}
#cogWork .wrow:last-child{border-bottom:none}
#cogWork .wrow .n{flex:1 0 110px;font-size:12.5px}
#cogWork .segs{display:flex;border:1px solid var(--rule);border-radius:3px;overflow:hidden}
#cogWork .seg{font:inherit;font-size:11.5px;padding:3px 9px;cursor:pointer;border:none;
  background:var(--panel);color:var(--ink-2);border-left:1px solid var(--rule)}
#cogWork .seg:first-child{border-left:none}
#cogWork .seg:hover{color:var(--accent)}
#cogWork .seg.on{background:var(--accent);color:#fff}

/* ------------------------------------------------- drivrutinslistan i ⚙
 *
 * En rad per drivrutin, med sitt läge. Allt som bara gäller EN drivrutin —
 * förmågor, diagnostik, anmärkningar — ligger bakom en utfällning, för det är
 * svar på frågor man ställer om en i taget. Se driverRows i 47-installningar.
 *
 * Den gamla `.drv` var en flexrad med blockinnehåll instoppat i sig: när
 * förmågorna visades bröts drivrutins-id:t över två rader och resten blev en
 * kolumn. Här är sammanfattningen en flexrad och utfällningen ett syskon.
 */
#cogBody .drvlist{border-top:1px solid var(--rule-2)}
#cogBody .drvrow{border-bottom:1px solid var(--rule-2)}
#cogBody .drvrow .sum{display:flex;align-items:baseline;gap:8px;padding:6px 2px;font-size:12.5px}
#cogBody .drvrow.has .sum{cursor:pointer}
#cogBody .drvrow.has .sum:hover{background:var(--panel-2)}
#cogBody .drvrow .nm{font-family:"IBM Plex Mono",monospace;white-space:nowrap}
#cogBody .drvrow .pk{font-size:11px;color:var(--ink-3);white-space:nowrap}
#cogBody .drvrow .ct{flex:1;min-width:0;font-size:11.5px;color:var(--ink-3)}
#cogBody .drvrow .chev{color:var(--ink-3);transition:transform .12s;line-height:1}
#cogBody .drvrow.open .chev{transform:rotate(90deg)}
#cogBody .drvrow.saknas .nm{color:var(--fire)}
#cogBody .drvrow .mer{padding:2px 2px 10px 18px}
#cogBody .drvrow .mer .cn{font-size:11.5px;color:var(--ink-3);line-height:1.5;margin:4px 0}
#cogBody .drvrow .mer .cn.bad{color:var(--fire)}
#cogBody .drvrow .mer .caps{margin:6px 0 2px}

/* Hopfällbara rubriker. Pilen säger att det går, och läget minns per webbläsare. */
#cogBody .sec-h.fold{cursor:pointer;user-select:none}
#cogBody .sec-h.fold::before{content:"▾";display:inline-block;width:12px;font-size:9px;
  opacity:.55;transition:transform .12s}
#cogBody .sec-h.fold.stangd::before{transform:rotate(-90deg)}
#cogBody .sec-h.fold:hover{color:var(--accent)}

/* "22 drivrutiner saknas" som EN rad. Faktumet lika högljutt, raderna borta
   tills någon vill se dem. Se driverRows. */
#cogBody .drvgone{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap;font-size:12.5px;
  padding:7px 9px;margin:0 0 6px;border-radius:3px;
  background:color-mix(in srgb,var(--fire) 9%,transparent);color:var(--fire)}
#cogBody .drvgone .ct{flex:1;min-width:140px;font-size:11.5px;color:var(--ink-3)}

/* --------------------------------------------- anläggningspaketets förhandsvisning
 * Den ska gå att läsa som en mening: så här mycket finns i paketet, så här
 * många av dess punkter finns här, och så här går det med det du redan har. */
.anlprev{border:1px solid var(--rule);border-radius:5px;padding:10px 12px;margin-top:6px;background:var(--panel)}
.anlprev .h{display:flex;flex-wrap:wrap;gap:8px;align-items:baseline;margin-bottom:8px}
.anlprev .h .ct{font-size:11px;color:var(--ink-3)}
.anlprev .grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(96px,1fr));gap:6px 14px;margin-bottom:10px}
.anlprev .f-l{font-size:10.5px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-3)}
.anlprev .f-v{font-size:14px;font-weight:500}
.anlprev .match{padding:7px 9px;border-radius:3px;font-size:12px;
  background:color-mix(in srgb,var(--ok) 11%,transparent);color:var(--ok)}
.anlprev .match.warn{background:color-mix(in srgb,var(--warn) 12%,transparent);color:var(--warn)}
.anlprev .match .pl{font-size:11px;color:var(--ink-3);margin-top:2px}
.anlprev .pl.warn{color:var(--warn);font-size:11.5px;margin-top:6px}
.anlprev .anllage{display:flex;flex-wrap:wrap;border:1px solid var(--rule);border-radius:3px;overflow:hidden;width:fit-content}
.anlprev .anllage .seg{font:inherit;font-size:11.5px;padding:4px 10px;cursor:pointer;border:none;
  background:var(--panel);color:var(--ink-2);border-left:1px solid var(--rule)}
.anlprev .anllage .seg:first-child{border-left:none}
.anlprev .anllage .seg.on{background:var(--accent);color:#fff}
/* Vad som faktiskt kommer att hända, i en mening. Röd när det inte går. */
.anlprev .utfall{margin-top:8px;font-size:12px;color:var(--ink-2);padding:6px 9px;
  border-radius:3px;background:var(--panel-2)}
.anlprev .utfall.bad{color:var(--fire);background:color-mix(in srgb,var(--fire) 10%,transparent)}
#cogBody .drvrow .sum .nm + .ct{margin-left:0}
.hm{display:grid;grid-template-columns:auto repeat(24,1fr);gap:2px;align-items:center}
.hm .hl{font-size:10px;color:var(--ink-3);padding-right:4px;text-align:right}
.hm i{display:block;aspect-ratio:1;border-radius:1.5px;background:var(--fire)}
.hm .hx{font-size:9px;color:var(--ink-3);text-align:center;font-family:"IBM Plex Mono",monospace}
.hmleg{display:flex;align-items:center;gap:4px;margin-top:8px;font-size:10.5px;color:var(--ink-3)}
.hmleg i{display:block;width:11px;height:11px;border-radius:1.5px;background:var(--fire)}
.hmleg .hmw{margin-left:4px}
@media print{
  header,#rail,.plan-panel,.side,.zoomer,.pal,#alarms,#demoBar,.tr-head button,.tr-head select{display:none!important}
  body,#world{overflow:visible!important;height:auto!important;background:#fff!important}
  .chart,.kpi{break-inside:avoid;border-color:#bbb}
}

/* ---------- the incident report ---------- */
body.report #stage{overflow:auto;inset:0;background:var(--ground);touch-action:auto;cursor:auto;user-select:text}
body.report #world{position:static;transform:none!important;padding:22px 0 60px;
  display:flex;justify-content:center}
body.report header,body.report #rail,body.report .zoomer,body.report #hint,
body.report .plan-panel,body.report .side{display:none!important}
.rep{width:min(900px,calc(100vw - 48px));background:var(--panel);border:1px solid var(--rule);
  border-radius:5px;padding:34px 40px 40px}
.rep h1{font-size:22px;margin:0 0 2px;font-weight:600;letter-spacing:-.01em}
.rep .rsub{color:var(--ink-3);font-size:12.5px;margin-bottom:20px}
.rep .stamp{display:inline-block;border:1.5px solid var(--warn);color:var(--warn);
  border-radius:3px;padding:3px 9px;font-size:11px;letter-spacing:.09em;text-transform:uppercase;
  font-weight:600;margin-bottom:14px}
.rep h2{font-size:12px;letter-spacing:.09em;text-transform:uppercase;color:var(--ink-3);
  margin:24px 0 8px;padding-bottom:5px;border-bottom:1px solid var(--rule-2)}
.rep .facts{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:12px 18px}
/* Bilden i incidentrapporten. Bryts aldrig över en sidbrytning vid utskrift. */
.rep .repimgs{display:flex;gap:14px;flex-wrap:wrap;margin:8px 0 4px}
.rep .repimgs figure{margin:0;max-width:min(520px,100%);break-inside:avoid;page-break-inside:avoid}
.rep .repimgs img{width:100%;height:auto;border:1px solid var(--rule);border-radius:2px;display:block}
.rep .repimgs figcaption{font-size:11.5px;color:var(--ink-3);margin-top:4px}
.rep .f-l{font-size:10.5px;letter-spacing:.07em;text-transform:uppercase;color:var(--ink-3)}
/* "Varade 43 h" om ett larm som fortfarande ljuder är en halvsanning i den rad
   läsaren litar mest på. Förbehållet står bredvid talet, inte i en fotnot. */
.rep .f-w{color:var(--warn);font-weight:400;font-size:11.5px}
.rep .rnote{color:var(--warn);font-size:11.5px;margin:-4px 0 8px}
.rep .f-v{font-size:15px;font-weight:500;margin-top:1px}
.rep table{width:100%;border-collapse:collapse;font-size:12.5px}
.rep th{text-align:left;font-size:10.5px;letter-spacing:.07em;text-transform:uppercase;
  color:var(--ink-3);font-weight:500;padding:0 8px 5px 0;border-bottom:1px solid var(--rule-2)}
.rep td{padding:5px 8px 5px 0;border-bottom:1px solid var(--rule-2);vertical-align:top}
.rep td.tt,.rep td.el{font-family:"IBM Plex Mono",monospace;font-size:11.5px;white-space:nowrap}
.rep td.el{color:var(--ink-3)}
.rep tr.k-fire td.ev{color:var(--fire);font-weight:500}
.rep .pl{font-size:11px;color:var(--ink-3)}
.kjform{display:flex;gap:12px;align-items:flex-end;flex-wrap:wrap;margin-top:8px}
.kjform label{display:flex;flex-direction:column;gap:3px;font-size:10.5px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-3)}
.kjform select,.kjform input{font:inherit;font-size:13px;padding:4px 7px;border:1px solid var(--rule);border-radius:3px;background:var(--panel);color:var(--ink)}
.rep tr.open td{background:color-mix(in srgb,var(--warn) 10%,transparent)}
.rep .none{font-size:12.5px;color:var(--ink-3);margin:4px 0 0}
.rep .sign{display:grid;grid-template-columns:1fr 1fr;gap:40px;margin-top:34px}
.rep .sign div{border-top:1px solid var(--ink);padding-top:4px;font-size:11px;color:var(--ink-3)}
.rep .lines{border:1px solid var(--rule-2);border-radius:4px;height:130px;
  background:repeating-linear-gradient(var(--panel),var(--panel) 27px,var(--rule-2) 27px,var(--rule-2) 28px)}
.rep .foot{margin-top:26px;padding-top:12px;border-top:1px solid var(--rule-2);
  font-size:11px;color:var(--ink-3);line-height:1.6}
.rep .acts{display:flex;gap:8px;margin-bottom:18px}
.rep .acts button{font:inherit;font-size:12px;padding:5px 12px;border:1px solid var(--rule);
  border-radius:3px;background:var(--panel-2);color:var(--ink);cursor:pointer}
@media print{
  body.report .rep{border:none;width:auto;padding:0}
  body.report #world{padding:0;display:block}
  .rep .acts{display:none}
  .rep{color:#000}
  .rep table{page-break-inside:auto}
  .rep tr{page-break-inside:avoid}
}

/* ---------- the journal ---------- */
.logbar{padding:8px 12px;border-bottom:1px solid var(--rule-2);display:flex;flex-wrap:wrap;
  gap:6px;align-items:center;background:var(--panel-2)}
.logbar input[type=search],.logbar select{font:inherit;font-size:12px;padding:3px 6px;
  border:1px solid var(--rule);border-radius:3px;background:var(--panel);color:var(--ink)}
.logbar input[type=search]{flex:1;min-width:110px}
.kchip{font-size:11px;padding:2px 8px;border-radius:11px;border:1px solid var(--rule);
  background:var(--panel);color:var(--ink-3);cursor:pointer;display:inline-flex;align-items:center;gap:5px;
  user-select:none;white-space:nowrap}
.kchip i{width:7px;height:7px;border-radius:50%;background:currentColor;flex:none}
/* The dot always carries the kind's colour, selected or not: the filter row
   is also the legend, and a legend in grey teaches nothing. */
.kchip.k-fire i{background:var(--fire)} .kchip.k-warn i{background:var(--warn)}
.kchip.k-idle i{background:var(--idle)} .kchip.k-acc i{background:var(--accent)}
.kchip.k-cmd i{background:var(--cmd)}   .kchip.k-link i{background:var(--link)}
.kchip.k-cfg i{background:var(--cfg)}   .kchip.k-sys i{background:var(--rule)}
.kchip.on{color:var(--ink);border-color:var(--ink-3);font-weight:500;background:var(--panel-2)}
.lrow{display:grid;grid-template-columns:62px 8px 1fr;gap:8px;padding:6px 12px;
  border-bottom:1px solid var(--rule-2);align-items:start;cursor:default}
.lrow.nav{cursor:pointer}
.lrow.nav:hover{background:var(--panel-2)}
.lrow .lt{font-family:"IBM Plex Mono",monospace;font-size:11px;color:var(--ink-3);
  padding-top:2px;white-space:nowrap}
.lrow .bar{width:3px;border-radius:2px;align-self:stretch;background:var(--idle)}
.lrow.k-fire .bar{background:var(--fire)} .lrow.k-warn .bar{background:var(--warn)}
.lrow.k-idle .bar{background:var(--idle)} .lrow.k-acc .bar{background:var(--accent)}
.lrow.k-cmd .bar{background:var(--cmd)}   .lrow.k-link .bar{background:var(--link)}
.lrow.k-cfg .bar{background:var(--cfg)}   .lrow.k-sys .bar{background:var(--rule)}
.lrow .lx{min-width:0}
/* What, then who: the headline is a row, and the person is pushed to its end. */
.lrow .lh{font-size:12.5px;line-height:1.35;display:flex;align-items:baseline;gap:7px;flex-wrap:wrap}
.lrow .lw{flex:1;min-width:0}
.lrow.k-fire .lw{color:var(--fire);font-weight:500}
/* The kind, in words as well as in colour. Nobody should have to learn that
   teal means Konfiguration to read their own journal. */
.lrow .ktag{flex:none;font-size:10px;letter-spacing:.04em;text-transform:uppercase;
  padding:1px 6px;border-radius:9px;background:var(--panel-2);color:var(--ink-3);
  border:1px solid var(--rule-2);white-space:nowrap;align-self:center}
.lrow.k-fire .ktag{background:color-mix(in srgb,var(--fire) 16%,transparent);color:var(--fire);border-color:transparent}
.lrow.k-warn .ktag{background:color-mix(in srgb,var(--warn) 16%,transparent);color:var(--warn);border-color:transparent}
.lrow.k-acc  .ktag{background:color-mix(in srgb,var(--accent) 14%,transparent);color:var(--accent);border-color:transparent}
.lrow.k-cmd  .ktag{background:color-mix(in srgb,var(--cmd) 14%,transparent);color:var(--cmd);border-color:transparent}
.lrow.k-link .ktag{background:color-mix(in srgb,var(--link) 14%,transparent);color:var(--link);border-color:transparent}
.lrow.k-cfg  .ktag{background:color-mix(in srgb,var(--cfg) 14%,transparent);color:var(--cfg);border-color:transparent}
/* Who did it. A row with none was the system, and silence says that. */
.lrow .lby{flex:none;display:inline-flex;align-items:center;gap:4px;font-size:11px;color:var(--ink-2);
  background:var(--panel-2);border:1px solid var(--rule-2);border-radius:9px;padding:1px 7px 1px 5px;white-space:nowrap}
.lrow .lby svg{width:11px;height:11px;fill:none;stroke:currentColor;stroke-width:1.6;opacity:.65;flex:none}
.lrow .lby i{font-style:normal;color:var(--ink-3);opacity:.8}
.lrow .lby i::before{content:"·";margin:0 3px}
.lrow .lms{font-family:"IBM Plex Mono",monospace;font-style:normal;opacity:.75}
/* What the chosen preset permits, shown while choosing rather than after. */
.nuwhat{margin-top:5px}
.nuwhat:empty{display:none}
.lrow .lo{font-weight:500;cursor:pointer;text-decoration:underline;text-decoration-style:dotted;
  text-underline-offset:2px;text-decoration-color:var(--rule)}
.lrow .lo:hover{text-decoration-color:currentColor}
.lrow .lp{font-size:11px;color:var(--ink-3);margin-top:1px}
.lrow .lp .sep{color:var(--rule);margin:0 4px}
.lrow .ld{font-size:11px;color:var(--ink-3);margin-top:1px;font-style:italic}
.logday{position:sticky;top:0;background:var(--panel);border-bottom:1px solid var(--rule);
  padding:5px 12px;font-size:11px;letter-spacing:.08em;text-transform:uppercase;
  color:var(--ink-3);z-index:2}
.lognote{padding:10px 12px;font-size:12px;color:var(--ink-3);line-height:1.5}

/* the lägesbild list */
.cats{display:grid;grid-template-columns:repeat(5,1fr);gap:6px;margin:0 0 10px}
.cat{display:flex;flex-direction:column;align-items:center;gap:1px;padding:7px 4px 6px;border-radius:5px;
  border:1px solid var(--rule);background:var(--panel-2);cursor:pointer;font:inherit;color:var(--ink-2);
  border-top:3px solid var(--ok)}
.cat b{font:600 18px/1.1 "IBM Plex Mono",monospace;color:var(--ink)}
.cat span{font-size:10px;letter-spacing:.06em;text-transform:uppercase}
.cat.fire{border-top-color:var(--fire)} .cat.fire b{color:var(--fire)}
.cat.warn{border-top-color:var(--warn)} .cat.warn b{color:var(--warn)}
.cat.idle{border-top-color:var(--idle)} .cat.acc{border-top-color:var(--accent)} .cat.acc b{color:var(--accent)}
.cat.unk{border-top-color:var(--unknown)}
.cat.zero{opacity:.45;cursor:default}
.cat.on{background:var(--panel);box-shadow:0 0 0 2px var(--accent) inset}
.cat:not(.zero):hover{background:var(--panel)}
.splace{font-size:10.5px;letter-spacing:.05em;text-transform:uppercase;color:var(--ink-3);margin:8px 0 2px 18px}
.slist.c-alarm h4{color:var(--fire)} .slist.c-fault h4{color:var(--warn)}
.slist.c-door h4{color:var(--accent)} .slist.c-disablement h4{color:var(--ink-2)}
.slist{border-left:3px solid var(--rule);padding-left:8px}
.slist.c-alarm{border-left-color:var(--fire)} .slist.c-fault{border-left-color:var(--warn)}
.slist.c-disablement{border-left-color:var(--idle)} .slist.c-door{border-left-color:var(--accent)}
/* the floor navigator, on every sheet */
#floornav{position:absolute;left:50%;transform:translateX(-50%);top:64px;z-index:21;display:flex;align-items:center;gap:4px;
  background:color-mix(in srgb,var(--panel) 92%,transparent);border:1px solid var(--rule);border-radius:4px;padding:4px 6px;
  box-shadow:0 4px 16px rgba(0,0,0,.12);font-size:12px;max-width:calc(100% - 560px);overflow:hidden;white-space:nowrap}
/**
 * RADERNA ÖVERST STAPLAS, DE RÄKNAS INTE FÖR HAND.
 *
 * Varje rad som kan stå överst — demostrimman, larmlisten, frånkopplingar
 * över tiden — sköts med en egen `top` per KOMBINATION, och kombinationen
 * demo + över tiden var aldrig skriven. Resultatet syntes på Tomas skärm 13
 * september: den bruna listen låg ovanpå planväljaren. Larmlisten hade sin
 * demovariant, frånkopplingslisten hade det inte, och nästa rad som tillkommer
 * hade glömt en till.
 *
 * Så höjden är en summa i stället: varje list bidrar med sin egen höjd, och
 * alla kombinationer följer av sig själva.
 */
body.demo{--demoh:24px}
body.alarmbar{--alarmh:42px}
body.drvbar{--drvh:42px}
body.overduebar{--odh:42px}
body.provbar{--pvh:42px}
#floornav{top:calc(64px + var(--demoh,0px) + var(--alarmh,0px) + var(--drvh,0px) + var(--odh,0px) + var(--pvh,0px))}
#floornav .fn{font:inherit;font-size:12px;padding:3px 8px;border:1px solid transparent;border-radius:3px;background:none;color:var(--accent);cursor:pointer;white-space:nowrap}
#floornav .fn:hover{background:var(--panel-2);border-color:var(--rule)}
#floornav .fn.off{color:var(--ink-3);opacity:.5;cursor:default}
#floornav .fn.on{background:var(--panel-2);color:var(--ink);border-color:var(--rule)}
#floornav .fn.home{font-size:14px;padding:1px 8px}
#floornav .fn.disc{font:600 10px/1.6 "IBM Plex Mono",monospace;letter-spacing:.06em;border:1px solid var(--rule)}
#floornav .cur{font-weight:600;color:var(--ink);padding:0 4px}
#floornav .disc.cur{font:600 10px/1.6 "IBM Plex Mono",monospace;letter-spacing:.06em;padding:1px 6px;border-radius:2px;border:1px solid currentColor;opacity:.8}
#floornav .lbl{font-size:10px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-3);margin:0 2px}
#floornav .sep{width:1px;height:18px;background:var(--rule);margin:0 4px}
#floornav .fsel{font:inherit;font-size:12px;padding:2px 4px;border:1px solid var(--rule);border-radius:3px;background:var(--panel);color:var(--ink);max-width:160px}
@media (max-width:1100px){#floornav{max-width:calc(100% - 300px)}}
/* selection: shift-click, shift-drag, and the bar */
.mark.picked .sym{outline:2.5px solid var(--accent);outline-offset:3px;border-radius:50%;background:color-mix(in srgb,var(--accent) 18%,transparent)}
#band{position:absolute;border:1.5px dashed var(--accent);background:color-mix(in srgb,var(--accent) 10%,transparent);pointer-events:none;z-index:30}
#selbar{position:absolute;left:50%;transform:translateX(-50%);bottom:12px;z-index:26;display:flex;gap:10px;align-items:center;
  background:var(--panel);border:1px solid var(--accent);border-radius:4px;padding:6px 12px;font-size:12.5px;box-shadow:0 6px 24px rgba(0,0,0,.2)}
#selbar .ct{font-size:11px;color:var(--ink-3)}
#delbar{position:absolute;left:50%;transform:translateX(-50%);bottom:52px;z-index:27;display:flex;gap:10px;align-items:center;
  background:var(--panel);border:1px solid var(--fire);border-radius:4px;padding:7px 12px;font-size:12.5px;box-shadow:0 6px 24px rgba(0,0,0,.25)}
#delbar.undo{border-color:var(--rule)}
#selbar select{font:inherit;font-size:12px;padding:3px 6px;border:1px solid var(--rule);border-radius:3px;background:var(--panel-2);color:var(--ink)}
.tagsblk{display:flex;flex-wrap:wrap;gap:5px;align-items:center;margin:4px 0 10px}
.tagsblk>.ct{font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:var(--ink-3);margin-right:2px}
.chip.tagchip{border-color:color-mix(in srgb,var(--accent) 45%,var(--rule));color:var(--accent);background:color-mix(in srgb,var(--accent) 8%,var(--panel-2))}
.chip.tagchip b{cursor:pointer;margin-left:2px;color:var(--ink-3)} .chip.tagchip b:hover{color:var(--fire)}
.tagsec .tagd{border:1px solid var(--rule-2);border-radius:4px;padding:6px 10px;margin:6px 0;background:var(--panel)}
.tagsec summary{cursor:pointer;display:flex;gap:8px;align-items:center;font-size:12.5px}
.tagsec summary .ct{font-size:11px;color:var(--ink-3);flex:1}
.tagsec .tagpts{display:flex;flex-wrap:wrap;gap:4px;margin:8px 0 4px}
.tagsec .tagpts .chip{cursor:pointer}
.docs .tag.rule{border-color:color-mix(in srgb,var(--warn) 50%,var(--rule));color:var(--warn)}
.docs .tag.reach{cursor:pointer;border-color:color-mix(in srgb,var(--accent) 40%,var(--rule))}
.rulebox{margin:6px 0;font-size:12px}
.rulebox summary{cursor:pointer;color:var(--accent);font-size:12px}
.rulebox .fld.rf{margin:4px 0 0}
.rulebox .fld.rf input,.rcount{font:inherit;font-size:12px}
.rulebox .fld.rf input{width:100%;box-sizing:border-box;padding:4px 7px;margin-top:2px;border:1px solid var(--rule);border-radius:3px;background:var(--panel);color:var(--ink)}
.rcount{margin-top:6px;font-size:12px;color:var(--ink-2);line-height:1.45}
.tag.cats-mini{display:inline-flex;gap:3px;padding:0;background:none;border:0;vertical-align:middle}
#statesBtn{white-space:nowrap}
.tag.cats-mini.off{display:none}
.tag.cats-mini i{font-style:normal;font:600 10px/1 "IBM Plex Mono",monospace;padding:2px 5px;border-radius:9px;
  background:var(--panel-2);color:var(--ink-2);border:1px solid var(--rule)}
.tag.cats-mini i.fire{background:var(--fire);color:#fff;border-color:var(--fire)}
.tag.cats-mini i.warn{background:var(--warn);color:#3B2A00;border-color:var(--warn)}
.tag.cats-mini i.acc{background:var(--accent);color:#fff;border-color:var(--accent)}
.slist{margin:0 0 14px}
.slist h4{display:flex;align-items:center;gap:7px;margin:14px 0 4px;font-size:11px;
  letter-spacing:.08em;text-transform:uppercase;color:var(--ink-2)}
.slist h4 .ct{margin-left:auto;font:600 11px/1 "IBM Plex Mono",monospace;color:var(--ink-3)}
.srow{display:grid;grid-template-columns:10px 1fr;gap:8px;padding:5px 6px;border-radius:3px;
  cursor:pointer;border-top:1px solid var(--rule-2)}
.srow:hover{background:var(--panel-2)}
.srow .nm{font:600 12.5px/1.3 "IBM Plex Mono",monospace;color:var(--ink)}
.srow .nm .desc{font-family:inherit;font-weight:400;color:var(--ink-2)}
.srow .path{font-size:11px;color:var(--ink-3);line-height:1.45;margin-top:1px}
.srow .path .sep{opacity:.5;padding:0 3px}
.srow .why{font-size:11px;color:var(--ink-2);margin-top:2px}
.srow .why .tag{margin:0 4px 0 0}
.srow.unplaced .path{font-style:italic}
.plan-panel h2{font-size:11px;letter-spacing:.09em;text-transform:uppercase;color:var(--ink-3);
  margin:0;padding:11px 14px;border-bottom:1px solid var(--rule-2);font-weight:600;
  display:flex;justify-content:space-between;align-items:center}
.plan-panel .body{overflow:auto;padding:12px 14px}
/* A driver that is not connected: the reason the rest of the list is short. */
.gonebar{border:1px solid var(--fire);border-left-width:3px;border-radius:4px;
  background:color-mix(in srgb,var(--fire) 10%,transparent);padding:9px 11px;margin:10px 0 4px}
.gonebar b{font-size:13px;color:var(--fire)}
.gonebar .gl{display:flex;flex-wrap:wrap;gap:6px 14px;margin:5px 0 4px}
.gonebar .g{display:flex;gap:7px;align-items:baseline;font-size:12.5px}
.gonebar .g .nm{font-weight:600}
.gonebar .ct{font-size:11.5px;color:var(--ink-3);line-height:1.5}
.gonebar .ct.bad{color:var(--fire);font-weight:600}
.gonebar .ct.ok{color:var(--ok)}
.gonebar .g{align-items:baseline}
/* Vad lägesbilden UTELÄMNAT. Tyst, längst ner, en rad: okända punkter som
   ingen placerat är ett underlag som inte är färdigt, inte en nyhet. Men den
   som läser listan ska kunna se att den inte är allt. */
.oplacerade{border-top:1px solid var(--rule-2);margin-top:14px;padding-top:8px;
  font-size:11.5px;color:var(--ink-3);line-height:1.55}

/* Schedules. Blue, not red: this is context, not something wrong. Quiet when
   nothing is being held, and only slightly louder when something is — a
   calendar doing exactly what it was told is not an event. */
.schedbar{border:1px solid var(--rule);border-left:3px solid var(--idle);border-radius:4px;
  background:var(--panel-2);padding:9px 11px;margin:10px 0 4px}
.schedbar.on{border-left-color:var(--cmd);background:color-mix(in srgb,var(--cmd) 8%,transparent)}
.schedbar b{font-size:13px;color:var(--ink-2)}
.schedbar.on b{color:var(--cmd)}
.schedbar .gl{display:flex;flex-wrap:wrap;gap:6px 14px;margin:5px 0 4px}
.schedbar .g{display:flex;gap:7px;align-items:baseline;font-size:12.5px;opacity:.7}
.schedbar .g.act{opacity:1}
.schedbar .g .nm{font-weight:600}
.schedbar .ct{font-size:11.5px;color:var(--ink-3);line-height:1.5}
/* What a test run actually did, said where the button is. */
.ranbox{border:1px solid var(--ok);border-left-width:3px;border-radius:5px;margin:6px 0 4px;padding:7px 10px;
  background:color-mix(in srgb,var(--ok) 10%,transparent);font-size:12px;line-height:1.5}
.ranbox.bad{border-color:var(--warn);background:color-mix(in srgb,var(--warn) 11%,transparent)}
.ranbox b{color:var(--ok)}.ranbox.bad b{color:var(--warn)}
.ranbox .ct{font-size:11px;color:var(--ink-3);margin-top:3px}
.srow .tag.sched{color:var(--cmd);background:color-mix(in srgb,var(--cmd) 13%,transparent)}
.fld{display:block;font-size:11px;letter-spacing:.05em;text-transform:uppercase;
  color:var(--ink-3);font-weight:600;margin:0 0 10px}
.fld select,.fld input[type=file]{display:block;width:100%;margin-top:4px;font:inherit;font-size:12px;
  text-transform:none;letter-spacing:0;color:var(--ink);background:var(--panel-2);
  border:1px solid var(--rule);border-radius:2px;padding:4px 6px}
.layfind{width:100%;box-sizing:border-box;font:inherit;font-size:12px;padding:4px 7px;margin:4px 0 0;
  border:1px solid var(--rule-2);border-radius:3px;background:var(--panel);color:var(--ink)}
.layers{border:1px solid var(--rule-2);border-radius:3px;max-height:190px;overflow:auto;margin:4px 0 10px}
.layers label{display:flex;gap:7px;align-items:center;padding:3px 8px;font-size:12px;cursor:pointer}
.layers label:hover{background:var(--panel-2)}
/* display:flex above wins over the browser's [hidden] rule, so filtering needs
   a class of its own — the first version silently filtered nothing. */
.layers label.off{display:none}
.layers label.hit{color:var(--warn)}
.layers input{accent-color:var(--accent)}

/* palette */
.pal{position:absolute;top:64px;left:280px;width:250px;max-height:calc(100% - 80px);
  background:var(--panel);border:1px solid var(--rule);border-radius:4px;z-index:20;
  display:flex;flex-direction:column;overflow:hidden;box-shadow:0 8px 30px rgba(0,0,0,.14)}
.pal .body{overflow:auto;padding:8px}
.pal.free{right:auto}
.pal h2{cursor:grab;user-select:none}
.pal h2{padding:9px 8px 9px 12px}
.pal h2 .ttl{flex:1;min-width:0;display:flex;align-items:center;gap:6px;flex-wrap:wrap;row-gap:2px}
.pal h2 .tag{white-space:nowrap}
.palfilt .chip.more{border-style:dashed;color:var(--ink-3)}
.palfilt .lbl{font-size:10px;color:var(--ink-3);text-transform:uppercase;letter-spacing:.06em;align-self:center;margin-right:2px}
.pal h2 .hb{display:flex;gap:2px}
.pal h2 .btn{padding:0 6px;font-size:12px;line-height:18px}
.pal.shrunk .body,.pal.shrunk .palfilt{display:none}
.palfilt{padding:7px 8px 0;flex:none}
.palfilt input{width:100%;box-sizing:border-box;font:inherit;font-size:12px;padding:4px 7px;
  border:1px solid var(--rule);border-radius:3px;background:var(--panel-2);color:var(--ink)}
.palfilt .chips{padding:6px 0 2px}
.palfilt .chip{cursor:pointer;user-select:none}
.palfilt .chip.on{border-color:var(--accent);color:var(--accent);background:color-mix(in srgb,var(--accent) 10%,var(--panel-2))}
.palfilt .chip .n{font:600 10px/1 "IBM Plex Mono",monospace;color:var(--ink-3)}
.pal .body .more{font-size:11.5px;color:var(--ink-3);padding:8px 7px;text-align:center}
.pitem{display:flex;align-items:center;gap:9px;padding:6px 7px;border-radius:3px;cursor:grab;
  border:1px solid transparent}
.pitem:hover{background:var(--panel-2);border-color:var(--rule-2)}
.pitem .t{min-width:0}
.pitem .n{font:600 12px/1.2 "IBM Plex Mono",monospace}
.pitem .d{font-size:11px;color:var(--ink-3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.empty{font-size:12px;color:var(--ink-3);padding:10px 4px}

/* alarm banner */
#watchbanner{position:absolute;left:50%;transform:translateX(-50%);top:64px;z-index:24;
  display:flex;gap:8px;align-items:center;background:var(--warn);color:#fff;
  padding:7px 12px;border-radius:3px;box-shadow:0 6px 24px rgba(0,0,0,.28);font-size:12.5px;max-width:78vw}
body.alarmbar #watchbanner{top:112px}
#lockdown{position:fixed;inset:0;z-index:1000;background:var(--ground);display:flex;align-items:center;justify-content:center}
#lockdown .box{text-align:center;color:var(--ink-2)}
#lockdown h2{font-size:22px;margin:0 0 8px;color:var(--ink)}
#lockdown p{margin:0;font-size:14px}
#idlenote{position:absolute;left:50%;transform:translateX(-50%);bottom:18px;z-index:24;background:var(--ink);color:var(--ground);
  padding:7px 12px;border-radius:3px;box-shadow:0 6px 24px rgba(0,0,0,.28);font-size:12.5px;max-width:78vw}
#alarms{position:absolute;left:50%;transform:translateX(-50%);top:64px;z-index:25;
  display:flex;gap:8px;align-items:center;background:var(--fire);color:#fff;
  padding:8px 12px;border-radius:3px;box-shadow:0 6px 24px rgba(0,0,0,.28);font-size:13px}
#alarms button{background:rgba(255,255,255,.18);border:1px solid rgba(255,255,255,.4);
  color:#fff;font:inherit;font-size:12px;padding:3px 9px;border-radius:2px;cursor:pointer}
#alarms button:hover{background:rgba(255,255,255,.3)}
/* Förvarningen under resan: "Brandlarm — Verkstaden · Plan 2 — visar vägen…" */
#journeyNote{position:absolute;left:50%;transform:translateX(-50%);top:104px;z-index:24;
  background:var(--ink);color:var(--ground);padding:6px 12px;border-radius:3px;font-size:12.5px;
  box-shadow:0 6px 24px rgba(0,0,0,.22);max-width:70vw;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
  pointer-events:none}
/**
 * INGEN SER DESSA PUNKTER — listen för en drivrutin som slutat svara.
 *
 * Under larmlisten, för en brand går före; över frånkopplingarna, för en
 * drivrutin som är borta är inte ett val någon gjort utan något som gått
 * sönder. Röd som larmet, men med en pulserande vänsterkant så att de två
 * inte förväxlas på håll: larmet är en punkt, det här är ett hål i
 * övervakningen. Pulsen slutar vid kvittens, färgen står kvar.
 */
#drvbanner{position:absolute;left:50%;transform:translateX(-50%);z-index:25;
  top:calc(64px + var(--demoh,0px) + var(--alarmh,0px));
  display:flex;gap:9px;align-items:center;background:var(--fire);color:#fff;
  padding:8px 12px;border-radius:3px;box-shadow:0 6px 24px rgba(0,0,0,.28);font-size:13px;max-width:86vw}
#drvbanner::before{content:"";width:4px;align-self:stretch;border-radius:2px;background:#fff;
  animation:drvpuls 1.1s ease-in-out infinite}
#drvbanner.kvitterad::before{animation:none;opacity:.45}
@keyframes drvpuls{0%,100%{opacity:1}50%{opacity:.25}}
#drvbanner .nms{font-family:"IBM Plex Mono",monospace;font-size:12px;opacity:.95;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:34vw}
#drvbanner .ackst{font-size:11px;opacity:.85;padding:1px 6px;border:1px solid rgba(255,255,255,.4);border-radius:9px;white-space:nowrap}
#drvbanner button{background:rgba(255,255,255,.18);border:1px solid rgba(255,255,255,.4);
  color:#fff;font:inherit;font-size:12px;padding:3px 9px;border-radius:2px;cursor:pointer;white-space:nowrap}
#drvbanner button:hover{background:rgba(255,255,255,.3)}
#drvbanner button.ghost{background:none}
/* disablements past their time: yellow, under the alarm bar */
#overdue{position:absolute;left:50%;transform:translateX(-50%);top:64px;z-index:24;
  display:flex;gap:10px;align-items:center;background:var(--warn);color:#3B2A00;
  padding:7px 12px;border-radius:3px;box-shadow:0 6px 24px rgba(0,0,0,.22);font-size:12.5px;max-width:70vw}
/** Frånkopplingslisten ligger under larmlisten, och planväljaren under båda — se staplingen ovan. */
#overdue{top:calc(64px + var(--demoh,0px) + var(--alarmh,0px) + var(--drvh,0px))}
#overdue .od{cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:260px}
#overdue .od i{font-style:normal;opacity:.75;margin-left:4px;font-size:11px}
#overdue button{background:rgba(0,0,0,.08);border:1px solid rgba(0,0,0,.25);color:#3B2A00;font:inherit;font-size:12px;padding:3px 9px;border-radius:2px;cursor:pointer}
body.overduebar .plan-panel{top:112px}
.dform2 .fld{margin:6px 0 0}
.dform2 .fldin{width:100%;box-sizing:border-box;font:inherit;font-size:12.5px;padding:5px 7px;margin-top:3px;border:1px solid var(--rule);border-radius:3px;background:var(--panel);color:var(--ink)}
.dform2 select{width:100%;font:inherit;font-size:12.5px;padding:5px 7px;margin-top:3px;border:1px solid var(--rule);border-radius:3px;background:var(--panel);color:var(--ink)}
.dform2 .chips{margin-top:4px}
.dform2 .chip{cursor:pointer;user-select:none}
.dform2 .chip.on{border-color:var(--accent);color:var(--accent);background:color-mix(in srgb,var(--accent) 10%,var(--panel-2))}
.srow .dwhy .tag{border-color:color-mix(in srgb,var(--accent) 40%,var(--rule))}
.srow .dwhy.over .tag{border-color:var(--warn);color:var(--warn)}
label.chk{display:flex;gap:8px;align-items:center;font-size:13px;margin:6px 0 4px}
label.chk input{accent-color:var(--accent)}
.ack{margin:8px 0 10px;padding:8px 10px;border:1px solid var(--fire);border-radius:4px;background:color-mix(in srgb,var(--fire) 7%,var(--panel-2))}
.ack.done{border-color:var(--ok);background:color-mix(in srgb,var(--ok) 8%,var(--panel-2))}
/* NÅGON ANNAN HÅLLER LARMET. Rutan är ihopfälld och neutral — inte larmröd,
   för den ber inte om något; den berättar vem som redan gör det. */
.ack.held{border-color:var(--cmd);background:color-mix(in srgb,var(--cmd) 9%,var(--panel-2))}
.ack.held .heldby{font-size:13.5px;color:var(--ink);margin-bottom:4px}
.ack.held .heldby b{font-weight:700}
.ack.held .ct{display:block;margin-bottom:8px}
.handle.other{border-color:var(--cmd);background:color-mix(in srgb,var(--cmd) 5%,var(--panel-2))}
.ack .t2{font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-3);margin-bottom:6px;display:flex;gap:8px;align-items:center}
.ack .t2 .ct{text-transform:none;letter-spacing:0;font-weight:400;margin-left:auto}
.ack .alab{font-size:12.5px;font-weight:600;margin:2px 0 3px}
.ack .achk{list-style:none;margin:0 0 6px;padding:0;font-size:12.5px;line-height:1.5}
.ack .achk li label{display:flex;gap:7px;align-items:flex-start;cursor:pointer}
.ack .achk input{accent-color:var(--fire);margin-top:3px}
.ack .achk li.on{color:var(--ink)} .ack .achk li:not(.on){color:var(--ink-3)}
.ack textarea{width:100%;box-sizing:border-box;font:inherit;font-size:12.5px;padding:5px 7px;margin-top:3px;border:1px solid var(--rule);border-radius:3px;background:var(--panel);color:var(--ink)}
.ack .astmt{white-space:pre-wrap;font-size:13px;margin:0 0 6px}
.handle{margin:0 0 10px;padding:8px 10px;border:1px solid var(--rule);border-radius:4px;background:var(--panel-2)}
.handle.mine{border-color:var(--cmd);background:color-mix(in srgb,var(--cmd) 8%,var(--panel-2))}
.handle .t2{font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-3);margin-bottom:6px}
.handle .t2.strong{color:var(--ink);text-transform:none;letter-spacing:0;font-size:13px;font-weight:600}
.handle .acts{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.handle .acts .ct{margin-left:2px}
.handle .hoList{display:flex;gap:6px;flex-wrap:wrap;margin-top:8px}
#alarms .ackst{font-size:11px;opacity:.85;padding:1px 6px;border:1px solid rgba(255,255,255,.4);border-radius:9px}
/* VEM SOM HÅLLER LARMET ÄR INTE EN BRICKA BLAND ANDRA. Fullt oförmörkad,
   fylld, och namnet i fetstil — den ska läsas på andra sidan rummet. */
#alarms .ackst.held{opacity:1;font-size:12px;font-weight:600;padding:2px 8px;
  background:rgba(255,255,255,.22);border-color:rgba(255,255,255,.75)}
#alarms .ackst.held b{font-weight:800}
#alarms .ackst.held.mine{background:rgba(255,255,255,.1);font-weight:500}
.docs .citems{margin:2px 0 0;padding-left:18px;font-size:12.5px;line-height:1.5}
.dreg{margin:8px 0 10px;padding:8px 10px;border:1px solid var(--rule-2);border-left:3px solid var(--idle);border-radius:4px;background:var(--panel-2)}
.dreg.over{border-left-color:var(--warn)} .dreg.over .t2 b{color:var(--warn)}
.dreg .t2{font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-3);margin-bottom:4px}
.dreg dl.kv{margin:0 0 6px}

.zoomer{position:absolute;left:280px;bottom:12px;z-index:20;display:flex;gap:6px}
.hint{position:absolute;right:12px;bottom:12px;z-index:20;font-size:11px;color:var(--ink-3);
  background:color-mix(in srgb, var(--panel) 85%, transparent);padding:4px 8px;border-radius:2px}

/* Responsive rules live last: CSS of equal specificity is won by whatever
   comes later, and these have to beat the base layout they are overriding. */
/* ---------- narrower screens ---------- */
@media (max-width: 1180px){
  #rail{width:220px} #stage{left:220px}
  .plan-panel,.pal,.zoomer{left:232px}
  .side{width:280px}
}
@media (max-width: 900px){
  /* The rail floats over the stage rather than shrinking it, and starts
     closed — on a small screen 268px of tree is most of the picture. */
  #rail{position:absolute;z-index:26;box-shadow:0 8px 30px rgba(0,0,0,.35)}
  #stage{left:0}
  .plan-panel,.pal,.zoomer{left:12px}
  /* On a small screen a floating panel covers the thing it describes, so it
     becomes a sheet along the bottom instead. */
  .side,.side.free{left:0;right:0;top:auto;bottom:0;width:auto;max-height:52%;
    border-radius:4px 4px 0 0;border-left:none;border-right:none;border-bottom:none}
  .pal{width:200px}
  .plan-panel{width:calc(100% - 24px);max-height:46%}
  /* The detail sheet covers the bottom half; the zoom controls have to live
     above it or they are permanently half under a panel. */
  body.sheet .zoomer{bottom:calc(52% + 12px)}
  /* Room for the breadcrumb: the status pills are the first thing to go. */
  header h1{display:none}
  #srvPill{display:none}
  .tog{font-size:11px}
}
@media (max-width: 900px){
  /* The simulation drawer is a bottom sheet too, but above the detail panel
     it would otherwise sit under. */
  #simPanel{left:0;right:0;bottom:0;width:auto;max-height:52%;z-index:23;
    border-radius:4px 4px 0 0;border-left:none;border-right:none;border-bottom:none}
}
@media (max-width: 620px){
  .tog span,.tog{gap:4px}
  #panelPill{display:none}
  /* The demo stripe keeps its colour but loses the explanation. */
  #demoBar .lite{display:none}
  /**
   * BEVAKNINGEN PÅ EN TELEFON.
   *
   * .wgrid är tre spalter — vad, hur, när — och hade ingen telefonregel alls.
   * På 390 px blev "när" en remsa längst ut till höger med veckodagarna på
   * sju rader och klockslagen utanför kanten. Tomas: "dagar tider osv ligger
   * långt till höger." Tre spalter under varandra är inte en kompromiss här;
   * det är samma formulär läst uppifrån och ner, vilket är hur en telefon
   * läses ändå.
   */
  .wgrid{grid-template-columns:1fr;gap:14px}
  .wgrid > div{min-width:0}
  /** Spalterna behöver en skiljelinje när de ligger på varandra — annars blir det ett fält. */
  .wgrid > div + div{border-top:1px solid var(--rule-2);padding-top:10px}
  /** Veckodagarna som tryckytor i stället för sju rader kryssrutor. */
  .wgrid .days{gap:4px 6px}
  .wgrid .days label{display:inline-flex;align-items:center;gap:4px;border:1px solid var(--rule-2);
    border-radius:11px;padding:3px 9px;background:var(--panel)}
  .wgrid .days label:has(input:checked){border-color:var(--accent);color:var(--ink)}
  .wgrid .row2{display:flex;flex-wrap:wrap;gap:8px}
  .wgrid .row2 .fld{flex:1 1 120px;min-width:0}
  .wgrid .fldin{width:100%;box-sizing:border-box}
  /** Kryssrutor och tidsfält ska gå att träffa med ett finger. */
  .wgrid input[type="checkbox"],.wgrid input[type="radio"]{width:17px;height:17px}
  .wgrid input[type="time"],.wgrid input[type="datetime-local"]{font-size:15px;padding:6px 8px}
  .wrule{padding:8px 8px 4px}
}
/**
 * A PHONE.
 *
 * Everything above assumes a window that is at least as wide as a laptop's,
 * and a 390 px screen breaks every one of those assumptions at once: a 520 px
 * panel hanging off the right, a header on three lines, a summary card over
 * the whole map. Tomas, on his phone: *everything went haywire — zooming,
 * dialogues too big, off.*
 *
 * The rule here is that anything floating becomes a full-width sheet and
 * anything optional goes away. What is left is the drawing, which is what
 * somebody has opened a phone to look at.
 */
@media (max-width: 560px){
  /**
   * The header carries eleven controls. On a 390 px screen they do not fit,
   * and `overflow:hidden` does not mean "make it work" — it means the ones at
   * the end are simply gone and the breadcrumb, which is how you get back, is
   * squeezed to nothing underneath them.
   *
   * So the phone header keeps what a phone is for: where am I (breadcrumb),
   * what is wrong (lägesbild), the drawing panel, and the two settings.
   * Following alarms stays ON and loses its tick box; editing a site by finger
   * is not a thing anybody does, and the role picker and status pills belong
   * on a screen with room for them.
   */
  header{gap:5px;padding:0 8px}
  header .btn{padding:4px 8px;font-size:11.5px;white-space:nowrap;flex:none}
  header .tog,header #who,header #srvPill,header #panelPill{display:none}
  header .hact{gap:6px}
  .crumbs{font-size:12px;flex:1 1 auto;min-width:0}
  .crumbs button{padding:2px}
  /* Panels are sheets from the bottom, full width, and never wider than the
     screen — including the wide one. */
  /* #cogPanel and #simPanel pin themselves to the right with a fixed width by
     id (higher specificity than a class), so they are named here too — or
     the settings sheet hangs 22 px off the left edge of the phone, which is
     what Tomas saw: "den panelen är för stor för telefonens skärm". */
  .plan-panel,.plan-panel.wide,#cogPanel,#simPanel{left:0;right:0;bottom:0;width:auto;max-width:none;
    max-height:66%;border-radius:5px 5px 0 0;border-left:none;border-right:none;border-bottom:none}
  body.alarmbar .plan-panel{top:auto}
  .pal{left:0;right:0;bottom:0;width:auto;max-height:52%;border-radius:5px 5px 0 0}
  /* Two heights for the detail sheet — see sheetTall() in 49-sidopanel.js.
     Low by default so the drawing stays the picture; tall on request. The
     command row sticks to the sheet's bottom edge so it is reachable in both
     without scrolling past the description and the health card. */
  .side,.side.free{max-height:40%}
  .side.tall,.side.free.tall{max-height:88%}
  .side #sideActs{position:sticky;bottom:-12px;margin:8px -14px -12px;padding:8px 14px 10px;
    background:var(--panel);border-top:1px solid var(--rule-2);z-index:2}
  .side #sideActs:empty{display:none}
  /* The detail sheet covers the bottom of the screen, so the zoom buttons have
     to sit above it — the 900 px rule did that and this one was overriding it,
     which put them on top of the sheet's own header. */
  body.sheet .zoomer{bottom:calc(40% + 10px)}
  /* The alarm bar is centred and sized for a desktop; on a phone it wrapped to
     three lines across the middle of the drawing. */
  /**
   * OCH DEN FÅR ALDRIG GÖRA SIDAN BREDARE ÄN TELEFONEN.
   *
   * Mätt 22 september på 390 px, med två larm:
   *
   *   svenska:  kvittera 66 · nästa okvitterade 89 · kvittera alla 66  = 221 px  → ryms
   *   engelska: acknowledge 99 · next unacknowledged 122 · acknowledge all 99 = 320 px → ryms inte
   *
   * Och när den inte ryms VÄXER LAYOUTVYN: `innerWidth` gick från 390 till
   * 449, alltså skalade webbläsaren ned hela sidan för att få plats. Då är det
   * inte bara knappen som är avklippt — allt annat blir mindre och hamnar fel
   * samtidigt, och det var det Tomas såg: *"Lite stökigt på mobil vid larm
   * också."*
   *
   * Layouten var alltså mätt i ETT språk. Engelska ord är här fyrtio procent
   * längre. Så: raden får radbrytas, texten får krympa till ingenting, och
   * knapparna behåller sin storlek — det är texten man kan avvara, inte
   * kvitteringen.
   */
  #alarms{left:8px;right:8px;transform:none;top:auto;bottom:8px;font-size:12px;padding:7px 10px;
    justify-content:flex-start;flex-wrap:wrap;row-gap:6px;max-width:calc(100vw - 16px);box-sizing:border-box}
  #alarms>span:not(.ackst){flex:1 1 60px;min-width:0}
  #alarms button{flex:0 0 auto;max-width:100%}
  body.sheet #alarms{bottom:calc(40% + 10px)}
  body.sheet.sheet-tall #alarms{bottom:calc(88% + 10px)}
  #alarms span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  /* The estate summary is a strip across the top, not a card over the map. */
  #mapsum{left:8px;right:8px;top:8px;width:auto;max-width:none;padding:9px 11px}
  #mapsum .ms-facts{margin-bottom:6px}
  /**
   * The bottom-left corner has three tenants on a phone — the detail sheet,
   * the alarm bar and the zoom buttons — and they stack in that order, worst
   * news lowest. Spelled out rather than left to chance: the first version put
   * the alarm bar straight on top of the zoom buttons.
   */
  .zoomer{left:8px;bottom:8px}
  body.alarmbar .zoomer{bottom:52px}
  body.sheet .zoomer{bottom:calc(40% + 10px)}
  body.sheet.alarmbar .zoomer{bottom:calc(40% + 54px)}
  /* Tall sheet: the drawing is a sliver — the zoom buttons have nothing to zoom. */
  body.sheet.sheet-tall .zoomer{display:none}
  .hint{display:none}
  /* Hit targets: a finger is not a mouse pointer. */
  .zoomer .btn{padding:9px 14px;font-size:14px}
  .srow{padding:9px 6px}
  .layers label{padding:7px 8px}
  /**
   * THE THREE SURFACES MUST BE REACHABLE (12 sep 2026, Tomas on his phone).
   *
   * Karta · Personer · CCTV live in the breadcrumb, and the breadcrumb is
   * the one flexible thing in the header — so on a 390 px screen the fixed
   * items on either side (the "who sits here" field, the open-mode button)
   * squeezed it to nothing and the tabs were simply not there. On a phone
   * the operator name has no place (a phone is one person's), the open-mode
   * button shrinks to its glyph, and the header may grow to a second line
   * so the tabs always fit. The two bottom-row extras on the map — the
   * label mode and the map theme — are in the "mer" menu already and go.
   */
  /**
   * EXACTLY TWO ROWS: the buttons, then the tabs and who you are.
   *
   * The first version made the breadcrumb `flex:1 1 100%`, which is right
   * only while the third thing in the header is narrow. Logged in it is not:
   * the chip says "tomas Administrator · system administrator", it took the
   * whole second row, and the tabs were pushed to a third row the header's
   * own height cut off — Tomas on his phone: "jag kommer inte ut från
   * Person". So the chip loses its role text (the menu it opens says it
   * anyway), and it shares the second row with the tabs instead of
   * displacing them.
   */
  header{height:86px;flex-wrap:wrap;align-content:flex-start;padding:0 8px;row-gap:0}
  header .hact{height:52px;flex:0 0 auto;min-width:0}
  header .hwho{order:10;height:34px;flex:0 0 auto;max-width:46%;gap:4px}
  header .hwho .opname{display:none}
  header .userchip{max-width:100%;font-size:11.5px;padding:2px 8px}
  header .userchip .r{display:none}
  header #openBtn{font-size:0;padding:3px 8px}
  header #openBtn::before{content:"öppet";font-size:11.5px}
  .crumbs{flex:1 1 auto;order:9;height:34px;justify-content:flex-start;min-width:0;overflow-x:auto;scrollbar-width:none}
  .crumbs::-webkit-scrollbar{display:none}
  .crumbs .tabs{margin-left:0}
  .crumbs .tabs button{padding:5px 12px;font-size:13px}
  /* Everything that hangs under the header follows its new height. */
  #rail,#demoBar{top:86px} #stage{top:86px}
  body.iamview #iamPanel,body.cctvview #cctvPanel{top:87px}
  body.demo #rail,body.demo #stage{top:110px}
  /* Panels and the detail sheet are bottom sheets on a phone — their top stays auto, demo stripe or not. */
  body.demo .plan-panel,body.demo .pal,body.demo .side,body.demo .side.free{top:auto}
  body.demo.iamview #iamPanel,body.demo.cctvview #cctvPanel{top:111px}
  #demoBar{gap:6px;padding:0 8px;font-size:10px;letter-spacing:.04em}
  #demoBar button{font-size:10px;padding:1px 6px}
  /* The stripe says "demo" and keeps its two buttons; the sentence is in the licence view. */
  #demoBar:not(.lic)>span:first-child{font-size:0}
  #demoBar:not(.lic)>span:first-child::before{content:"Demo";font-size:10px}
  #demoBar>span:first-child{overflow:hidden;text-overflow:ellipsis;min-width:0;flex:1 1 auto}
  #demoBar #demoOpen{font-size:0;padding:1px 6px}
  #demoBar #demoOpen::before{content:"simulering";font-size:10px}
  .zoomer #addrBtn2,#mapTools{display:none}
  /**
   * THE FLOOR NAVIGATION (Tomas, 12 sep, on his phone: the yellow demo
   * stripe covered it, and what showed was broken). It sat at a desktop
   * offset under a header that is now taller, and its width was
   * "the screen minus 300 px" — ninety pixels on a phone. Full width,
   * under the stripe, scrolls sideways if the building has many floors.
   */
  /** På telefonen ligger listerna i sidled över hela bredden; bara demostrimman skjuter ner planväljaren. */
  #floornav{left:8px;right:8px;transform:none;max-width:none;overflow-x:auto;scrollbar-width:none;
    top:calc(94px + var(--demoh,0px))}
  #floornav::-webkit-scrollbar{display:none}
  #floornav .fsel{max-width:120px}
  /* And the estate summary card moves below it. */
  #mapsum{top:44px}
}

/* ------------------------------------------------------ symbolpaletten
 *
 * Att välja symbol är att välja BILD. Rullgardinen som stod här listade
 * namn — "Rökdetektor — SB-17-016" — och den som ritar såg aldrig symbolen
 * förrän den satt på planet. Rutnätet ritar dem. Se 54-symbolpalett.js.
 */
.symbtn{display:flex;align-items:center;gap:8px;width:100%;text-align:left;cursor:pointer;
  background:var(--panel);border:1px solid var(--rule);border-radius:5px;padding:6px 9px;
  color:var(--ink);font:inherit}
.symbtn:hover{border-color:var(--accent)}
.symbtn .n{font-size:12.5px}
.symbtn .c{font-size:11px;color:var(--ink-3);font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
.symbtn .ch{margin-left:auto;font-size:11px;color:var(--accent)}

.sympal{position:fixed;inset:0;z-index:220;background:rgba(0,0,0,.32);
  display:flex;align-items:center;justify-content:center;padding:16px}
.symsheet{background:var(--panel);border:1px solid var(--rule);border-radius:8px;
  box-shadow:0 18px 48px rgba(0,0,0,.28);width:min(760px,100%);max-height:min(78vh,720px);
  display:flex;flex-direction:column;overflow:hidden}
.symtop{display:flex;align-items:center;gap:10px;padding:10px 12px;border-bottom:1px solid var(--rule-2)}
.symq{flex:1;min-width:0;font:inherit;font-size:13px;padding:6px 9px;
  border:1px solid var(--rule);border-radius:5px;background:var(--ground);color:var(--ink)}
.symcount{font-size:11.5px;color:var(--ink-3);white-space:nowrap}
/* auto-fill, inte auto-fit: en enda träff ska ligga kvar till vänster i sin
   egen ruta, inte blåsas upp över hela bredden. */
.symgrid{overflow:auto;padding:10px;display:grid;gap:6px;
  grid-template-columns:repeat(auto-fill,minmax(104px,1fr))}
.symcell{display:flex;flex-direction:column;align-items:center;gap:3px;cursor:pointer;
  background:none;border:1px solid transparent;border-radius:6px;padding:8px 4px;
  color:var(--ink);font:inherit}
.symcell:hover{background:var(--panel-2)}
.symcell.cur{border-color:var(--rule)}
.symcell.on{border-color:var(--accent);background:var(--panel-2)}
.symcell .n{font-size:11px;line-height:1.25;text-align:center;
  /* "Kombinationsdetektor" är ett ord och rutan är 104 px. Utan det här
     rinner två namn ihop till ett. */
  overflow-wrap:anywhere;hyphens:auto}
.symcell .c{font-size:9.5px;color:var(--ink-3);font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
.symgrid .empty{grid-column:1/-1;padding:26px 12px;text-align:center;color:var(--ink-3);font-size:12.5px}
.symfoot{padding:8px 12px;border-top:1px solid var(--rule-2);font-size:11.5px;color:var(--ink-3)}
@media (max-width:620px){
  .sympal{padding:0;align-items:stretch}
  .symsheet{width:100%;max-height:100%;border-radius:0;border:none}
  .symgrid{grid-template-columns:repeat(auto-fill,minmax(92px,1fr))}
}

/* En punkt ur en export som ingen central bekräftat — se shared/planerat.js. */
.pitem .demotag.plan{color:var(--unknown);border-color:var(--unknown)}
/* Raden i ⚙ bär varningar under sig, inte bredvid: .drv är en flexrad, och
   en anmärkning som blir en egen kolumn pressar ihop allt annat. */
#cogBody .drv.plan{flex-wrap:wrap}
#cogBody .drv.plan .ct{flex:1 1 auto;min-width:0}
#cogBody .drv.plan .cn{flex-basis:100%;margin:2px 0 0 16px;font-size:11.5px;
  color:var(--ink-3);line-height:1.45}


/* ---------------------------------------------------------------- provläget
 *
 * EGEN FÄRG, INTE LARMETS OCH INTE DRIVRUTINSBORTFALLETS.
 *
 * Ingenting är fel när provläget står på — men läget är inte normalt, och en
 * operatör som ser larm rada upp sig måste kunna se på håll vilken av de tre
 * sakerna som pågår. Rött är "något är trasigt", det här är "det du ser nu är
 * en övning". Den diagonala randningen säger samma sak utan att läsa texten,
 * och överlever att någon tittar på skärmen från andra sidan rummet.
 */
#provbanner{position:absolute;left:50%;transform:translateX(-50%);z-index:25;
  top:calc(64px + var(--demoh,0px) + var(--alarmh,0px) + var(--drvh,0px) + var(--odh,0px));
  display:flex;gap:9px;align-items:center;color:#10131a;font-size:13px;max-width:88vw;
  padding:8px 12px;border-radius:3px;box-shadow:0 6px 24px rgba(0,0,0,.28);
  background:repeating-linear-gradient(135deg,#f2c14e 0 12px,#e8b63c 12px 24px)}
#provbanner b{letter-spacing:.04em}
#provbanner .pst{font-size:11px;padding:1px 6px;border:1px solid rgba(0,0,0,.28);border-radius:9px;white-space:nowrap}
#provbanner .pst.warn{background:#10131a;color:#f2c14e;border-color:#10131a;font-weight:600}
#provbanner button{background:rgba(0,0,0,.12);border:1px solid rgba(0,0,0,.32);
  color:#10131a;font:inherit;font-size:12px;padding:3px 9px;border-radius:2px;cursor:pointer;white-space:nowrap}
#provbanner button:hover{background:rgba(0,0,0,.2)}

.pvpane{position:absolute;right:14px;z-index:26;width:min(760px,94vw);max-height:78vh;overflow:auto;
  top:calc(70px + var(--demoh,0px) + var(--alarmh,0px) + var(--drvh,0px) + var(--odh,0px) + var(--pvh,0px));
  background:var(--panel);border:1px solid var(--rule);border-radius:4px;
  box-shadow:0 10px 40px rgba(0,0,0,.34);padding:12px 14px}
.pvpane .pvhead{display:flex;align-items:center;gap:8px;margin-bottom:8px}
.pvpane .pvsec{border-top:1px solid var(--rule);padding-top:10px;margin-top:10px}
.pvpane .pvsec:first-of-type{border-top:0;margin-top:0}
.pvpane h4{margin:0 0 6px;font-size:12.5px;display:flex;align-items:center;gap:8px}
.pvpane .pvper{font:inherit;font-size:12px;padding:2px 6px;background:var(--panel-2);
  color:var(--ink);border:1px solid var(--rule);border-radius:3px}
.pvpane .pvrow{display:flex;align-items:center;gap:10px;justify-content:space-between;
  padding:6px 0;border-bottom:1px solid var(--rule)}
.pvpane .pvrow:last-child{border-bottom:0}
.pvpane .pvscope{max-height:190px;overflow:auto;border:1px solid var(--rule);border-radius:3px;padding:6px 8px}
.pvpane .pvscope .lvl2{padding-left:18px}
.pvpane .pvsum{display:flex;gap:6px;align-items:center;flex-wrap:wrap;margin-bottom:8px}
/* Den tredje raden är hela poängen — se provningsProtokoll() i servern. */
.pvpane .pvwarn{background:var(--panel-2);border-left:3px solid var(--warn);
  padding:6px 10px;margin-bottom:8px;font-size:12px;line-height:1.5}
.pvpane .pvtab{font-size:12px}
.pvpane .pvtab tr.pv-utan td{background:color-mix(in srgb,var(--warn) 9%,transparent)}
.pvpane .pvtab tr.pv-oprovad td{opacity:.72}

.pvpane .pvfilt{display:flex;gap:5px;align-items:center;flex-wrap:wrap;margin:6px 0 8px}
.pvpane .btn.tiny.on{background:var(--accent);color:#fff;border-color:var(--accent)}
.pvpane .pvtab tr.pv-provad td{background:color-mix(in srgb,var(--ok,#1d6b3f) 7%,transparent)}
.pvpane select.pvbed,.pvpane input.pvnote{font:inherit;font-size:12px;width:100%;
  background:var(--panel-2);color:var(--ink);border:1px solid var(--rule);border-radius:3px;padding:2px 5px}
/* Underskriften står för sig, inte som ännu en rad i tabellen. */
.pvpane .pvval{display:flex;gap:8px;align-items:end;flex-wrap:wrap;margin-top:10px;
  border-top:1px solid var(--rule);padding-top:10px}
.pvpane .pvval .fld{min-width:260px;flex:1}

/* Symbol per modell — tabellen på centralsidan. Se 38-central.js.
   Smal på modell och antal, resten åt väljaren: modellbeteckningar är korta
   och symbolnamnen långa ("Vägledande markering, tillgänglighetsanpassad"). */
.symkarta .symtab{width:100%;margin-top:8px}
.symkarta .symtab th{text-align:left;font-weight:600;color:#5A6472;font-size:12px;padding:4px 8px 4px 0}
.symkarta .symtab td{padding:3px 8px 3px 0;vertical-align:middle}
.symkarta .symtab td:nth-child(2){white-space:nowrap;color:#5A6472}
.symkarta select.symval{width:100%;max-width:340px}
.symkarta .tag.warn{background:#E3B02F;color:#161A1F}
.symkarta .symtab td.symikon{width:34px;padding-right:6px}
.symkarta .symtab td.symikon img{width:28px;height:28px;object-fit:contain;display:block;
  background:#fff;border:1px solid #E3E7EB;border-radius:4px}
.symkarta .symkmd{font-size:11px;line-height:1.3;margin-top:1px;max-width:280px}
.symkarta .symforslag{margin-top:3px;display:flex;gap:6px;align-items:baseline;flex-wrap:wrap}
.symkarta .symforslag .ct{font-size:11px}
.symkarta .symforslag .btn.tiny{padding:1px 7px;font-size:11px}
/* Provläget: disciplinerna i omgångens rubrik och i startrutan. */
.pvdiscip{margin-left:8px;font-size:12px;white-space:nowrap}
.pvdiscip i.sw{margin:0 3px 0 6px}
.pvdisc{margin-top:8px;display:flex;gap:14px;flex-wrap:wrap;align-items:center}
.pvdisc .ct{flex-basis:100%}

/* Paletten: disciplinbricka per rad, och disciplinchipar överst.
   Färgen är samma som i rälen — den som läst den känner igen sig. */
.pitem > i.sw{flex:0 0 auto;width:9px;height:9px;border-radius:2px;margin:0 2px 0 -2px}
#palChips .chip.disc i.sw{width:9px;height:9px;border-radius:2px;margin-right:5px;display:inline-block}


/* ---------------------------------------------------------------- topologi
   Behörighetsförslaget. Avsikten till vänster, översättningen till höger —
   samma form som simuleringen hade, med sidans egna färger i stället för
   sina egna. En yta som ser ut som en främmande produkt läses som en
   främmande produkt. */
.topo{display:grid;grid-template-columns:minmax(260px,1fr) minmax(320px,1.6fr);gap:14px;align-items:start}
@media (max-width:860px){.topo{grid-template-columns:1fr}}
.tpane{border:1px solid var(--rule-2);border-radius:10px;background:var(--panel);padding:10px 12px 12px}
/* `<header>` är upptaget av sidans topprad (height:52px, overflow:hidden) —
   en rubrik i en panel som använder samma element försvinner tyst. Egen
   klass i stället: ett elementnamn som redan betyder något annat är inte
   ledigt bara för att det låter rätt. */
.thead{display:flex;align-items:baseline;gap:8px;margin:0 0 8px}
.thead h3{margin:0;font-size:13px;letter-spacing:.01em}
.thead.tsecond{margin-top:14px;padding-top:10px;border-top:1px solid var(--rule-2)}
.tctl{display:flex;flex-direction:column;gap:3px;margin-bottom:8px}
.tctl label{font-size:11.5px;color:var(--ink-3)}
.tctl input,.tctl select{width:100%}
.tswitch{display:flex;align-items:center;gap:7px;font-size:12px;margin:2px 0 10px}
.ttree{border:1px solid var(--rule-2);border-radius:8px;max-height:46vh;overflow:auto;background:var(--panel-2)}
.tsite{padding:6px 10px;font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;
  color:var(--ink-3);background:var(--panel);position:sticky;top:0;border-bottom:1px solid var(--rule-2)}
.trow{display:flex;align-items:center;gap:8px;padding:5px 10px;font-size:12.5px;cursor:pointer}
.trow:hover{background:color-mix(in srgb,var(--ink) 4%,transparent)}
.trow>span:first-of-type{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.tbld{font-weight:600}
.tdoor{padding-left:26px;font-weight:400}
.tsys{font-size:10.5px;color:var(--ink-3);white-space:nowrap}
.tflag{font-size:10px;padding:1px 5px;border-radius:99px;background:var(--panel-2);color:var(--ink-3);font-weight:500}
.tsmall{font-size:11px;margin:8px 0 0}
.tempty{font-size:12px;color:var(--ink-3);margin:6px 0}
.tsysbox{border:1px solid var(--rule-2);border-radius:8px;padding:8px 10px;margin-bottom:10px;background:var(--panel-2)}
.tsysbox>.thead{margin:0 0 6px}
.tsysbox>.thead h3{font-size:12.5px}
.tvendor{font-size:10.5px;color:var(--ink-3)}
.tcap{font-size:11px;color:var(--ink-3);margin-bottom:6px}
.tcap.no{color:var(--warn,#a66300)}
.tgrp{border-left:2px solid var(--rule-2);padding:4px 0 4px 8px;margin:6px 0}
.tg{display:flex;align-items:baseline;gap:6px}
.tg .tid{font-size:10.5px;color:var(--ink-3);font-family:ui-monospace,monospace}
.tsched{font-size:11.5px;color:var(--ink-3);margin-top:1px}
.tchips{display:flex;flex-wrap:wrap;gap:4px;margin-top:5px}
.tchip{font-size:11px;padding:1px 7px;border-radius:99px;background:var(--panel);border:1px solid var(--rule-2)}
.tchip.extra{border-style:dashed;color:var(--ink-3)}
.tnote{display:flex;flex-direction:column;gap:1px;padding:6px 8px;border-radius:6px;margin:6px 0;font-size:11.5px;
  border-left:3px solid var(--rule-2);background:var(--panel)}
.tnote b{font-size:11.5px}
.tnote.bad{border-left-color:var(--fire)}
.tnote.warn{border-left-color:var(--warn,#a66300)}
.tnote.unsure{border-left-color:var(--ink-3)}
.tnote.idle{border-left-color:var(--rule-2);color:var(--ink-3)}
.trec{padding:7px 9px;border-radius:6px;background:var(--panel);border:1px dashed var(--rule-2);
  display:flex;flex-direction:column;gap:2px;font-size:12px;margin-top:8px}
.trec span{font-size:11px;color:var(--ink-3)}
.tmap{display:flex;flex-direction:column;gap:2px}
.tdoorrow{display:grid;grid-template-columns:minmax(80px,1fr) auto;gap:4px 10px;align-items:baseline;
  padding:5px 8px;border-radius:6px;background:var(--panel-2);border-left:3px solid var(--rule-2);font-size:12px}
.tdoorrow .tdw{grid-column:1/-1;font-size:11px;color:var(--ink-3)}
.tdoorrow .tds{font-size:11px;color:var(--ink-3);white-space:nowrap}
.tdoorrow.s-ok{border-left-color:var(--ok)}
.tdoorrow.s-extra{border-left-color:var(--warn,#a66300)}
.tdoorrow.s-unverified,.tdoorrow.s-uncertain,.tdoorrow.s-attest,.tdoorrow.s-partial{border-left-color:var(--ink-3)}
.tdoorrow.s-missing{border-left-color:var(--fire)}
.tdoorrow.s-none{opacity:.55}
.tw{color:var(--warn,#a66300)}
.tb{color:var(--fire)}
.tu{color:var(--ink-3)}

/* ---------- språken i ⚙ (se ritaSprak i del 47) ---------- */
.sprakbar{display:inline-block;width:70px;height:6px;border-radius:3px;background:var(--panel-2);
  border:1px solid var(--rule-2);vertical-align:middle;overflow:hidden}
.sprakbar i{display:block;height:100%;background:var(--ok)}
.sprakhuvud{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin:14px 0 4px;
  padding-top:12px;border-top:1px solid var(--rule-2)}
.sprakhuvud .fldin{flex:1 1 220px;min-width:0}
/**
 * TRE KOLUMNER: svenskan som ÄR nyckeln, engelskan som redan finns, och den
 * tomma att fylla i. De två första är data — därför `data-raw` i markupen, så
 * översättningsvandraren inte översätter tabellen som visar översättningarna.
 */
table.sprakt{width:100%;table-layout:fixed;font-size:12.5px}
table.sprakt td{vertical-align:top;padding:3px 6px;border-bottom:1px solid var(--rule-2);
  overflow-wrap:anywhere}
table.sprakt td:nth-child(1){width:34%}
table.sprakt td:nth-child(2){width:28%}
table.sprakt td input{width:100%}
table.sprakt tr.foraldralos td:nth-child(1){color:var(--ink-3);font-style:italic}
table.sprakt input.bad{border-color:var(--fire);background:rgba(208,2,27,.06)}
table.sprakl td{padding:4px 8px}
@media (max-width:760px){
  /* Telefonen: nyckeln över fältet i stället för bredvid — se test/mobilen.js. */
  table.sprakt, table.sprakt tbody, table.sprakt tr, table.sprakt td{display:block;width:auto}
  table.sprakt tr{border-bottom:1px solid var(--rule-2);padding:6px 0}
  table.sprakt td{border:none;padding:2px 0}
}
/** En släckt länk ser inte tryckbar ut. Se sprakLankar i del 47. */
a.btn.av{opacity:.45;cursor:not-allowed}
/** En länk som är en knapp ska se ut som en knapp — se sprakLankar i del 47. */
a.btn{display:inline-block;text-decoration:none;color:inherit}

/* ⚙ › Språk: "Nytt språk" i en egen ruta, så att dess mall inte förväxlas med språkens egna filer (0.123.1). */
fieldset.sprakny{border:1px solid var(--rule-2);border-radius:4px;padding:8px 12px 12px;margin:14px 0 6px}
fieldset.sprakny legend{font-size:12px;font-weight:600;color:var(--ink-2);padding:0 6px}

/* ---------- huset i 3D (web/app/56-hus3d.js) ----------
   Ytan ligger över ritytan (#stage) men utanför den: fast position som
   följer ritytans rektangel, så att skena, demorad och telefonläge följer med
   utan egna regler. Under larmlisten (z 25) och panelerna, över ritytan. */
#hus3d{position:fixed;z-index:9;background:var(--sea);overflow:hidden;touch-action:none}
#hus3d canvas{display:block;width:100%;height:100%;cursor:grab}
#hus3d canvas:active{cursor:grabbing}
#hus3d .h3rad{position:absolute;left:10px;right:10px;bottom:10px;display:flex;flex-wrap:wrap;align-items:center;gap:6px 8px;pointer-events:none}
#hus3d .h3rad>*{pointer-events:auto}
#hus3d .h3namn{font-weight:600;background:var(--panel);border:1px solid var(--rule);border-radius:4px;padding:4px 9px;font-size:13px}
#hus3d .h3hjalp{flex:1;min-width:0;font-size:12px;color:var(--ink-3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;pointer-events:none}
#hus3d .btn.primary{background:var(--accent);border-color:var(--accent);color:#fff}
#hus3d .btn.primary:disabled{opacity:.55}
#hus3d .h3tip{position:absolute;pointer-events:none;background:var(--panel);border:1px solid var(--rule);border-radius:4px;
  padding:5px 8px;font-size:12px;box-shadow:0 2px 8px rgba(0,0,0,.12);white-space:nowrap;z-index:2}
#hus3d .h3msg{position:absolute;left:16px;right:16px;top:40%;margin:auto;max-width:460px;background:var(--panel);
  border:1px solid var(--rule);border-radius:6px;padding:14px 16px;font-size:13px;text-align:center}
@media (max-width:620px){#hus3d .h3hjalp{display:none}}
/* Under läget, med luft: knappen tätt intill statuschipen såg ut att höra till dem (Tomas, Solna Gate). */
#hus3dKnapp{display:block;margin-top:18px}
#hus3d .h3disc{display:inline-flex;border:1px solid var(--rule);border-radius:4px;overflow:hidden;background:var(--panel)}
#hus3d .h3disc button{border:0;background:none;padding:5px 10px;font:inherit;font-size:12px;letter-spacing:.04em;color:var(--ink-2);cursor:pointer}
#hus3d .h3disc button+button{border-left:1px solid var(--rule)}
#hus3d .h3disc button.on{background:var(--panel-2);color:var(--ink);font-weight:600}
#hus3d .h3disc button.larm::after{content:"";display:inline-block;width:7px;height:7px;border-radius:50%;background:var(--fire);margin-left:6px;vertical-align:1px}
body.hus3dvy #hint,body.hus3dvy .zoomer{display:none!important}

/* ---- provplanen (56-provplan.js) */
.pvpane .ppq{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;margin-top:6px}
@media (max-width:900px){.pvpane .ppq{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:620px){.pvpane .ppq{grid-template-columns:1fr}}
.pvpane .ppkv{border:1px solid var(--rule);border-radius:4px;padding:6px 8px;min-height:60px}
.pvpane .ppkv.nu{border-color:var(--accent,#2c5c86)}
.pvpane .ppkv-h{display:flex;gap:6px;align-items:center;margin-bottom:4px}
.pvpane .pprow{border-top:1px solid var(--rule);padding:6px 0;display:grid;gap:2px}
.pvpane .pprow .acts{display:flex;gap:4px;flex-wrap:wrap;margin-top:2px}
.pvpane .ppempty{padding:4px 0}
.pvpane .ppbox ul{margin:2px 0 6px;padding-left:18px}
.pvpane .ppfunk td{vertical-align:top}
.pvpane textarea{width:100%;box-sizing:border-box;font:inherit}
.pvpane .ppbar{position:relative;height:14px;background:var(--panel-2);border-radius:3px;overflow:hidden;font-size:11px;margin:2px 0}
.pvpane .ppbar i{position:absolute;left:0;top:0;bottom:0;background:color-mix(in srgb,var(--ok,#1d6b3f) 35%,transparent)}
.pvpane .ppbar span{position:relative;padding-left:6px;line-height:14px}
.pvpane .ppenh{max-height:320px;overflow:auto;border:1px solid var(--rule);border-radius:3px;padding:4px 8px}
.pvpane .ppenh details{margin:2px 0}
.pvpane .ppenh summary{cursor:pointer;display:flex;gap:6px;align-items:center}
.pvpane .ppenh-rader{padding:2px 0 4px 22px;display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:2px 10px}
.pvpane .ppenh .ppprovad{opacity:.75}

