/* ZCOM Plant — design tokens.
 *
 * The palette is the TIA-598 fibre colour code: blue, orange, green, brown,
 * slate, white, red, black, yellow, violet, rose, aqua. It is the vernacular
 * of this trade — every splicer knows the sequence by heart — so the splice
 * view reads like the closure they are standing in front of, not like a
 * generic chart.
 *
 * IBM Plex throughout: drawn for technical and industrial work, and it sets
 * numbers and identifiers cleanly, which is most of what this interface is.
 */

@font-face{font-family:"Plex";src:url("fonts/ibm-plex-sans-latin-400-normal.woff2") format("woff2");font-weight:400;font-display:swap}
@font-face{font-family:"Plex";src:url("fonts/ibm-plex-sans-latin-500-normal.woff2") format("woff2");font-weight:500;font-display:swap}
@font-face{font-family:"Plex";src:url("fonts/ibm-plex-sans-latin-600-normal.woff2") format("woff2");font-weight:600;font-display:swap}
@font-face{font-family:"Plex";src:url("fonts/ibm-plex-sans-latin-700-normal.woff2") format("woff2");font-weight:700;font-display:swap}
@font-face{font-family:"PlexMono";src:url("fonts/ibm-plex-mono-latin-400-normal.woff2") format("woff2");font-weight:400;font-display:swap}
@font-face{font-family:"PlexMono";src:url("fonts/ibm-plex-mono-latin-500-normal.woff2") format("woff2");font-weight:500;font-display:swap}
@font-face{font-family:"PlexMono";src:url("fonts/ibm-plex-mono-latin-600-normal.woff2") format("woff2");font-weight:600;font-display:swap}

:root{
  /* fibre colour code — the accent system */
  --f-blue:#0f62fe; --f-orange:#ff832b; --f-green:#24a148; --f-brown:#8d6e4a;
  --f-slate:#697077; --f-white:#f4f4f4; --f-red:#da1e28;   --f-black:#161616;
  --f-yellow:#f1c21b; --f-violet:#8a3ffc; --f-rose:#ee5396; --f-aqua:#08bdba;

  /* surfaces: cool graphite, the colour of a splice enclosure */
  --s-0:#0d1117; --s-1:#141a22; --s-2:#1b232d; --s-3:#232d39;
  --line:#2a3644; --line-soft:#212b36;

  --ink:#e6edf3; --ink-2:#9aa8b8; --ink-3:#6b7a8b;

  --ok:var(--f-green); --warn:var(--f-yellow); --bad:var(--f-red);
  --accent:var(--f-aqua);

  --sans:"Plex",system-ui,-apple-system,sans-serif;
  --mono:"PlexMono",ui-monospace,monospace;

  --nav-w:216px; --right-w:322px; --top-h:52px; --bar-h:30px; --tool-h:46px;
  --r:6px;

  --shadow:0 12px 40px rgba(0,0,0,.55);
  --map-bg:#0a1017;
  --label-halo:#05090e;
  --core-free:#243040;
  --core-free-bd:#2f3d4e;
}

/* Light is not the dark palette inverted. A plant record gets read on a
   laptop in a van in daylight, so it wants paper contrast — near-white
   surfaces, ink-dark text, and the fibre colours kept saturated because they
   are the one thing that must not drift. */
:root[data-theme="light"]{
  --s-0:#f5f7fa; --s-1:#ffffff; --s-2:#eef2f7; --s-3:#e2e8f0;
  --line:#cfd8e3; --line-soft:#e4eaf1;
  --ink:#16212e; --ink-2:#46586c; --ink-3:#6b7c90;
  --accent:#0369a1;
  --ok:#177245; --warn:#a16207; --bad:#b91c1c;
  --shadow:0 10px 30px rgba(15,30,50,.16);
  --map-bg:#eef2f6;
  --label-halo:#ffffff;
  --core-free:#dbe3ec;
  --core-free-bd:#c3cedb;
}

*{box-sizing:border-box}
html,body{margin:0;height:100%;overflow:hidden;background:var(--s-0);
  color:var(--ink);font:14px/1.45 var(--sans);
  -webkit-font-smoothing:antialiased}
button,input,select{font-family:inherit}
:focus-visible{outline:2px solid var(--accent);outline-offset:1px}
/* Except on the map. An outline on an SVG path is drawn around the path's
 * BOUNDING BOX, so clicking a cable that runs diagonally across the city
 * boxed half the city — which read as a selection rectangle nobody had
 * drawn. Keyboard focus still has to be visible, so it becomes a glow that
 * follows the line itself.
 */
.leaflet-interactive:focus{outline:none}
.leaflet-interactive:focus-visible{outline:none;
  filter:drop-shadow(0 0 2px var(--accent)) drop-shadow(0 0 5px var(--accent))}
@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}

/* ---------------------------------------------------------------- shell */
#app{display:grid;height:100%;
  grid-template-columns:var(--nav-w) 1fr;
  grid-template-rows:var(--top-h) 1fr var(--bar-h);
  grid-template-areas:"brand top" "nav main" "nav bar"}

#brand{grid-area:brand;display:flex;align-items:center;gap:9px;
  padding:0 16px;background:var(--s-1);border-right:1px solid var(--line);
  border-bottom:1px solid var(--line)}
#brand .mk{width:15px;height:15px;flex:none;border-radius:2px;
  background:
    linear-gradient(var(--f-blue) 0 25%, var(--f-orange) 25% 50%,
                    var(--f-green) 50% 75%, var(--f-brown) 75% 100%)}
#brand b{font:600 14px/1 var(--sans);letter-spacing:-.01em}
#brand span{font:400 9.5px/1 var(--mono);color:var(--ink-3);
  letter-spacing:.1em;text-transform:uppercase;margin-top:2px;display:block}

#top{grid-area:top;display:flex;align-items:center;gap:12px;padding:0 14px;
  background:var(--s-1);border-bottom:1px solid var(--line)}
#top h1{margin:0;font:600 15px/1 var(--sans);letter-spacing:-.01em}
#top .sub{font:400 10.5px/1 var(--mono);color:var(--ink-3);margin-top:4px}
.spacer{flex:1}

/* the two worlds. Layout is where you record the plant; Monitor is where you
   watch it. They never share a toolbar, because editing cable while chasing
   an outage is how mistakes get made. */
#modes{display:flex;background:var(--s-0);border:1px solid var(--line);
  border-radius:var(--r);padding:2px;gap:2px}
#modes button{background:none;border:0;color:var(--ink-2);cursor:pointer;
  padding:6px 15px;border-radius:4px;font:500 12.5px/1 var(--sans)}
#modes button[aria-pressed="true"]{background:var(--s-3);color:var(--ink);
  box-shadow:inset 0 0 0 1px var(--line)}

#nav{grid-area:nav;background:var(--s-1);border-right:1px solid var(--line);
  padding:12px 9px;overflow:auto}
#nav .grp{font:600 9.5px/1 var(--mono);letter-spacing:.12em;color:var(--ink-3);
  text-transform:uppercase;padding:12px 8px 6px}
#nav button{display:flex;align-items:center;gap:9px;width:100%;background:none;
  border:0;color:var(--ink-2);cursor:pointer;padding:7px 9px;border-radius:5px;
  font:400 13px/1.2 var(--sans);text-align:left}
#nav button:hover{background:var(--s-2);color:var(--ink)}
#nav button[aria-current="true"]{background:var(--s-3);color:var(--ink);
  font-weight:500;box-shadow:inset 2px 0 0 var(--accent)}
#nav .ct{margin-left:auto;font:500 10.5px/1 var(--mono);color:var(--ink-3)}
#nav .ct.bad{color:var(--bad)}
#nav .ct.warn{color:var(--warn)}

/* Rows are placed explicitly. Relying on auto-placement meant that hiding the
   toolbar on a screen with no tools promoted #views into row 1 — a fixed 46px
   — and clipped the entire page to a sliver. */
#main{grid-area:main;position:relative;display:grid;
  grid-template-rows:auto 1fr;min-width:0;min-height:0}

#tools{grid-row:1;display:flex;align-items:center;gap:7px;padding:0 12px;
  height:var(--tool-h);background:var(--s-1);
  border-bottom:1px solid var(--line);overflow-x:auto;scrollbar-width:thin}
/* Screens without tools collapse the bar instead of hiding the element, so
   the grid keeps its shape. */
#tools:empty{height:0;padding:0;border-bottom:0}
#tools .lbl{font:600 9.5px/1 var(--mono);letter-spacing:.11em;color:var(--ink-3);
  text-transform:uppercase;margin-right:2px;white-space:nowrap}
#tools .sep{width:1px;height:20px;background:var(--line);flex:none;margin:0 3px}

.btn{background:var(--s-2);color:var(--ink);border:1px solid var(--line);
  border-radius:5px;padding:6px 11px;font:500 12px/1 var(--sans);cursor:pointer;
  white-space:nowrap;display:inline-flex;align-items:center;gap:6px}
.btn:hover{border-color:var(--ink-3);background:var(--s-3)}
.btn[aria-pressed="true"]{background:color-mix(in srgb,var(--accent) 18%,var(--s-2));
  border-color:var(--accent);color:var(--accent)}
.btn.pri{background:color-mix(in srgb,var(--accent) 16%,var(--s-2));
  border-color:color-mix(in srgb,var(--accent) 45%,var(--line));color:var(--accent)}
.btn.danger:hover{border-color:var(--bad);color:var(--bad)}
.btn:disabled{opacity:.4;cursor:not-allowed}
.btn.sm{padding:4px 8px;font-size:11px}

#canvas{position:relative;min-height:0}
#map{position:absolute;inset:0;background:var(--map-bg)}
.leaflet-container{background:var(--map-bg);font-family:var(--sans)}
.leaflet-control-attribution{background:color-mix(in srgb,var(--s-1) 82%,transparent)!important;
  color:var(--ink-3)!important;font-size:9px!important}
.leaflet-bar a{background:var(--s-2)!important;color:var(--ink)!important;
  border-color:var(--line)!important}

#bar{grid-area:bar;display:flex;align-items:center;gap:0;padding:0 14px;
  background:var(--s-1);border-top:1px solid var(--line);
  font:500 10.5px/1 var(--mono);color:var(--ink-3);overflow-x:auto}
#bar .m{display:flex;align-items:baseline;gap:6px;padding:0 14px;
  border-right:1px solid var(--line-soft);white-space:nowrap}
#bar .m:first-child{padding-left:0}
#bar .m b{color:var(--ink);font-weight:600;font-size:11.5px}
#bar .m.live b{color:var(--ok)}
#bar .m.stale b{color:var(--warn)}
#bar .m.bad b{color:var(--bad)}

/* ---------------------------------------------------------------- panels */
#right{position:absolute;top:calc(var(--tool-h) + 10px);right:10px;
  width:var(--right-w);max-height:calc(100% - var(--tool-h) - 20px);
  display:none;flex-direction:column;z-index:600;
  background:color-mix(in srgb,var(--s-1) 94%,transparent);
  border:1px solid var(--line);border-radius:8px;
  box-shadow:var(--shadow);backdrop-filter:blur(8px)}
#right.on{display:flex}
#right header{padding:11px 13px;border-bottom:1px solid var(--line);
  display:flex;align-items:flex-start;gap:8px}
#right header h2{margin:0;font:600 14px/1.25 var(--sans)}
#right header .k{font:600 9.5px/1 var(--mono);letter-spacing:.11em;
  text-transform:uppercase;color:var(--ink-3);margin-bottom:4px}
#right header .x{margin-left:auto;background:none;border:0;color:var(--ink-3);
  cursor:pointer;font-size:15px;line-height:1;padding:2px 4px}
#right header .x:hover{color:var(--ink)}
#rbody{overflow:auto;padding:11px 13px}
#rfoot{border-top:1px solid var(--line);padding:10px 13px}

.field{margin-bottom:9px}
.field label{display:block;font:500 10.5px/1 var(--mono);letter-spacing:.06em;
  color:var(--ink-3);text-transform:uppercase;margin-bottom:5px}
.field input,.field select,.field textarea{width:100%;background:var(--s-0);
  color:var(--ink);border:1px solid var(--line);border-radius:5px;
  padding:7px 9px;font:400 12.5px/1.3 var(--sans)}
.field input:focus,.field select:focus{outline:none;border-color:var(--accent)}
.pair{display:flex;gap:7px}
.row{display:flex;gap:7px;margin-top:9px;flex-wrap:wrap}
/* Shared width only where the row is narrow: in the side panel and dialogs
   a pair of buttons reads as a pair. On a full-width page the same rule
   stretched "Terminate" across half the screen. */
#right .row .btn,.modal .row .btn{flex:1}

.tag{display:inline-block;font:600 9.5px/1 var(--mono);letter-spacing:.07em;
  text-transform:uppercase;padding:3px 6px;border-radius:3px;
  background:var(--s-3);color:var(--ink-2)}
.tag.ok{background:color-mix(in srgb,var(--ok) 18%,transparent);color:var(--ok)}
.tag.warn{background:color-mix(in srgb,var(--warn) 18%,transparent);color:var(--warn)}
.tag.bad{background:color-mix(in srgb,var(--bad) 20%,transparent);color:var(--bad)}

.kv{display:flex;justify-content:space-between;gap:14px;
  font:400 12px/1.9 var(--mono);color:var(--ink-2)}
.kv b{color:var(--ink);font-weight:500}

h4.sec{margin:15px 0 7px;font:600 9.5px/1 var(--mono);letter-spacing:.12em;
  text-transform:uppercase;color:var(--ink-3);
  display:flex;align-items:center;gap:8px}
h4.sec::after{content:"";flex:1;height:1px;background:var(--line-soft)}
h4.sec:first-child{margin-top:0}

/* ------------------------------------------------- signature: splice view
 * Cores drawn in their real jacket colours, in buffer-tube groups of twelve.
 * A splicer reads this the way they read the closure: tube, then position.
 */
.tube{margin-bottom:9px}
.tube .th{font:500 10.5px/1 var(--mono);color:var(--ink-3);margin-bottom:4px;
  display:flex;justify-content:space-between}
.cores{display:grid;grid-template-columns:repeat(12,1fr);gap:2px}
.core{aspect-ratio:1;border-radius:2px;position:relative;cursor:pointer;
  border:1px solid rgba(0,0,0,.45)}
.core.free{opacity:.24}
.core.free::after{content:"";position:absolute;inset:0;
  background:repeating-linear-gradient(45deg,transparent 0 2px,rgba(0,0,0,.5) 2px 4px)}
.core:hover{transform:scale(1.35);z-index:5;box-shadow:0 0 0 1px var(--ink)}
.legend{display:flex;gap:9px;flex-wrap:wrap;font:400 10px/1 var(--mono);
  color:var(--ink-3);margin-top:7px}
.legend i{width:8px;height:8px;border-radius:2px;display:inline-block;
  margin-right:4px;vertical-align:-1px}

.alloc{display:flex;justify-content:space-between;gap:8px;align-items:center;
  font:400 11.5px/1.5 var(--mono);padding:5px 7px;border-radius:4px;
  background:var(--s-2);margin-bottom:4px}
.alloc button{background:none;border:0;color:var(--ink-3);cursor:pointer;
  padding:0 3px;font-size:12px}
.alloc button:hover{color:var(--bad)}

.card{border:1px solid var(--line);border-left-width:3px;border-radius:6px;
  padding:9px 10px;margin-bottom:7px;background:var(--s-2)}
.card.P1{border-left-color:var(--bad)}
.card.P2{border-left-color:var(--warn)}
.card .t{font:600 12.5px/1.35 var(--sans)}
.card .d{font:400 11px/1.5 var(--mono);color:var(--ink-3);margin-top:4px}
.card .loc{font:400 11.5px/1.45 var(--sans);color:var(--accent);margin-top:6px;
  padding-left:8px;border-left:2px solid color-mix(in srgb,var(--accent) 45%,transparent)}

#alarm{position:absolute;top:0;left:0;right:0;z-index:800;
  padding:8px 16px;background:linear-gradient(90deg,#6b1218,#8c1a22);
  color:#fff;font:600 12.5px/1.3 var(--sans);letter-spacing:.01em;display:none}
#alarm.on{display:block}

#hint{position:absolute;top:12px;left:50%;transform:translateX(-50%);z-index:700;
  padding:8px 15px;border-radius:6px;font:500 12px/1 var(--sans);display:none;
  background:color-mix(in srgb,var(--accent) 15%,var(--s-1));
  border:1px solid color-mix(in srgb,var(--accent) 50%,var(--line));
  color:var(--accent)}
#hint.on{display:block}

#toast{position:absolute;bottom:16px;left:50%;transform:translateX(-50%);
  z-index:850;padding:9px 15px;border-radius:6px;font:400 12.5px/1.35 var(--sans);
  background:var(--s-2);border:1px solid var(--line);opacity:0;max-width:66%;
  transition:opacity .22s;pointer-events:none;box-shadow:var(--shadow)}
#toast.show{opacity:1}
#toast.err{border-color:var(--bad);color:#ffb3b0}

#menu{position:absolute;z-index:900;min-width:206px;padding:5px;display:none;
  background:var(--s-1);border:1px solid var(--line);border-radius:7px;
  box-shadow:var(--shadow)}
#menu.on{display:block}
#menu .hd{padding:6px 9px 7px;font:600 9.5px/1 var(--mono);letter-spacing:.1em;
  text-transform:uppercase;color:var(--ink-3);border-bottom:1px solid var(--line);
  margin-bottom:4px}
#menu button{display:block;width:100%;text-align:left;background:none;border:0;
  color:var(--ink);padding:7px 9px;border-radius:5px;cursor:pointer;
  font:400 12.5px/1.2 var(--sans)}
#menu button:hover{background:var(--s-3);color:var(--accent)}
#menu button.danger:hover{color:var(--bad)}
#menu .note{padding:6px 9px 4px;color:var(--ink-3);font:400 10.5px/1.45 var(--sans)}

/* map markers */
.mk-pop,.mk-pt,.mk-cl,.vtx,.mk-fault{background:none!important;border:none!important}
/* The estimated break. Deliberately loud and deliberately not the same
   shape as anything recorded: this is a calculated guess from an
   instrument reading, not a surveyed feature of the plant. */
.mk-fault i{position:absolute;width:16px;height:16px;left:-8px;top:-8px;
  border-radius:50%;background:var(--bad);border:2px solid var(--label-halo);
  box-shadow:0 0 0 6px color-mix(in srgb,var(--bad) 30%,transparent);
  animation:pl 1.15s ease-in-out infinite}
.mk-fault b{position:absolute;left:14px;top:-9px;white-space:nowrap;
  font:700 11px/1 var(--mono);color:var(--bad);
  text-shadow:0 0 4px var(--label-halo),0 0 3px var(--label-halo),
              0 0 2px var(--label-halo)}
/* Status carries three cues, not one: colour, SHAPE, and a glyph on the
 * label. Colour alone fails for a colour-blind operator and washes out on a
 * projector, which is exactly where this map lives. Shape survives both.
 *   up        filled circle
 *   degraded  diamond
 *   down      square, pulsing
 *   unknown   hollow dashed ring
 */
.mk-pop i{position:absolute;width:12px;height:12px;left:-6px;top:-6px;
  border-radius:50%;background:var(--ok);border:2px solid var(--label-halo)}
.mk-pop.st-degraded i{background:var(--warn);border-radius:2px;
  transform:rotate(45deg)}
.mk-pop.st-down i{background:var(--bad);border-radius:2px;
  box-shadow:0 0 0 5px rgba(218,30,40,.22);
  animation:pl 1.15s ease-in-out infinite}
.mk-pop.st-unknown i,.mk-pop.st-stale i{background:var(--label-halo);
  border:2px dashed var(--ink-3)}
.mk-pop.sel i{box-shadow:0 0 0 5px color-mix(in srgb,var(--accent) 38%,transparent)}
@keyframes pl{0%,100%{opacity:1}50%{opacity:.35}}
/* The glyph rides in the label so it inherits the halo and never sits under
 * a marker. Up gets none — the common case should stay quiet. */
.mk-pop b .g{font-weight:700;margin-right:2px}
.mk-pop.st-degraded b .g{color:var(--warn)}
.mk-pop.st-down b .g{color:var(--bad)}
.mk-pop.st-unknown b .g,.mk-pop.st-stale b .g{color:var(--ink-3)}
/* A label suppressed for collision, not deleted. Hover brings it back, so a
 * joint whose name is hidden is still one mouse-over from being readable
 * without zooming in. */
.mk-pt b.lbl-off,.mk-pop b.lbl-off{display:none}
.mk-pt:hover b.lbl-off,.mk-pop:hover b.lbl-off{display:block;z-index:900}

/* The status legend needs dot-shaped swatches, not the 16x3 line used for
 * cable types, or the shape it is teaching is not the shape on the map. */
#legend i.st{width:10px;height:10px;border-radius:50%;vertical-align:-1px;
  margin-right:7px}
#legend i.st.sh-degraded{border-radius:2px;transform:rotate(45deg)}
#legend i.st.sh-down{border-radius:2px}
#legend i.st.sh-unknown{background:transparent;border:2px dashed var(--ink-3);
  box-sizing:border-box}
#legend b{font:700 10.5px/1 var(--mono)}
#legend .lgnote{color:var(--ink-3);font-size:9.5px}
.mk-pop b{position:absolute;left:11px;top:-8px;white-space:nowrap;
  font:600 10.5px/1 var(--mono);color:var(--ink);
  text-shadow:0 0 4px var(--label-halo),0 0 3px var(--label-halo),
              0 0 2px var(--label-halo)}
.mk-pt i{position:absolute;width:9px;height:9px;left:-4.5px;top:-4.5px;
  transform:rotate(45deg);background:var(--f-orange);
  border:1.5px solid var(--label-halo)}
/* One marker per point kind (v1.76), by family: splices are diamonds
   (orange t-joint, yellow closure), underground access is aqua (square
   handhole, round manhole, diamond chamber), distribution is green (square
   FDT, round FAT), indoors is violet (square cabinet, larger square
   building). A slack loop is a brown ring, the coil seen from above; a
   pole a slate dot. Shape and colour both differ, so no kind depends on
   colour alone. The legend draws the same classes. */
.mk-pt.k-splice_closure i{background:var(--f-yellow)}
.mk-pt.k-handhole i{background:var(--f-aqua);transform:none;border-radius:1px}
.mk-pt.k-manhole i{background:var(--f-aqua);transform:none;border-radius:50%}
.mk-pt.k-chamber i{background:var(--f-aqua)}
.mk-pt.k-slack_loop i{background:transparent;border:2.5px solid var(--f-brown);
  border-radius:50%;transform:none;
  box-shadow:0 0 0 1.5px var(--label-halo),inset 0 0 0 1px var(--label-halo)}
.mk-pt.k-pole i{background:var(--f-slate);border-radius:50%;transform:none;
  width:7px;height:7px;left:-3.5px;top:-3.5px}
.mk-pt.k-cabinet i{background:var(--f-violet);transform:none}
.mk-pt.k-building i{background:var(--f-violet);transform:none;
  width:11px;height:11px;left:-5.5px;top:-5.5px}
.mk-pt.k-fdt i{background:var(--f-green);transform:none}
.mk-pt.k-fat i{background:var(--f-green);transform:none;border-radius:50%}
.mk-pt.k-other i{background:var(--ink-3);transform:none;border-radius:50%}
/* The same markers as legend swatches: in the flow, not pinned at 0,0. */
#legend .mk-pt{position:relative;display:inline-block;width:12px;height:12px;
  margin-right:7px;vertical-align:-2px}
#legend .mk-pt i{left:50%;top:50%;margin:-4.5px 0 0 -4.5px}
#legend .mk-pt.k-pole i{margin:-3.5px 0 0 -3.5px}
#legend .mk-pt.k-building i{margin:-5.5px 0 0 -5.5px}
.mk-pt b{position:absolute;left:9px;top:-7px;white-space:nowrap;
  font:500 9.5px/1 var(--mono);color:var(--f-brown);
  text-shadow:0 0 4px var(--label-halo),0 0 2px var(--label-halo)}
.mk-cl i{position:absolute;width:7px;height:7px;left:-3.5px;top:-3.5px;
  background:var(--ink-3);border:1.5px solid var(--label-halo)}
.mk-cl b{position:absolute;left:9px;top:-6px;white-space:nowrap;
  font:400 9px/1 var(--mono);color:var(--ink-2);
  text-shadow:0 0 4px var(--label-halo),0 0 2px var(--label-halo)}
.vtx i{position:absolute;width:9px;height:9px;left:-4.5px;top:-4.5px;
  border-radius:50%;background:var(--accent);border:1.5px solid var(--label-halo)}
.vtx.mid i{width:7px;height:7px;left:-3.5px;top:-3.5px;background:var(--ink-3)}

#legend{position:absolute;left:10px;bottom:10px;z-index:600;padding:9px 11px;
  background:color-mix(in srgb,var(--s-1) 92%,transparent);
  border:1px solid var(--line);border-radius:7px;font:400 10.5px/1.7 var(--mono);
  color:var(--ink-2);backdrop-filter:blur(6px)}
#legend .t{font:600 9.5px/1 var(--mono);letter-spacing:.11em;color:var(--ink-3);
  text-transform:uppercase;margin-bottom:6px}
#legend > div > i{width:16px;height:3px;display:inline-block;margin-right:7px;
  vertical-align:2px;border-radius:2px}

/* The top bar is one row that does not wrap. From v1.71 it also carries
   who is signed in and Sign out, which pushed it past the width of a
   1366-pixel laptop screen. Tighten it rather than hide any control: the
   title block may shrink (its subtitle ellipsised), the role label goes,
   the coordinate box narrows. */
#top > div:first-child{min-width:0;flex:0 1 auto}
#top .sub{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
@media (max-width:1500px){
  #top{gap:8px}
  .gotobar{flex-basis:150px;margin-left:4px}
  .gsearch{margin-left:6px}
  #whoRole{display:none}
  #top .btn{padding:6px 9px}
}

@media (max-width:1100px){
  :root{--nav-w:58px;--right-w:290px}
  #nav button span,#nav .grp,#brand b,#brand span{display:none}
  #nav button{justify-content:center}
}


/* ---------------------------------------------------------------- views
 * Each nav entry is a real screen, not a panel on the map. Map views get a
 * map; data views get a table. Mixing them on one canvas is what made the
 * previous build unreadable.
 */
#views{grid-row:2;position:relative;min-height:0;overflow:hidden}
.view{position:absolute;inset:0;display:none;overflow:auto}
.view.on{display:block}
.view.map-view{overflow:hidden}

.sheet{padding:16px 18px;max-width:1500px}
.sheet h3{margin:0 0 3px;font:600 16px/1.25 var(--sans);letter-spacing:-.01em}
.sheet .lede{font:400 12.5px/1.5 var(--sans);color:var(--ink-3);margin-bottom:16px;
  max-width:62ch}

table.tbl{width:100%;border-collapse:collapse;font:400 12.5px/1.4 var(--sans)}
table.tbl th{position:sticky;top:0;z-index:2;text-align:left;background:var(--s-2);
  color:var(--ink-3);font:600 9.5px/1 var(--mono);letter-spacing:.11em;
  text-transform:uppercase;padding:9px 10px;border-bottom:1px solid var(--line);
  cursor:pointer;white-space:nowrap;user-select:none}
table.tbl th:hover{color:var(--ink)}
table.tbl th[data-dir]::after{content:" ▾";color:var(--accent)}
table.tbl th[data-dir="asc"]::after{content:" ▴";color:var(--accent)}
table.tbl td{padding:8px 10px;border-bottom:1px solid var(--line-soft);
  vertical-align:top}
table.tbl tbody tr:hover{background:var(--s-1)}
table.tbl td.num,table.tbl td.mono{font-family:var(--mono);font-size:12px}
table.tbl td.num{text-align:right;font-variant-numeric:tabular-nums}
table.tbl .muted{color:var(--ink-3)}

.toolrow{display:flex;gap:8px;align-items:center;margin-bottom:12px;flex-wrap:wrap}
.search{background:var(--s-0);border:1px solid var(--line);border-radius:5px;
  padding:7px 10px;color:var(--ink);font:400 12.5px var(--sans);min-width:230px}
.search:focus{outline:none;border-color:var(--accent)}

.tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(168px,1fr));
  gap:11px;margin-bottom:20px}
.tile{background:var(--s-1);border:1px solid var(--line);border-radius:8px;
  padding:13px 14px}
.tile .k{font:600 9.5px/1 var(--mono);letter-spacing:.11em;text-transform:uppercase;
  color:var(--ink-3)}
.tile .v{font:600 25px/1.1 var(--sans);letter-spacing:-.02em;margin-top:7px;
  font-variant-numeric:tabular-nums}
.tile .n{font:400 11px/1.4 var(--mono);color:var(--ink-3);margin-top:4px}
.tile.bad .v{color:var(--bad)}
.tile.ok .v{color:var(--ok)}
.tile.warn .v{color:var(--warn)}

/* utilisation bar — reads left to right like a fill gauge */
.util{position:relative;height:15px;background:var(--s-0);border-radius:3px;
  overflow:hidden;min-width:92px;border:1px solid var(--line-soft)}
.util i{position:absolute;inset:0 auto 0 0;border-radius:2px}
.util b{position:absolute;inset:0;display:flex;align-items:center;
  justify-content:flex-end;padding-right:5px;font:600 9.5px/1 var(--mono);
  color:var(--ink)}

.sev{font:600 9.5px/1 var(--mono);letter-spacing:.06em;text-transform:uppercase;
  padding:2px 5px;border-radius:3px;background:var(--s-3);color:var(--ink-2)}
.sev.err{background:color-mix(in srgb,var(--bad) 20%,transparent);color:#ff9d99}
.sev.warn{background:color-mix(in srgb,var(--warn) 20%,transparent);color:var(--warn)}

.empty{padding:44px 20px;text-align:center;color:var(--ink-3)}
.empty h4{margin:0 0 6px;font:600 14px/1.3 var(--sans);color:var(--ink-2)}
.empty p{margin:0;font:400 12.5px/1.6 var(--sans);max-width:46ch;
  margin-inline:auto}


/* ------------------------------------------------- ODF front panel
 * Laid out the way the panel is: rows of adapters, split into 12-port blocks,
 * numbered left to right and top to bottom. An engineer counting across the
 * frame should be counting the same squares.
 */
.panel-odf{background:var(--s-0);border:1px solid var(--line);border-radius:5px;
  padding:9px 10px;margin-top:7px}
.panel-odf .prow{display:flex;gap:9px;margin-bottom:6px;align-items:center}
.panel-odf .prow:last-child{margin-bottom:0}
.panel-odf .rlab{font:600 9px/1 var(--mono);color:var(--ink-3);width:16px;
  flex:none;text-align:right}
.panel-odf .colhdr{margin-bottom:2px}
.panel-odf .colname{flex:none;text-align:center;font:700 9px/1 var(--mono);
  color:var(--ink-3)}
.panel-odf .blk{display:flex;gap:2px;padding:3px;border-radius:3px;
  background:rgba(255,255,255,.035);border:1px solid var(--line-soft)}
.panel-odf .ad{width:13px;height:19px;border-radius:2px;position:relative;
  cursor:pointer;border:1px solid rgba(0,0,0,.55);
  display:flex;align-items:flex-end;justify-content:center}
.panel-odf .ad span{font:600 6.5px/1 var(--mono);color:rgba(0,0,0,.6);
  padding-bottom:1px}
.panel-odf .ad.free{background:var(--core-free)!important;
  border-color:var(--core-free-bd)}
.panel-odf .ad.free span{color:var(--ink-3)}
.panel-odf .ad:hover{outline:2px solid var(--accent);outline-offset:1px;z-index:3}
.panel-odf .cap{font:400 10px/1.5 var(--mono);color:var(--ink-3);margin-top:7px;
  display:flex;justify-content:space-between;gap:12px}

/* ------------------------------------------------- switch faceplate
 * Ports laid out as two staggered rows the way a 1U switch is built, with the
 * uplink cage group separated. Colour is state, so a glance at the screen and
 * a glance at the front of the box agree.
 */
.faceplate{background:var(--s-0);border:1px solid var(--line);border-radius:5px;
  padding:9px 10px;margin-top:6px}
.faceplate .grp{display:flex;gap:9px;align-items:flex-start;flex-wrap:wrap}
.faceplate .bank{display:flex;flex-direction:column;gap:2px}
.faceplate .brow{display:flex;gap:2px}
.faceplate .glabel{font:600 8.5px/1 var(--mono);color:var(--ink-3);
  letter-spacing:.08em;text-transform:uppercase;margin-bottom:4px}
.pt{width:15px;height:11px;border-radius:2px;cursor:grab;position:relative;
  border:1px solid rgba(0,0,0,.35);background:var(--core-free)}
.pt.up{background:var(--ok)}
.pt.down{background:var(--bad)}
.pt.shut{background:var(--ink-3)}
.pt.free{background:var(--core-free);border-style:dashed;
  border-color:var(--core-free-bd)}
.pt.patched::after{content:"";position:absolute;right:1px;bottom:1px;
  width:3px;height:3px;border-radius:50%;background:#04070c}
.pt:hover{outline:2px solid var(--accent);outline-offset:1px;z-index:4}
.pt.dragging{opacity:.45}
.pt.big{width:23px;height:13px}
.drop-ok{outline:2px dashed var(--accent)!important;outline-offset:2px}
.faceplate .cap{font:400 10px/1.5 var(--mono);color:var(--ink-3);margin-top:8px;
  display:flex;gap:14px;flex-wrap:wrap}
.faceplate .cap i{width:9px;height:7px;border-radius:2px;display:inline-block;
  margin-right:5px;vertical-align:0}

.panel-odf .ad.dropzone{outline:2px dashed var(--accent);outline-offset:1px}
.patchlist{font:400 11px/1.7 var(--mono);color:var(--ink-2);margin-top:8px}
.patchlist .pr{display:flex;justify-content:space-between;gap:10px;
  padding:3px 6px;border-radius:3px}
.patchlist .pr:hover{background:var(--s-2)}
.patchlist button{background:none;border:0;color:var(--ink-3);cursor:pointer;
  font-size:11px;padding:0 3px}
.patchlist button:hover{color:var(--bad)}


/* ---------------------------------------------------------------- theme */
#theme{background:none;border:1px solid var(--line);border-radius:var(--r);
  color:var(--ink-2);cursor:pointer;padding:6px 10px;font:500 12px/1 var(--sans);
  display:flex;align-items:center;gap:6px}
#theme:hover{color:var(--ink);border-color:var(--ink-3)}
#theme .ico{width:12px;height:12px;border-radius:50%;border:1.5px solid currentColor;
  background:linear-gradient(90deg,currentColor 0 50%,transparent 50% 100%)}
:root[data-theme="light"] .core.free::after{
  background:repeating-linear-gradient(45deg,transparent 0 2px,
    rgba(0,0,0,.22) 2px 4px)}
:root[data-theme="light"] #alarm{
  background:linear-gradient(90deg,#b91c1c,#991b1b)}
:root[data-theme="light"] .pt.patched::after{background:rgba(255,255,255,.9)}
:root[data-theme="light"] .panel-odf .ad span{color:rgba(0,0,0,.55)}


/* All three map-style pickers in the top bar look alike. Only #basemap
   was styled; the two line pickers added later rendered as bare browser
   selects beside it. */
#basemap,#cablewidth,#cabledash{background:var(--s-0);color:var(--ink-2);
  border:1px solid var(--line);border-radius:var(--r);padding:6px 9px;
  font:500 12px/1 var(--sans);cursor:pointer}
#basemap:hover,#cablewidth:hover,#cabledash:hover{color:var(--ink);
  border-color:var(--ink-3)}
/* Satellite needs a darker halo behind labels or names vanish over rooftops */
.leaflet-container .mk-pop b,.leaflet-container .mk-pt b,
.leaflet-container .mk-cl b{paint-order:stroke}

/* ---------------------------------------------------------------- modal
 * A real form for anything with more than two fields. Chained prompt()
 * dialogs cannot be reviewed before submitting, cannot be corrected without
 * starting over, and give no room to explain a field.
 */
#scrim{position:fixed;inset:0;z-index:1000;display:none;
  background:rgba(4,8,14,.62);backdrop-filter:blur(3px)}
#scrim.on{display:flex;align-items:flex-start;justify-content:center;
  padding:6vh 16px}
.modal{background:var(--s-1);border:1px solid var(--line);border-radius:10px;
  width:100%;max-width:560px;box-shadow:var(--shadow);max-height:86vh;
  display:flex;flex-direction:column}
.modal header{padding:15px 18px 12px;border-bottom:1px solid var(--line)}
.modal header h2{margin:0;font:600 16px/1.2 var(--sans);letter-spacing:-.01em}
.modal header p{margin:6px 0 0;font:400 12.5px/1.55 var(--sans);
  color:var(--ink-3)}
.modal .body{padding:15px 18px;overflow:auto}
.modal footer{padding:12px 18px;border-top:1px solid var(--line);
  display:flex;gap:8px;justify-content:flex-end;align-items:center}
.modal footer .msg{margin-right:auto;font:400 12px/1.4 var(--sans);
  color:var(--bad)}
.frow{display:grid;grid-template-columns:150px 1fr;gap:12px;
  align-items:start;margin-bottom:12px}
.frow > label{font:500 12.5px/1.5 var(--sans);color:var(--ink-2);
  padding-top:7px;text-align:right}
.frow .hint{grid-column:2;font:400 11.5px/1.5 var(--sans);color:var(--ink-3);
  margin-top:-6px}
.frow input,.frow select{width:100%;background:var(--s-0);color:var(--ink);
  border:1px solid var(--line);border-radius:5px;padding:8px 10px;
  font:400 13px/1.3 var(--sans)}
.frow input:focus,.frow select:focus{outline:none;border-color:var(--accent)}
.frow .inline{display:flex;gap:8px}
.fsec{margin:16px 0 12px;padding:9px 12px;border-radius:6px;
  background:var(--s-2);font:500 12px/1.4 var(--sans);color:var(--ink-2)}
/* The class goes on #modal itself; ".modal .busy" never matched, so
   nothing stopped a second click on Save while the first was in flight.
   Only the primary button: Cancel and Escape must keep working. */
.modal.busy footer .btn.pri{opacity:.5;pointer-events:none}

/* ------------------------------------------------- device table
 * Denser than a plain grid: a status stripe reads at a glance from across a
 * room, and stacking name over address halves the width without losing
 * either. Vendor is a coloured monogram rather than a logo — the point is to
 * tell platforms apart at speed, and reproducing trademarks is not needed
 * for that.
 */
table.tbl tr.dev td{padding:9px 10px}
table.tbl tr.dev td:first-child{position:relative;padding-left:16px}
table.tbl tr.dev td:first-child::before{content:"";position:absolute;
  left:0;top:6px;bottom:6px;width:3px;border-radius:2px;background:var(--ink-3)}
table.tbl tr.dev.up td:first-child::before{background:var(--ok)}
table.tbl tr.dev.down td:first-child::before{background:var(--bad)}
table.tbl tr.dev.paused td:first-child::before{
  background:repeating-linear-gradient(180deg,var(--ink-3) 0 3px,transparent 3px 6px)}

.vend{width:26px;height:26px;border-radius:6px;display:inline-flex;
  align-items:center;justify-content:center;font:700 10px/1 var(--mono);
  letter-spacing:.02em;color:#fff;flex:none}
.dcell{display:flex;align-items:center;gap:9px}
.dcell .n{font:600 13px/1.25 var(--sans)}
.dcell .a{font:400 11px/1.3 var(--mono);color:var(--ink-3);margin-top:2px}
.dcell .acts{margin-left:auto;display:flex;gap:2px;opacity:0;
  transition:opacity .12s}
table.tbl tr:hover .dcell .acts{opacity:1}
.dcell .acts button{background:none;border:0;color:var(--ink-3);cursor:pointer;
  font:12px/1 var(--sans);padding:3px 5px;border-radius:4px}
.dcell .acts button:hover{background:var(--s-3);color:var(--ink)}
.dcell .acts button.rm:hover{color:var(--bad)}

.two{line-height:1.3}
.two b{display:block;font:500 12.5px/1.35 var(--sans)}
.two span{display:block;font:400 10.5px/1.4 var(--mono);color:var(--ink-3)}

.mini{display:flex;align-items:center;gap:6px}
.mini .bar{position:relative;height:6px;flex:1;min-width:54px;border-radius:3px;
  background:var(--s-3);overflow:hidden}
.mini .bar i{position:absolute;inset:0 auto 0 0;border-radius:3px}
.mini .pc{font:500 10.5px/1 var(--mono);color:var(--ink-2);min-width:30px;
  text-align:right}

/* Cassette rows: one fibre per line, because a splicer standing at the tray
   finds a core by colour and position, not by reading a range like
   "1-12 to 1-12". Dark cores stay visible but recede — an unspliced fibre
   is spare capacity, not an error. */
/* "Your page is older than the server". Fixed to the bottom and loud: it
   is telling somebody that what they are looking at is not what is running,
   which is worth interrupting for. */
.stale-bar{position:fixed;left:0;right:0;bottom:var(--hostbar-h,0);z-index:2000;
  display:flex;align-items:center;gap:12px;padding:10px 14px;
  background:var(--warn);color:#1c1c1c;
  font:500 12.5px/1.5 var(--sans);
  box-shadow:0 -2px 12px rgba(0,0,0,.25)}
.stale-bar span{flex:1}
/* The server itself is in trouble (disk, WAL archive, backups). Red, and
   above everything including the wall: a full disk stops the database. */
.host-bar{position:fixed;left:0;right:0;bottom:0;z-index:2001;
  display:flex;align-items:center;gap:14px;padding:10px 14px;
  background:var(--bad);color:#fff;
  font:500 12.5px/1.5 var(--sans);
  box-shadow:0 -2px 12px rgba(0,0,0,.3)}
.host-bar > span{flex:1;display:flex;flex-direction:column}
.host-bar small{opacity:.8;white-space:nowrap}
body.wall .host-bar{font-size:18px;padding:14px 22px}

/* The KMZ viewer: folder tree beside a map, the way Google Earth lays it
 * out. A fixed height because the map is inside a scrolling sheet — without
 * one Leaflet initialises into a zero-height box and draws nothing. */
.kvwrap{display:grid;grid-template-columns:220px 1fr;gap:12px;
  margin-top:10px;border:1px solid var(--line);border-radius:8px;
  overflow:hidden;background:var(--s-1)}
.kvside{padding:10px;border-right:1px solid var(--line);overflow:auto;
  max-height:520px}
.kvmap{height:520px;min-height:320px}
.kvdesc{margin-top:5px;font:400 11.5px/1.5 var(--sans);color:var(--ink-2);
  max-height:220px;overflow:auto}
.kvdesc table{border-collapse:collapse}
.kvdesc td{padding:1px 6px 1px 0}
@media (max-width:900px){
  .kvwrap{grid-template-columns:1fr}
  .kvside{border-right:none;border-bottom:1px solid var(--line);max-height:160px}
}

/* Planned work reads differently from a fault: muted rather than urgent,
   because the point is that nobody should chase it. Still visible — the
   alarm is real — just not shouting. */
.card.planned{border-left-color:var(--ink-3);opacity:.85}
.card .tag{display:inline-block;padding:1px 6px;margin-right:6px;
  border-radius:9px;background:var(--ink-3);color:var(--s-0);
  font:700 9.5px/1.6 var(--mono);letter-spacing:.06em;vertical-align:1px}

/* A client recorded from the router but not yet located. Hollow and
   dashed: a position nobody has confirmed, not a building at the exchange. */
.mk-cl.unplaced i{background:transparent!important;
  border:1.5px dashed var(--warn)!important}
.mk-cl.unplaced b{color:var(--warn);font-style:italic}

/* Layer checkboxes in the KMZ composer.
 *
 * The width override is not cosmetic: .frow input sets width:100%, which
 * stretched each checkbox across the whole column so the tick rendered
 * centred and its text was thrown to the far right edge. An attribute
 * selector to out-specify it. */
.chk{display:flex;align-items:center;gap:7px;padding:3px 0;
  font:400 12.5px/1.5 var(--sans);color:var(--ink-2);cursor:pointer}
.chk input[type="checkbox"]{width:auto;flex:0 0 auto;margin:0}
.chk:hover{color:var(--ink)}

.cass{display:flex;flex-direction:column;gap:1px}
.cassrow{display:flex;align-items:center;gap:7px;padding:2px 3px;
  font:400 10.5px/1.5 var(--mono);border-radius:3px}
.cassrow:nth-child(odd){background:var(--s-2)}
.cassrow.dark{opacity:.55}
.cassrow i{flex:0 0 auto;width:11px;height:11px;border-radius:2px;
  outline:1px solid color-mix(in srgb,var(--ink-3) 45%,transparent)}
.cassrow .n{flex:0 0 26px;text-align:right;font-weight:600;color:var(--ink)}
.cassrow .c{flex:0 0 auto;min-width:132px;color:var(--ink-2)}
.cassrow .to{color:var(--ink-2)}

.spark{display:flex;align-items:center;gap:8px}
.spark .sparksvg{background:var(--s-3);border-radius:3px;flex:0 0 auto}
.spark .sparkv{font:500 10.5px/1 var(--mono);color:var(--ink-2);
  white-space:nowrap}
.spark .sparkv .muted{font-weight:400}
.chip{display:inline-block;padding:2px 6px;border-radius:10px;
  font:600 9.5px/1.5 var(--mono);background:var(--s-3);color:var(--ink-2)}
.chip.warn{background:color-mix(in srgb,var(--warn) 20%,transparent);
  color:var(--warn)}
.chip.bad{background:color-mix(in srgb,var(--bad) 20%,transparent);
  color:var(--bad)}

/* Port numbers on the faceplate. Small, but the difference between "a green
   square" and "port 27". */
.pt{position:relative}
.pt .pnum{position:absolute;inset:0;display:flex;align-items:center;
  justify-content:center;font:600 7px/1 var(--mono);color:rgba(0,0,0,.55);
  pointer-events:none}
.pt.free .pnum,.pt.shut .pnum{color:var(--ink-3)}
.pt.big{width:26px;height:15px}
.pt.big .pnum{font-size:8px}
.faceplate .bank{gap:3px}
.faceplate .brow{gap:3px}
:root[data-theme="dark"] .pt .pnum{color:rgba(0,0,0,.62)}

/* ------------------------------------------------- rack elevation
 * Drawn as the rack is read: unit 1 at the bottom, numbered down the side,
 * kit occupying the units it actually fills. A list sorted by U is not the
 * same thing — you cannot see the gap where the next switch goes.
 */
.rack{border:1px solid var(--line);border-radius:6px;background:var(--s-0);
  padding:8px;margin-top:8px;max-width:520px}
.rack .u{display:flex;align-items:stretch;height:17px;margin-bottom:1px}
.rack .un{width:26px;flex:none;font:500 9px/17px var(--mono);color:var(--ink-3);
  text-align:right;padding-right:7px}
.rack .slot{flex:1;border-radius:3px;background:var(--s-2);
  border:1px dashed var(--line);cursor:pointer}
.rack .slot:hover{border-color:var(--accent);background:var(--s-3)}
.rack .kit{flex:1;border-radius:3px;display:flex;align-items:center;gap:8px;
  padding:0 9px;font:500 11px/1 var(--sans);color:#fff;overflow:hidden;
  white-space:nowrap;cursor:pointer}
.rack .kit small{font:400 10px/1 var(--mono);opacity:.82}
.rack .kit .rm{margin-left:auto;opacity:.75;font-size:11px}
.rack .kit:hover .rm{opacity:1}
.rack .cap{font:400 10px/1.5 var(--mono);color:var(--ink-3);margin-top:7px;
  display:flex;justify-content:space-between}

/* ------------------------------------------------- chassis image & bays */
.chassis{margin-top:8px;border:1px solid var(--line);border-radius:6px;
  background:var(--s-0);padding:8px;overflow:hidden}
.chassis img{display:block;width:100%;height:auto;image-rendering:auto;
  border-radius:3px}
.chassis .cap{font:400 10px/1.5 var(--mono);color:var(--ink-3);margin-top:6px}
.bays{display:grid;grid-template-columns:repeat(auto-fill,minmax(168px,1fr));
  gap:6px;margin-top:8px}
.bay{border:1px solid var(--line);border-radius:5px;padding:7px 9px;
  cursor:pointer;background:var(--s-0)}
.bay:hover{border-color:var(--accent)}
.bay .bn{font:600 10px/1 var(--mono);letter-spacing:.06em;color:var(--ink-3);
  text-transform:uppercase}
.bay .bv{font:500 12px/1.35 var(--sans);margin-top:5px}
.bay.empty .bv{color:var(--ink-3);font-weight:400;font-style:italic}
.bay .bs{font:400 10px/1.4 var(--mono);color:var(--ink-3);margin-top:3px}
.bay.fpc{border-left:3px solid var(--f-blue)}
.bay.psu{border-left:3px solid var(--f-orange)}
.bay.re{border-left:3px solid var(--f-violet)}

/* ============================================================ rack floor
 * Racks drawn as racks: a frame with punched mounting rails, equipment
 * mounted at the units it occupies with its ports visible on the face, and
 * cabinets standing side by side the way they do on a floor.
 *
 * The previous version was a table with coloured rows. It held the same
 * information and told you nothing at a glance — which is the whole reason
 * to draw a rack rather than list its contents.
 */
#floor{position:relative;overflow:auto;padding:4px 0 18px}
.floor-inner{display:flex;flex-direction:column;gap:20px;align-items:flex-start;
  transform-origin:0 0;transition:transform .12s ease-out;padding:2px}
.rackline{display:flex;gap:26px;align-items:flex-start}
.rackrow{width:100%}
.rackrow h4.sec{margin-top:0}

.zoombar{display:flex;align-items:center;gap:6px;margin-left:auto}
.zoombar button{width:26px;height:26px;padding:0;justify-content:center}
.zoombar .lvl{font:500 11px/1 var(--mono);color:var(--ink-3);min-width:38px;
  text-align:center}

/* ---- the cabinet itself */
.rk{flex:none;width:452px}
.rk .plate{background:linear-gradient(180deg,var(--s-3),var(--s-2));
  border:1px solid var(--line);border-bottom:0;border-radius:6px 6px 0 0;
  padding:7px 11px}
.rk .plate b{display:block;font:600 12.5px/1.25 var(--sans)}
.rk .plate span{display:block;font:400 10px/1.4 var(--mono);color:var(--ink-3);
  margin-top:2px}
.rk .body{display:flex;background:#10151b;border:1px solid var(--line);
  border-top:0;border-radius:0 0 6px 6px;padding:7px 0}
:root[data-theme="light"] .rk .body{background:#e7ebf0}
.rk .rail{width:17px;flex:none;background:
  repeating-linear-gradient(180deg,transparent 0 4px,
    rgba(255,255,255,.13) 4px 6px,transparent 6px 10px);
  border-left:1px solid rgba(255,255,255,.07);
  border-right:1px solid rgba(0,0,0,.35)}
:root[data-theme="light"] .rk .rail{background:
  repeating-linear-gradient(180deg,transparent 0 4px,
    rgba(0,0,0,.16) 4px 6px,transparent 6px 10px);
  border-left-color:rgba(0,0,0,.12);border-right-color:rgba(0,0,0,.12)}
.rk .col{flex:1;min-width:0}
.rk .unum{width:24px;flex:none;font:500 9px/38px var(--mono);color:var(--ink-3);
  text-align:right;padding-right:5px;user-select:none}

/* One rack unit is 1.75in on a 19in face — about 10.9:1. Drawing it 16px
   tall in a 430px rack made every device a flat strip, which is why the rack
   read as a table however the contents were styled. */
.ru{display:flex;height:38px;margin-bottom:1px}
.ru .slot{flex:1;border-radius:2px;
  background:repeating-linear-gradient(90deg,rgba(255,255,255,.028) 0 6px,
    transparent 6px 12px);
  border:1px solid rgba(255,255,255,.05);cursor:pointer}
:root[data-theme="light"] .ru .slot{
  background:repeating-linear-gradient(90deg,rgba(0,0,0,.035) 0 6px,
    transparent 6px 12px);border-color:rgba(0,0,0,.07)}
.ru .slot:hover{background:color-mix(in srgb,var(--accent) 22%,transparent);
  border-color:var(--accent)}

/* ---- a mounted device, drawn as its faceplate */
.kitface{flex:1;border-radius:3px;position:relative;overflow:hidden;
  cursor:pointer;display:flex;flex-direction:column;justify-content:center;
  background:linear-gradient(180deg,#39424e,#252c35);
  border:1px solid rgba(0,0,0,.5);box-shadow:inset 0 1px 0 rgba(255,255,255,.09)}
:root[data-theme="light"] .kitface{
  background:linear-gradient(180deg,#f2f5f8,#d9dfe6);
  border-color:rgba(0,0,0,.22);box-shadow:inset 0 1px 0 rgba(255,255,255,.8)}
.kitface .lip{position:absolute;left:0;top:0;bottom:0;width:3px}
.kitface .nm{font:600 10px/1 var(--mono);color:#e8eef4;padding:0 6px 0 9px;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;letter-spacing:.02em}
:root[data-theme="light"] .kitface .nm{color:#1d2733}
.kitface .pts{display:flex;gap:1px;padding:2px 6px 0 9px;flex-wrap:wrap;
  overflow:hidden}
.kitface .pd{width:4px;height:4px;border-radius:1px;background:#3d4550;flex:none}
.kitface .pd.up{background:#37d67a}
.kitface .pd.down{background:#e5484d}
.kitface .pd.free{background:#4a545f}
:root[data-theme="light"] .kitface .pd{background:#b7c0ca}
:root[data-theme="light"] .kitface .pd.free{background:#c8d0d8}
.kitface .led{position:absolute;right:6px;top:50%;transform:translateY(-50%);
  width:5px;height:5px;border-radius:50%;background:var(--ok);
  box-shadow:0 0 5px var(--ok)}
.kitface .led.down{background:var(--bad);box-shadow:0 0 5px var(--bad)}
.kitface .led.na{background:var(--ink-3);box-shadow:none}
.kitface:hover{outline:2px solid var(--accent);outline-offset:-1px;z-index:3}
.kitface.sel{outline:2px solid var(--accent);outline-offset:-1px}

/* an ODF face reads as adapters, not as a switch */
.kitface.odf{background:linear-gradient(180deg,#2f3742,#20262e)}
:root[data-theme="light"] .kitface.odf{
  background:linear-gradient(180deg,#eef1f5,#dbe1e8)}
.kitface .ad4{width:3px;height:7px;border-radius:1px;background:#4a90d9;flex:none}
.kitface .ad4.free{background:#3b444f}
:root[data-theme="light"] .kitface .ad4.free{background:#c2cad3}

.rk .foot{font:400 9.5px/1.5 var(--mono);color:var(--ink-3);
  display:flex;justify-content:space-between;padding:5px 2px 0}

/* Jumping from a rack face to the detail below needs to say where it landed */
@keyframes flashin{0%,100%{box-shadow:0 0 0 0 transparent}
  30%{box-shadow:0 0 0 3px color-mix(in srgb,var(--accent) 55%,transparent)}}
.flash{animation:flashin 1.4s ease-out;border-radius:6px}



/* ODF adapters in the rack are drop targets */
.kitface .rad{width:8px;height:15px;border-radius:1px;flex:none;cursor:pointer;
  border:1px solid rgba(0,0,0,.45);background:#3b444f;position:relative}
.kitface .rad.on{background:#4a90d9}
.kitface .rad.dropzone{outline:1.5px dashed var(--accent);outline-offset:1px}
.kitface .rad:hover{outline:1.5px solid var(--accent);outline-offset:1px;z-index:6}
:root[data-theme="light"] .kitface .rad{background:#c2cad3}

.kitface .meta{font:400 8.5px/1 var(--mono);color:var(--ink-3);margin-left:auto;
  white-space:nowrap}
.kitface .blockgap{width:3px;flex:none}





/* ------------------------------------------------- equipment in a rack
 * A name, then the ports, numbered. Nothing else on the face. The earlier
 * version layered a manufacturer drawing, a status overlay, a meta line and
 * an LED into 38 pixels and became harder to read than the plain squares it
 * replaced.
 */
.kitface{background:var(--s-0);border:1px solid var(--line);
  border-radius:3px;padding:3px 5px 3px 8px;gap:2px;justify-content:flex-start}
:root[data-theme="dark"] .kitface{background:var(--s-2)}
.kitface .hdr{font:400 9px/1.35 var(--mono);color:var(--ink-2);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.kitface .hdr b{font-weight:700;color:var(--ink)}
.kitface .hdr .alarm{color:var(--bad);font-weight:700}
.kitface .ftr{font:400 8px/1.3 var(--mono);color:var(--ink-3);margin-top:1px}
.kitface .prow{display:flex;gap:2px;align-items:center}
.kitface .rlabel{font:500 8px/1 var(--mono);color:var(--ink-3);width:11px;
  flex:none;text-align:right;margin-right:2px}
/* ODF columns (v1.89): a divider between 12-port blocks, letters above. */
.kitface .colgap{width:9px;flex:none;align-self:stretch;
  background:linear-gradient(90deg,transparent 4px,var(--ink-3) 4px,
  var(--ink-3) 5px,transparent 5px);opacity:.7}
.kitface .colhdr .colgap{background:none}
.kitface .colname{flex:none;text-align:center;font:700 8px/1 var(--mono);
  color:var(--ink-3);letter-spacing:.04em}
.kitface .gap{width:5px;flex:none}

/* a switch port */
.kitface .pp{border-radius:2px;flex:none;
  position:relative;cursor:grab;background:var(--s-3);
  border:1px solid rgba(0,0,0,.28)}
.kitface .pp.up{background:#1f9d55;border-color:#17794180}
.kitface .pp.down{background:#d64045;border-color:#a52c3080}
.kitface .pp.shut{background:#7c8794}
.kitface .pp.free{background:#8d97a4}
:root[data-theme="light"] .kitface .pp.free{background:#aeb8c4}
.kitface .pp b{position:absolute;inset:0;display:flex;align-items:center;
  justify-content:center;font:600 7px/1 var(--mono);color:#fff;
  pointer-events:none;opacity:.92}
.kitface .pp.free b,.kitface .pp.shut b{opacity:.85}
.kitface .pp:hover{outline:2px solid var(--accent);outline-offset:1px;z-index:6}
.kitface .pp.dragging{opacity:.35}
/* a place on the face with no port recorded: kept so the rows line up */
.kitface .pp.void{background:none;border:1px dashed var(--line);opacity:.6;
  cursor:default}
.kitface .pp.void:hover{outline:none}
.kitface .pp.patched::after{content:"";position:absolute;right:1px;bottom:1px;
  width:3px;height:3px;border-radius:50%;background:rgba(0,0,0,.75)}

/* an ODF adapter */
.kitface .ad{border-radius:2px;flex:none;
  position:relative;cursor:pointer;background:var(--s-3);
  border:1px solid rgba(0,0,0,.22)}
.kitface .ad.on{background:#3d7fc1}
.kitface .ad b{position:absolute;inset:0;display:flex;align-items:center;
  justify-content:center;font:600 7px/1 var(--mono);color:var(--ink-2);
  pointer-events:none}
.kitface .ad.on b{color:#fff}
.kitface .ad.dropzone{outline:2px dashed var(--accent);outline-offset:1px}
.kitface .ad:hover{outline:2px solid var(--accent);outline-offset:1px;z-index:6}

/* Below about 12px a number is unreadable; the colour still is. */
.kitface .pp.tight b,.kitface .ad.tight b{display:none}
.kitface .pp b,.kitface .ad b{font-size:clamp(5px,0.62em,8px)}
/* The face must not spill past the rails whatever is mounted in it. */
.kitface{overflow:hidden}
.kitface .prow{flex-wrap:nowrap;overflow:hidden}

/* ---- the hardware layout designer */
.preview{margin:8px 0;padding:7px 9px;border:1px solid var(--line);
  border-radius:5px;background:var(--s-0);overflow:hidden}
.preview .prow{margin-bottom:2px}
.brow{display:flex;gap:6px;align-items:center;margin-bottom:6px}
.brow input{background:var(--s-0);color:var(--ink);border:1px solid var(--line);
  border-radius:4px;padding:5px 7px;font:400 12px/1.3 var(--sans);flex:1}
.brow button{flex:none}

/* ------------------------------------------------- patch cords
 * Drawn on demand, not always. Forty-eight cords permanently on screen is
 * noise; one cord under the cursor answers "where does this go" instantly.
 */
#cords{position:absolute;inset:0;pointer-events:none;z-index:20;overflow:visible}
#cords path{fill:none;stroke-linecap:round;stroke-linejoin:round;
  stroke-width:2.5;opacity:.9}
#cords path.pinned{stroke-width:3}
#cords path.all{stroke-width:1.4;opacity:.42}
#cords circle{opacity:.95}
#cords circle.all{opacity:.42}
.kitface .pp.peer,.kitface .ad.peer{outline:2px solid var(--accent);
  outline-offset:1px;z-index:7}
.kitface .pp.lit,.kitface .ad.lit{outline:2px solid #ff8b2b;
  outline-offset:1px;z-index:8}
.zoombar .on{background:var(--accent);color:#fff;border-color:var(--accent)}

/* ---- fibre colour code on an ODF
 * Solid jacket colour, with the tube it came out of banded along the top.
 * The fibre colours repeat every twelve, so the tube is not decoration —
 * without it core 1 and core 13 are the same blue square.
 */
.kitface .ad.coded,.panel-odf .ad.coded{border-style:solid;
  border-color:rgba(0,0,0,.35)}
.kitface .ad.coded b,.panel-odf .ad.coded b{color:inherit;font-weight:700}
.legend-fibre{display:flex;flex-wrap:wrap;gap:6px 12px;margin-top:8px}
.legend-fibre span{display:flex;align-items:center;gap:5px;
  font:400 10.5px/1.4 var(--mono);color:var(--ink-3)}
.legend-fibre i{width:11px;height:11px;border-radius:2px;flex:none;
  border:1px solid rgba(0,0,0,.3)}
.legend-fibre i.tube{border-top-width:4px}

/* A free adapter still shows the position it is built for — muted, so a
   terminated core is never mistaken for an empty one. */
.kitface .ad.codedfree,.panel-odf .ad.codedfree{
  border-style:solid;border-color:rgba(0,0,0,.18)}
.kitface .ad.codedfree b,.panel-odf .ad.codedfree span{color:var(--ink-2)}

/* -------------------------------------------------- cable route editing */
.rv i, .rv-end i, .rv-mid i{position:absolute;border-radius:50%;
  box-shadow:0 1px 3px rgba(0,0,0,.35)}
.rv i{width:12px;height:12px;left:-6px;top:-6px;background:var(--warn);
  border:2px solid #fff;cursor:grab}
.rv-end i{width:12px;height:12px;left:-6px;top:-6px;background:var(--ink-2);
  border:2px solid #fff;cursor:not-allowed}
.rv-mid i{width:8px;height:8px;left:-4px;top:-4px;background:#fff;
  border:2px solid var(--warn);opacity:.85;cursor:copy}
.route-bar{position:absolute;left:50%;transform:translateX(-50%);bottom:18px;
  z-index:1200;background:var(--s-0);border:1px solid var(--line);
  border-radius:8px;padding:8px 12px;display:flex;gap:10px;align-items:center;
  box-shadow:0 4px 16px rgba(0,0,0,.18)}
.route-bar b{font:600 12px var(--sans)}
.route-bar span{font:400 11px var(--sans);color:var(--ink-2)}
.rv,.rv-end,.rv-mid{background:none!important;border:none!important}


/* One search box for everything. Sits in the top bar where the eye already
   goes; results drop below it and never cover the box being typed in. */
.gsearch{position:relative;margin-left:18px;flex:0 1 360px}
#gsearch{width:100%;padding:6px 10px;border:1px solid var(--line);
  border-radius:6px;background:var(--s-0);color:var(--ink);
  font:13px var(--sans)}
#gres{position:absolute;top:calc(100% + 4px);left:0;right:0;z-index:1500;
  max-height:60vh;overflow:auto;background:var(--s-0);
  border:1px solid var(--line);border-radius:6px;
  box-shadow:0 8px 24px rgba(0,0,0,.18)}
#gres .gr{display:grid;grid-template-columns:86px 1fr;gap:0 8px;width:100%;
  padding:7px 10px;border:0;border-bottom:1px solid var(--line);
  background:none;text-align:left;cursor:pointer;color:var(--ink)}
#gres .gr:hover,#gres .gr:focus{background:var(--s-2)}
#gres .gk{grid-row:span 2;font:600 10px/1.8 var(--mono);text-transform:uppercase;
  color:var(--ink-3)}
#gres .gt{font-weight:600}
#gres .gs{font-size:11.5px;color:var(--ink-3)}
#gres .gnone{padding:10px;color:var(--ink-3)}

/* Data quality: severity reads first, the count second, the reason third. */
.qcheck{border:1px solid var(--line);border-left:4px solid var(--ink-3);
  border-radius:6px;padding:10px 12px;margin:10px 0}
.qcheck.q-error{border-left-color:var(--bad)}
.qcheck.q-warning{border-left-color:var(--warn)}
.qcheck.clean{opacity:.55}
.qcheck .qh{display:flex;gap:10px;align-items:baseline}
.qcheck .qh b{flex:1}
.qcheck .qsev{font:700 9.5px var(--mono);text-transform:uppercase;
  color:var(--ink-3)}
.qcheck .qn{font:700 16px var(--mono)}
.qcheck .qw{font-size:12px;color:var(--ink-3);margin:4px 0 6px}
.qcheck .qi{display:flex;justify-content:space-between;width:100%;padding:4px 6px;
  border:0;background:none;text-align:left;cursor:pointer;color:var(--ink)}
.qcheck .qi:hover{background:var(--s-2)}


/* Go-to-coordinates, next to search: two different questions, two boxes. */
.gotobar{margin-left:12px;flex:0 0 190px}
#gotoxy{width:100%;padding:6px 10px;border:1px solid var(--line);
  border-radius:6px;background:var(--s-0);color:var(--ink);
  font:12.5px var(--mono)}

/* The tab that is showing reads as pressed, not merely highlighted. */
.btn.tab.on{background:var(--ink);color:var(--s-0);border-color:var(--ink)}

/* The KMZ bar sits between the toolbar and the map, so the map keeps the
   whole width — a side panel would squeeze the thing being compared. */
.kmzbar{padding:8px 12px;border-bottom:1px solid var(--line);
  background:var(--s-1);max-height:28vh;overflow:auto}
.kmzbar .kvside{display:flex;flex-wrap:wrap;gap:4px 16px;margin-top:6px}
.kmzbar .chk{font-size:12px}

/* `hidden` must win over any display rule: .btn and .who are inline-flex
   and the sign-in labels are block, so elements the code hid still showed
   — Sign out before anyone was known to be signed in, and a "Current
   password" label with no box under it. */
[hidden]{display:none!important}

/* Signed-in user in the top bar (v1.71). */
.who{display:inline-flex;align-items:baseline;gap:6px;white-space:nowrap;
  font:500 12px/1 var(--sans);color:var(--ink)}
.who #whoRole{font:500 10px/1 var(--mono);color:var(--ink-3);
  text-transform:uppercase;letter-spacing:.08em}

/* Sign-in page. */
html:has(body.login){overflow:auto;height:auto}
body.login{display:flex;align-items:center;justify-content:center;
  min-height:100vh;margin:0;background:var(--s-0);color:var(--ink);
  font:400 13px/1.5 var(--sans);overflow:auto;height:auto;
  padding:16px 0;box-sizing:border-box}
.login-card{width:min(340px,calc(100vw - 32px));background:var(--s-1);
  border:1px solid var(--line);border-radius:var(--r);padding:22px 22px 18px}
.login-card h1{margin:0 0 2px;font:600 16px/1.2 var(--sans)}
.login-card .sub{margin:0 0 16px;font:400 10.5px/1 var(--mono);
  color:var(--ink-3);text-transform:uppercase;letter-spacing:.08em}
.login-card label{display:block;margin:10px 0 4px;font:500 11px/1 var(--sans);
  color:var(--ink-2)}
.login-card input[type=text],.login-card input[type=password]{width:100%;
  box-sizing:border-box;background:var(--s-0);color:var(--ink);
  border:1px solid var(--line);border-radius:4px;padding:8px 9px;
  font:400 13px/1 var(--sans)}
.login-card .keep{display:flex;align-items:center;gap:7px;margin:12px 0 0;
  font:400 12px/1.3 var(--sans);color:var(--ink-2)}
.login-card .btn{width:100%;justify-content:center;margin-top:16px;padding:9px}
.login-card .msg{min-height:18px;margin-top:10px;font:400 12px/1.4 var(--sans);
  color:var(--bad)}
.login-card .note{margin-top:4px;font:400 11.5px/1.45 var(--sans);
  color:var(--ink-2)}

/* Bulk selection on list screens (v1.75). The bar stays in view while
   scrolling a long list, because that is where the ticking happens. */
.bulkbar{position:sticky;top:0;z-index:5;display:flex;flex-wrap:wrap;
  align-items:center;gap:8px;margin:0 0 10px;padding:8px 12px;
  background:color-mix(in srgb,var(--accent) 12%,var(--s-1));
  border:1px solid color-mix(in srgb,var(--accent) 40%,var(--line));
  border-radius:var(--r);font:400 12.5px/1.3 var(--sans)}
.bulkbar .n{margin-right:auto}
.bulkbar .n b{font-family:var(--mono);font-variant-numeric:tabular-nums}
.tbl th.selc,.tbl td.selc{width:30px;padding-right:0;text-align:center}
.tbl td.selc input,.tbl th.selc input{cursor:pointer;accent-color:var(--accent);
  width:15px;height:15px;margin:0}
.tbl tr.picked td{background:color-mix(in srgb,var(--accent) 9%,transparent)}
ul.bulklist{margin:4px 0 10px;padding-left:18px;max-height:180px;overflow:auto;
  font:400 12px/1.6 var(--sans);color:var(--ink-2)}
ul.bulklist.bad li{color:var(--ink)}
ul.bulklist.bad b{color:var(--bad);font-weight:600}

/* ------------------------------------------------ NOC wall mode (v1.77)
   Read from across the room: big type, colour only where there is
   something to act on, and the same shapes as the status map (square =
   down, diamond = degraded, ring = planned). The frame elements carry
   [hidden] outside wall mode, so every display rule here is under
   body.wall. */
body.wall #app{grid-template-columns:1fr 460px;grid-template-rows:92px 1fr 54px;
  grid-template-areas:"wtop wtop" "main walarm" "wfoot wfoot"}
body.wall #brand,body.wall #top,body.wall #nav,body.wall #bar,body.wall #tools,
body.wall #alarm,body.wall #right,body.wall #kmzbar,body.wall #tracebar,body.wall #histbar{display:none!important}
body.wall #wallTop{grid-area:wtop;display:flex;align-items:center;gap:28px;
  padding:0 28px;background:var(--s-1);border-bottom:1px solid var(--line)}
body.wall #wallAlarms{grid-area:walarm;display:flex;flex-direction:column;gap:12px;
  padding:22px 22px 16px;background:var(--s-1);border-left:1px solid var(--line);
  overflow:hidden;min-height:0}
body.wall #wallFoot{grid-area:wfoot;position:relative;display:flex;align-items:center;
  gap:20px;padding:0 28px;background:var(--s-1);border-top:1px solid var(--line);
  font:400 16px/1 var(--mono);color:var(--ink-2)}
body.wall #legend{font-size:14px}

.wbrand{display:flex;flex-direction:column;gap:6px;min-width:0}
.wbrand span{font:600 14px/1 var(--mono);letter-spacing:.16em;color:var(--ink-2)}
.wword{font:700 32px/1 var(--sans);white-space:nowrap}
.wword.k-down{color:color-mix(in srgb,var(--bad) 70%,var(--ink))}
.wword.k-degraded{color:var(--warn)}
.wword.k-planned{color:color-mix(in srgb,var(--f-violet) 55%,var(--ink))}
.wword.k-up{color:var(--ok)}
.wword.k-stale{color:var(--ink-2)}
.wcounts{display:flex;gap:10px}
.wcount{width:128px;padding:8px 12px;border-radius:6px;display:flex;flex-direction:column;
  gap:4px;border:1px solid var(--line-soft)}
.wcount b{font:600 34px/1 var(--mono);font-variant-numeric:tabular-nums}
.wcount span{font:600 12px/1 var(--mono);letter-spacing:.12em}
.wcount.k-zero{color:var(--ink-3)}
.wcount.k-down{background:color-mix(in srgb,var(--bad) 18%,var(--s-1));border-color:transparent;
  color:color-mix(in srgb,var(--bad) 70%,var(--ink))}
.wcount.k-degraded{background:color-mix(in srgb,var(--warn) 14%,var(--s-1));border-color:transparent;
  color:var(--warn)}
.wcount.k-planned{background:color-mix(in srgb,var(--f-violet) 18%,var(--s-1));border-color:transparent;
  color:color-mix(in srgb,var(--f-violet) 55%,var(--ink))}
.wcount.k-up{background:color-mix(in srgb,var(--ok) 16%,var(--s-1));border-color:transparent;
  color:var(--ok)}
.wtime{margin-left:auto;display:flex;flex-direction:column;align-items:flex-end;gap:6px;
  font:500 16px/1 var(--mono);color:var(--ink-2)}
.wtime .ok{color:var(--ok)}
.wtime .bad{color:color-mix(in srgb,var(--bad) 70%,var(--ink));font-weight:700}
#wClock{font:600 58px/1 var(--mono);letter-spacing:-.02em;font-variant-numeric:tabular-nums}
#wExit{opacity:.55}
#wExit:hover,#wExit:focus-visible{opacity:1}

/* Health */
.wallhealth{display:flex;flex-direction:column;gap:16px;padding:6px 6px 20px}
.whead{display:flex;align-items:baseline;gap:14px;margin-top:4px}
.whead span{font:600 15px/1 var(--mono);letter-spacing:.14em;color:var(--ink-2)}
.whead em{font:400 15px/1 var(--sans);font-style:normal;color:var(--ink-3)}
.wtiles{display:grid;grid-template-columns:repeat(auto-fill,minmax(520px,1fr));gap:16px}
.wtile{padding:20px 22px;border-radius:10px;display:flex;flex-direction:column;gap:10px;
  border:3px solid var(--line);background:var(--s-1);min-width:0}
.wtile.k-down{border-color:var(--bad);background:color-mix(in srgb,var(--bad) 13%,var(--s-0))}
.wtile.k-degraded,.wtile.k-unknown{border-color:var(--warn);
  background:color-mix(in srgb,var(--warn) 9%,var(--s-0))}
.wtile.k-planned{border:2px dashed var(--f-violet);
  background:color-mix(in srgb,var(--f-violet) 10%,var(--s-0))}
/* acknowledged: still listed, no longer shouting */
.wtile.quiet{border-width:1px;border-color:var(--line);background:var(--s-1)}
.wtile.quiet.k-planned{border:2px dashed color-mix(in srgb,var(--f-violet) 60%,var(--line))}
.wt-head{display:flex;align-items:center;gap:14px;min-width:0}
.wt-name{font:700 38px/1.05 var(--sans);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.wtile.quiet .wt-name{color:var(--ink-2)}
.wchips{margin-left:auto;display:flex;gap:6px;flex-wrap:wrap;justify-content:flex-end}
.wt-state{font:600 24px/1.1 var(--mono)}
.wtile.k-down .wt-state{color:color-mix(in srgb,var(--bad) 70%,var(--ink))}
.wtile.k-degraded .wt-state,.wtile.k-unknown .wt-state{color:var(--warn)}
.wtile.k-planned .wt-state{color:color-mix(in srgb,var(--f-violet) 55%,var(--ink))}
.wt-cause{font:500 21px/1.35 var(--sans)}
.wt-more{font:400 18px/1.4 var(--sans);color:var(--ink-2)}
.wglyph{flex:none;width:30px;height:30px;box-sizing:border-box}
.wglyph.k-down{background:var(--bad);border-radius:3px}
.wglyph.k-degraded,.wglyph.k-unknown{background:var(--warn);border-radius:3px;
  transform:rotate(45deg) scale(.82)}
.wglyph.k-planned{border:4px solid var(--f-violet);border-radius:50%}
.wmore{font:500 18px/1 var(--mono);color:var(--ink-2)}
.wpills{display:flex;flex-wrap:wrap;gap:8px}
.wpill{display:flex;align-items:center;gap:8px;padding:9px 13px;border-radius:6px;
  background:var(--s-1);border:1px solid var(--line-soft);font:500 18px/1 var(--mono);
  color:var(--ink-2)}
.wpill i{width:11px;height:11px;border-radius:50%;background:var(--ok)}
.wpill.k-stale i{background:transparent;border:2px dashed var(--ink-3);box-sizing:border-box}
.wpill.k-unwatched{color:var(--ink-3);background:transparent}
.wpill.k-unwatched i{background:transparent;border:2px solid var(--ink-3);box-sizing:border-box}
.wclear{padding:40px 8px 12px}
.wclear b{display:block;font:700 84px/1 var(--sans);letter-spacing:-.02em}
.wclear p{margin:16px 0 0;font:400 24px/1.4 var(--sans);color:var(--ink-2);max-width:900px}
.wstale{padding:16px 20px;border-radius:8px;border:2px dashed var(--ink-3);
  font:600 20px/1.4 var(--sans);color:var(--ink)}

/* chips, shared by tiles and alarm cards */
.wchip{font:700 14px/1 var(--mono);padding:6px 8px;border-radius:4px;white-space:nowrap;
  border:2px solid currentColor}
.wchip.sev.P1{background:var(--bad);border-color:var(--bad);color:#fff}
.wchip.sev.P2{background:var(--warn);border-color:var(--warn);color:#161616}
.wchip.sev.P3,.wchip.sev.P4{color:var(--ink-2)}
.wchip.nack{color:color-mix(in srgb,var(--bad) 60%,var(--ink))}
.wchip.ack{color:var(--ok);border-color:transparent;
  background:color-mix(in srgb,var(--ok) 16%,var(--s-1))}
.wchip.new{color:var(--accent);border-style:dashed}
.wchip.plan{color:color-mix(in srgb,var(--f-violet) 55%,var(--ink))}

/* pinned alarms */
.wa-top{display:flex;justify-content:space-between;align-items:baseline}
.wa-top span{font:600 15px/1 var(--mono);letter-spacing:.14em;color:var(--ink-2)}
.wa-top em{font:500 15px/1 var(--mono);font-style:normal;color:var(--ink-2)}
.walarm{padding:14px 16px;border-radius:8px;display:flex;flex-direction:column;gap:7px;
  border:1px solid var(--line);background:var(--s-2);flex:none}
.walarm.sev-P1{border:3px solid var(--bad);background:color-mix(in srgb,var(--bad) 13%,var(--s-0))}
.walarm.sev-P2{border:3px solid var(--warn);background:color-mix(in srgb,var(--warn) 9%,var(--s-0))}
.walarm.plan{border:1px dashed var(--f-violet);background:color-mix(in srgb,var(--f-violet) 9%,var(--s-0))}
.wa-head{display:flex;gap:6px;align-items:center;flex-wrap:wrap}
.wa-head em{margin-left:auto;font:600 18px/1 var(--mono);font-style:normal;color:var(--ink-2)}
.wa-title{font:600 20px/1.25 var(--sans)}
.walarm.acked .wa-title,.walarm.plan .wa-title{color:var(--ink-2)}
.wa-more{font:400 15.5px/1.4 var(--sans);color:var(--ink-2)}
.wa-none b{display:block;padding:22px 4px;font:600 26px/1.2 var(--sans);color:var(--ok)}
.wa-foot{margin-top:auto;padding-top:12px;border-top:1px solid var(--line);
  font:400 14px/1.3 var(--mono);color:var(--ink-3)}
/* fibre health under the alarms (v1.80): warnings, deliberately quieter */
.wa-fibre{display:flex;flex-direction:column;gap:6px;padding-top:10px;
  border-top:1px solid var(--line);flex:none}
.wa-fibre > span{font:600 13px/1 var(--mono);letter-spacing:.14em;color:var(--ink-2)}
.wfib{display:flex;gap:10px;align-items:baseline;font:400 15px/1.35 var(--sans);
  color:var(--ink-2)}
.wfib b{font:600 15px/1.35 var(--mono);color:var(--ink);white-space:nowrap}
.wfib i{font:600 11px/1 var(--mono);font-style:normal;letter-spacing:.08em;
  padding:4px 6px;border-radius:3px;border:1.5px solid currentColor;flex:none}
.wfib.alarm i{color:var(--bad)} .wfib.warn i,.wfib.watch i{color:var(--warn)}

/* rotation bar */
.wprog{position:absolute;left:0;top:-1px;height:3px;background:var(--accent);
  transition:width 1s linear}
.wscreens{display:flex;gap:6px}
.wscreens button{padding:8px 13px;border-radius:4px;border:1px solid var(--line);
  background:var(--s-2);color:var(--ink);font:500 16px/1 var(--mono);cursor:pointer}
.wscreens button b{color:var(--ink-3);font-weight:500;margin-right:3px}
.wscreens button:hover{border-color:var(--accent)}
.wscreens button.on{background:var(--accent);border-color:var(--accent);color:var(--s-0);font-weight:600}
.wscreens button.on b{color:inherit}
#wPause{font:500 15px/1 var(--mono)}
.wwho{margin-left:auto;color:var(--ink-3)}

/* Status map marks (v1.77), wall or not: a dashed ring for "changed in the
   last 30 minutes", a tick badge for "every alarm here is acknowledged". */
.mk-pop.chg::before{content:"";position:absolute;width:30px;height:30px;left:-15px;
  top:-15px;border-radius:50%;border:2px dashed var(--accent);box-sizing:border-box}
.mk-pop.ack::after{content:"\2713";position:absolute;left:-22px;top:-22px;width:15px;
  height:15px;border-radius:50%;box-sizing:border-box;background:var(--s-1);
  border:1.5px solid var(--ok);color:var(--ok);font:700 10px/12px var(--sans);text-align:center}
#legend .lg-mark{display:inline-block;width:12px;height:12px;margin-right:7px;
  vertical-align:-1px;border-radius:50%;box-sizing:border-box}
#legend .lg-mark.chg{border:2px dashed var(--accent)}
#legend .lg-mark.ack{border:1.5px solid var(--ok)}

/* fibre health chart (v1.80) */
.fhchart{margin:6px 0 12px}
.fhsvg{width:100%;height:auto;background:var(--s-3);border-radius:4px;display:block}
.fhgrid{stroke:var(--line);stroke-width:1}
.fhtick{font:400 10.5px var(--mono);fill:var(--ink-3)}

/* ---- the circuit as one line (v1.81) ----
   Sections are flex items sized by length; each carries the node at its
   start (and the last one its end too), labels above the line, length and
   core below. Scrolls sideways inside its own box on a narrow screen
   rather than the page. */
.cstrip{overflow-x:auto;border:1px solid var(--line);border-radius:8px;
  background:var(--s-1);padding:70px 64px 14px;margin:6px 0 10px}
.cs-line{display:flex;min-width:max-content;width:100%}
.cs-leg{position:relative;flex:1 0 120px;min-width:120px;cursor:pointer;
  padding:0 0 2px}
.cs-leg:hover .cs-bar{filter:brightness(1.08)}
.cs-bar{height:9px;border:1.5px solid var(--ink-3);border-radius:2px;
  margin:0 6px}
.cs-bar.unknown{background:repeating-linear-gradient(90deg,var(--ink-3) 0 6px,
  transparent 6px 10px)}
.cs-leg.on .cs-bar{box-shadow:0 0 0 4px color-mix(in srgb,var(--accent) 40%,transparent)}
.cs-len{text-align:center;margin-top:7px;font:400 10.5px/1.35 var(--mono);
  color:var(--ink-3);white-space:nowrap;padding:0 8px}
.cs-len b{display:block;font:600 11.5px/1.3 var(--mono);color:var(--ink)}
.cs-leg.on .cs-len b{color:var(--accent)}
.cs-node{position:absolute;left:0;top:6px;width:0}
.cs-node.last{left:100%}
.cs-node i{position:absolute;left:-7px;top:-7px;width:14px;height:14px;
  background:var(--s-0);border:2px solid var(--ink);box-sizing:border-box;z-index:1}
.cs-joint i{transform:rotate(45deg) scale(.85);border-color:var(--ink-2)}
.cs-client i{border-radius:50%}
.cs-node.stop i{border-color:var(--bad);background:var(--bad)}
.cs-node b,.cs-node small{position:absolute;left:0;transform:translateX(-50%);
  max-width:112px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  text-align:center}
.cs-node b{bottom:30px;font:600 11.5px/1.2 var(--sans);color:var(--ink)}
.cs-node small{bottom:16px;font:400 10px/1.2 var(--mono);color:var(--ink-3)}
.cs-node small.cs-slack{bottom:44px;color:var(--warn)}
.cs-node.stop small{color:var(--bad)}
/* The break: the same loud red as the pin on the map, so the two read as
   one thing seen twice. */
.cs-fault{position:absolute;top:-3px;width:18px;height:18px;margin-left:-9px;
  box-sizing:border-box;z-index:2;border-radius:50%;background:var(--bad);
  border:3px solid var(--s-0);
  box-shadow:0 0 0 4px color-mix(in srgb,var(--bad) 35%,transparent)}
.cs-node .cs-fault{left:0;top:-9px}
.cs-detail{margin:0 0 10px}
.cs-otdr-row{display:flex;flex-wrap:wrap;gap:6px 14px;align-items:flex-end}
.cs-otdr-row .frow{min-width:150px;grid-template-columns:1fr;gap:3px}
.cs-otdr-row .btn{margin-bottom:6px}
.cs-result{margin:8px 0}
.cs-result .t{font-size:14px}
.tracebar .tb-txt{font:12px/1.4 var(--sans);margin-right:8px}

/* ---- availability: outage calendar and SLA trend (v1.82) ----
   One row per site, one square per Lahore day. Scrolls sideways inside its
   own box; the site names stay put while it does. */
.calwrap{overflow-x:auto;border:1px solid var(--line);border-radius:8px;
  background:var(--s-1);padding:10px 12px 12px;margin:6px 0 8px}
.cal{display:grid;gap:3px;align-items:center;--cw:13px;width:max-content}
.cal-30{--cw:22px}
.cal-365{--cw:9px;gap:2px}
.cal-corner,.cal-site{position:sticky;left:0;z-index:1;background:var(--s-1);
  padding-right:10px}
.cal-m{font:500 9.5px/1 var(--mono);color:var(--ink-3);white-space:nowrap;
  height:14px;overflow:visible;position:relative}
.cal-dn{position:absolute;left:0;right:0;top:0;text-align:center;
  color:var(--ink-3);font-size:9px}
.cal-30 .cal-m{height:26px}
.cal-30 .cal-m .cal-dn{top:13px}
.cal-site{display:flex;flex-direction:column;min-width:0;padding:1px 10px 1px 0}
.cal-site b{font:600 12px/1.25 var(--sans);overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
.cal-site small{font:400 10px/1.3 var(--mono);color:var(--ink-3);white-space:nowrap}
.cal-site.nm b{color:var(--ink-3)}
.cc{width:var(--cw);height:var(--cw);border-radius:2px;display:inline-block;
  box-sizing:border-box}
.cal .cc{height:24px}
.cc.l0{background:var(--s-3)}
.cc.l1{background:color-mix(in srgb,var(--bad) 22%,var(--s-3))}
.cc.l2{background:color-mix(in srgb,var(--bad) 42%,var(--s-3))}
.cc.l3{background:color-mix(in srgb,var(--bad) 62%,var(--s-3))}
.cc.l4{background:color-mix(in srgb,var(--bad) 82%,var(--s-3))}
.cc.l5{background:var(--bad);box-shadow:inset 0 0 0 1.5px color-mix(in srgb,var(--ink) 35%,transparent)}
.cc.nm{background:repeating-linear-gradient(135deg,var(--s-3) 0 2px,transparent 2px 4px);
  border:1px solid var(--line)}
.cal .cc[tabindex]{cursor:pointer}
.cal .cc[tabindex]:hover,.cal .cc[tabindex]:focus-visible{outline:2px solid var(--ink);
  outline-offset:1px}
.cal-legend{display:flex;flex-wrap:wrap;gap:6px 16px;align-items:center;
  font:400 11px/1.4 var(--sans);color:var(--ink-2);margin:0 0 14px}
.cal-legend span{display:inline-flex;align-items:center;gap:6px}
.cal-legend .cc{width:12px;height:12px}
.slasvg{display:block;overflow:visible}
.slasvg .sla-line{stroke:var(--ink-3);stroke-width:1;stroke-dasharray:3 3}
.slasvg .sla-path{fill:none;stroke:var(--ink-2);stroke-width:1.4}
.slasvg .sla-dot{stroke:var(--s-1);stroke-width:1}
.slasvg .sla-dot.ok{fill:var(--ok)}
.slasvg .sla-dot.bad{fill:var(--bad)}
.slasvg .sla-dot.none{fill:var(--ink-2)}
.slasvg .sla-dot.partial{fill:var(--s-1);stroke-width:1.6}
.slasvg .sla-dot.partial.ok{stroke:var(--ok)}
.slasvg .sla-dot.partial.bad{stroke:var(--bad)}
.slasvg .sla-dot.partial.none{stroke:var(--ink-2)}
@media print{
  .calwrap{overflow:visible}
  .cal-corner,.cal-site{position:static}
}

/* ---- time travel on the Status map (v1.83) ----
   Sits over the map like the KMZ bar. The track carries ticks where
   something went down or degraded, a band where only incidents are known,
   and a hatch where the poller was not running; the range input lies on
   top of them so the ticks line up with the thumb. */
.histbar{max-height:none;overflow:visible}
.hb-top{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px}
.hb-when{font:600 15px/1.2 var(--mono);letter-spacing:-.01em}
.hb-src{font:400 11.5px/1.3 var(--sans);color:var(--ink-3)}
.hb-src.incidents{color:var(--warn)}
.hb-src.gap{color:var(--bad);font-weight:600}
.hb-wins{display:inline-flex;gap:4px;margin-left:auto}
.hb-track{position:relative;height:30px;margin:8px 0 4px}
.hb-track > i{position:absolute;top:6px;height:12px;pointer-events:none}
.hb-inc{background:color-mix(in srgb,var(--warn) 16%,transparent);
  border-top:2px solid color-mix(in srgb,var(--warn) 55%,transparent)}
.hb-gap{background:repeating-linear-gradient(135deg,var(--ink-3) 0 2px,transparent 2px 5px);
  opacity:.6}
.hb-tick{width:2px;margin-left:-1px;background:var(--warn)}
.hb-tick.down{background:var(--bad);width:3px;margin-left:-1.5px}
.hb-range{position:absolute;left:0;right:0;top:0;width:100%;margin:0;
  height:30px;background:transparent;accent-color:var(--accent)}
.hb-ctl{display:flex;flex-wrap:wrap;align-items:center;gap:4px}
.hb-sum{margin-left:8px;font:600 12px/1.3 var(--sans)}
.hb-open{display:flex;flex-wrap:wrap;gap:4px;margin-top:6px;font-size:11.5px}
.hb-chip{border:1px solid var(--line);border-left:3px solid var(--warn);
  border-radius:4px;padding:2px 7px;background:var(--s-0);
  max-width:360px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.hb-chip.P1{border-left-color:var(--bad)}
.hb-chip.planned{border-left-color:var(--ink-3)}

/* ---- site page (v1.84) ----
   Arriving cable: one line per cable, what it is, how much has landed here,
   and what to do with it, instead of a header over two page-wide buttons. */
.arrive{display:grid;grid-template-columns:minmax(0,1fr) minmax(180px,260px) auto;
  gap:6px 18px;align-items:center;padding:9px 12px;border:1px solid var(--line);
  border-radius:8px;background:var(--s-1);margin-bottom:6px}
.ar-info{min-width:0;display:flex;flex-direction:column;gap:2px}
.ar-info b{font:600 12.5px/1.3 var(--sans);overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap}
.ar-info span{font:400 11px/1.3 var(--mono);color:var(--ink-3)}
.ar-prog{display:flex;flex-direction:column;gap:4px;min-width:0}
.ar-prog .bar{height:6px;border-radius:3px;background:var(--s-3);overflow:hidden}
.ar-prog .bar i{display:block;height:100%;background:var(--ok)}
.ar-prog small{font:400 11px/1.2 var(--mono);color:var(--ink-3)}
.ar-prog.none small{color:var(--warn)}
.ar-acts{display:flex;gap:6px;flex-wrap:wrap;justify-content:flex-end}
.ar-runs{grid-column:1 / -1;font:400 11.5px/1.7 var(--mono);color:var(--ink-2);
  border-top:1px dashed var(--line);padding-top:6px}
.ar-runs .runed{padding:0 6px;font-size:10.5px;margin-left:6px}
.kv .btn.sm{flex:none}
@media (max-width:760px){
  .arrive{grid-template-columns:1fr}
  .ar-acts{justify-content:flex-start}
}
/* A run of empty units drawn as one band (v1.84): a 42U rack with four
   things in it is mostly air, and drawing the air to scale is what forced
   the page to open at 55%. */
.ru.gapu{height:22px}
.ru .slot.gapslot{display:flex;align-items:center;justify-content:center;
  font:500 10px/1 var(--mono);color:var(--ink-3);letter-spacing:.04em;
  border-style:dashed}
.rk .unum .gapn{font-size:8px;line-height:22px}
.zoombar button.wide{width:auto;padding:0 9px}
/* Header at the top, footer at the bottom, adapter rows spread between:
   the frame's face is filled, not packed against its top edge. */
.kitface.odf{justify-content:space-between}
/* Equipment faces (v1.86): the port rows fill the space under the name and
   sit in its middle, in blocks like the ODF adapters. */
.kitface .pgrid{flex:1;display:flex;flex-direction:column;justify-content:center;
  gap:2px;min-height:0}
.kitface .prow .gap{width:5px;flex:none}

/* Hardware catalogue (v1.93) */
.catalogue{display:flex;flex-direction:column;gap:6px;max-width:760px}
.catalogue input[type=search]{max-width:460px}
.catmeta{font-size:11px}
.catlist{display:flex;flex-direction:column;max-height:260px;overflow:auto;
  border:1px solid var(--line);border-radius:5px}
.catlist:empty{display:none}
.catrow{all:unset;cursor:pointer;display:flex;justify-content:space-between;
  gap:10px;padding:5px 9px;font:400 12px/1.3 var(--sans);color:var(--ink);
  border-bottom:1px solid var(--line-soft)}
.catrow span{font:400 11px/1.3 var(--mono);color:var(--ink-3);white-space:nowrap}
.catrow:hover,.catrow.on{background:var(--s-2)}
.catview .t{margin:4px 0}
.catbays{max-width:760px}
.catbays .bay.psu{cursor:default;opacity:.7}
.catpick{display:flex;flex-direction:column;gap:6px;max-width:760px}
.catpick:empty{display:none}
.catpick input[type=search]{max-width:460px}
.catbar{display:flex;gap:6px}
.bay .bs.learned{color:var(--accent)}
/* A broken-out cage's further channels anchor their cords here (v1.95) */
.kitface .pp .chan{position:absolute;left:50%;top:50%;width:0;height:0}
/* The card each bank of ports is on (v1.96) */
.kitface .cardhdr{min-height:0}
.kitface .cardname{flex:none;font:600 9px/1.2 var(--mono);color:var(--ink-2);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* Polling paused: the states shown are old (v1.98) */
.kitface.stale .pp:not(.void){filter:grayscale(.85);opacity:.7}
/* A switch port as a cord's end (v2.00) */
.kitface .pp.dropzone{outline:2px dashed var(--accent);outline-offset:1px}
