/* radioctl LIVE AIM page - mobile-first, sunlight-first.
 *
 * Its own stylesheet on purpose: style.css dresses a desktop map SPA, and
 * this page has the opposite constraints - a phone at arm's length, held
 * one-handed, outdoors, by someone whose other hand is on an antenna. So:
 *   * numbers are 40-56 px, not 14 px, and they are the ONLY large ink
 *   * contrast is pushed well past the map's (values are near-white on
 *     near-black; the good/warn/bad hues are lightened so they survive a
 *     bright screen washed out by sun)
 *   * every touch target is >= 44 px
 *   * the layout is a single column that never needs a horizontal scroll
 * The palette variables are copied from style.css deliberately rather than
 * imported - this page must render if style.css is not loaded at all, and
 * app.js's stylesheet belongs to another surface. */
:root {
  --bg: #14171c;
  --bg2: #1c2027;
  --bg3: #242a33;
  --line: #323a46;
  --fg: #d7dce3;
  --muted: #8a93a0;
  --accent: #4d9de0;
  /* lightened vs style.css: outdoors, the map's #3fb950 reads muddy */
  --good: #56d364;
  --warn: #f0b849;
  --bad: #ff6b62;
  --dn: #6f97ee;
  --cn: #2ad4e8;
  --tile: #1a1f27;
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font: 15px/1.4 "Segoe UI", system-ui, -apple-system, sans-serif;
  /* the sticky header is 56 px + the notch inset */
  padding: 0 0 env(safe-area-inset-bottom);
  overscroll-behavior-y: contain;
}
.mono { font-family: Consolas, "Cascadia Mono", ui-monospace, monospace; }
.lvmuted { color: var(--muted); }
.hidden { display: none !important; }

/* ---------- sticky header ---------- */
#lvbar {
  position: sticky;
  top: 0;
  z-index: 5;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  padding-top: calc(8px + env(safe-area-inset-top));
  background: var(--bg2);
  border-bottom: 1px solid var(--line);
}
#lvback {
  color: var(--muted);
  display: flex;
  align-items: center;
  justify-content: center;
  /* 44px touch target even though the glyph is 18px */
  width: 44px; height: 44px;
  margin: -8px 0 -8px -8px;
  text-decoration: none;
  flex: 0 0 auto;
}
#lvback:active { color: var(--fg); }
#lvwho { flex: 1 1 auto; min-width: 0; }
#lvlot {
  font-size: 19px;
  font-weight: 650;
  letter-spacing: 0.2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
#lvhost {
  font-size: 12px;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.lvbtn {
  background: var(--bg3);
  color: var(--fg);
  border: 1px solid var(--line);
  border-radius: 9px;
  width: 44px; height: 40px;
  display: flex; align-items: center; justify-content: center;
  flex: 0 0 auto;
  cursor: pointer;
}
.lvbtn.paused { color: var(--warn); border-color: var(--warn); }
.lvdot {
  width: 11px; height: 11px; border-radius: 50%;
  flex: 0 0 auto;
  background: var(--muted);
}
.lvdot.ok { background: var(--good); }
.lvdot.bad { background: var(--bad); }
.lvdot.stale { background: var(--warn); }
/* the poll heartbeat: a short pulse each time a beat lands, so a
 * technician can tell "the page is alive" from across a lot without
 * reading anything */
.lvdot.beat { animation: lvbeat 0.5s ease-out; }
@keyframes lvbeat {
  from { box-shadow: 0 0 0 0 currentColor; opacity: 1; }
  to   { box-shadow: 0 0 0 9px transparent; opacity: 1; }
}

/* ---------- cards ---------- */
#lvmain { padding: 10px 12px 28px; display: grid; gap: 10px; }
.lvcard {
  background: var(--bg2);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 11px 13px;
}
.lvsechead {
  font-size: 11px;
  letter-spacing: 1.3px;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 7px;
}

/* ---------- serving DN ---------- */
.lvserv { border-color: #3d4a5e; }
.lvservtop { display: flex; align-items: baseline; gap: 10px;
             flex-wrap: wrap; }
.lvpole {
  font-size: 34px;
  font-weight: 700;
  line-height: 1.05;
  color: var(--fg);
  letter-spacing: 0.5px;
}
.lvwlan {
  font-size: 17px;
  font-weight: 600;
  color: var(--dn);
  font-family: Consolas, "Cascadia Mono", ui-monospace, monospace;
}
.lvdnhost { font-size: 16px; margin-top: 3px; color: var(--fg); }
.lvdnname { font-size: 12px;
            white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lvchips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.lvchip {
  font-size: 12px;
  padding: 3px 8px;
  border-radius: 999px;
  background: var(--bg3);
  border: 1px solid var(--line);
  color: var(--fg);
  white-space: nowrap;
}
.lvchip b { font-weight: 650; }
.lvchip.ch1 { border-color: #d8c33a; color: #e7d768; }
.lvchip.ch2 { border-color: #a173d8; color: #c2a3ec; }
.lvchip.ch3 { border-color: #4fae5c; color: #82d68d; }
.lvchip.ch4 { border-color: #4d7ce5; color: #8ab0f5; }
.lvchip.warn { border-color: var(--warn); color: var(--warn); }

/* "now served by ..." - the association moved under the technician's
 * feet, which must be impossible to miss */
.lvchange {
  background: #3a2e12;
  border: 1px solid var(--warn);
  color: #ffd98a;
  border-radius: 9px;
  padding: 7px 10px;
  font-size: 13px;
  margin-bottom: 8px;
  animation: lvflash 1s ease-in-out 3;
}
@keyframes lvflash { 50% { background: #5a4415; } }

/* ---------- site-survey jump-off ----------
 * Two destinations plus their copy buttons, on a wrapping row so a phone
 * held in one hand at a pole stacks them instead of scrolling sideways.
 * Targets are 40 px tall for the same reason every other control on this
 * page is: it is tapped outdoors, with gloves on. */
.lvsvyrow {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.lvsvybtn {
  background: var(--bg3);
  color: var(--fg);
  border: 1px solid var(--line);
  border-radius: 9px;
  min-height: 40px;
  padding: 0 13px;
  display: inline-flex;
  align-items: center;
  font: inherit;
  font-size: 13.5px;
  text-decoration: none;
  cursor: pointer;
}
.lvsvybtn:active { background: var(--line); }
/* the CN link is the one that always works (no shared position needed),
 * so it reads as the primary of the pair */
.lvsvybtn.lvsvyprim { border-color: var(--accent); color: #a9d3f5; }
.lvsvynote {
  margin: 8px 0 0;
  font-size: 12.5px;
  line-height: 1.35;
  overflow-wrap: anywhere;   /* a pasted absolute URL must not widen the
                                page - horizontal overflow on a phone is
                                the one layout bug this app keeps hitting */
}
.lvsvynote.lvsvyerr { color: var(--warn); }

/* ---------- link actions: open-on-map + publish a guest share ----------
 * The share sub-panel is a warning box (amber, like the "association moved"
 * notice) because publishing exposes live metrics to an unauthenticated
 * visitor - the operator taps through it deliberately, which is also the
 * `acknowledge` the server requires. */
.lvsharebox {
  margin-top: 10px;
  padding: 10px 11px;
  border: 1px solid var(--warn);
  border-radius: 9px;
  background: #2a2410;
}
.lvsharewarn {
  margin: 0 0 9px;
  font-size: 12.5px;
  line-height: 1.4;
  color: #ffd98a;
}
.lvsharelbl { margin: 0 0 5px; font-size: 12px; }
.lvshareurl {
  font-size: 12.5px;
  line-height: 1.35;
  padding: 8px 9px;
  margin-bottom: 9px;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 8px;
  color: var(--fg);
  overflow-wrap: anywhere;      /* the secret URL must never widen the page */
  user-select: all;             /* one tap selects the whole URL to copy */
  -webkit-user-select: all;
}

/* ---------- alerts ---------- */
.lvalert {
  border-radius: 12px;
  padding: 10px 12px;
  font-size: 13.5px;
  line-height: 1.35;
  border: 1px solid;
  margin-bottom: 8px;
  background: var(--bg2);
}
.lvalert:last-child { margin-bottom: 0; }
.lvalert .lvacode {
  display: block;
  font-size: 11px;
  letter-spacing: 1.1px;
  text-transform: uppercase;
  font-weight: 700;
  margin-bottom: 3px;
}
.lvalert.critical, .lvalert.high { border-color: var(--bad); }
.lvalert.critical .lvacode, .lvalert.high .lvacode { color: var(--bad); }
.lvalert.warn { border-color: var(--warn); }
.lvalert.warn .lvacode { color: var(--warn); }
.lvalert.info { border-color: var(--line); }
.lvalert.info .lvacode { color: var(--muted); }
/* the advisory's evidence, under its instruction: quieter than the thing
   to do, but present - a technician who cannot see WHY cannot sanity-check
   the guidance against the numbers on the same screen */
.lvalert .lvawhy {
  margin-top: 4px;
  font-size: 12px;
  color: var(--muted);
}

/* ---------- metric tiles: the reason the page exists ---------- */
.lvtiles {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}
.lvtile {
  background: var(--tile);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 9px 10px 6px;
  min-width: 0;
}
.lvthead {
  display: flex; align-items: baseline; justify-content: space-between;
  font-size: 11px; letter-spacing: 1.2px; text-transform: uppercase;
  color: var(--muted);
}
.lvtunit { font-size: 10px; letter-spacing: 0.6px; }
.lvtval {
  font-size: 44px;
  font-weight: 700;
  line-height: 1.02;
  font-variant-numeric: tabular-nums;
  font-family: Consolas, "Cascadia Mono", ui-monospace, monospace;
  letter-spacing: -1.5px;
  margin: 1px 0 0;
}
.lvtval.good { color: var(--good); }
.lvtval.warn { color: var(--warn); }
.lvtval.bad  { color: var(--bad); }
.lvtval.none { color: var(--muted); }
.lvtsub {
  display: flex; align-items: center; gap: 7px;
  font-size: 11.5px; color: var(--muted);
  font-variant-numeric: tabular-nums;
  min-height: 16px;
}
.lvtdelta { font-weight: 700; font-family: Consolas, ui-monospace, monospace; }
.lvtdelta.up { color: var(--good); }
.lvtdelta.down { color: var(--bad); }
.lvtdelta.flat { color: var(--muted); }
.lvspark { display: block; width: 100%; height: 30px; margin-top: 3px; }
.lvspark .ln { fill: none; stroke: var(--accent); stroke-width: 1.8;
               stroke-linejoin: round; stroke-linecap: round; }
.lvspark .bestln { stroke: var(--good); stroke-width: 1;
                   stroke-dasharray: 3 3; opacity: 0.75; }
.lvspark .now { fill: var(--fg); }

/* ---------- beams ---------- */
.lvbeamgrid {
  display: grid;
  grid-template-columns: auto repeat(2, minmax(0, 1fr));
  gap: 4px 10px;
  align-items: center;
  font-variant-numeric: tabular-nums;
}
.lvbeamgrid .h {
  display: flex;
  flex-direction: column;
  min-width: 0;                 /* let the device name ellipsize, never widen
                                   the column - the overflow trap this page
                                   keeps hitting */
  font-size: 11px; letter-spacing: 1px; text-transform: uppercase;
  color: var(--muted);
}
/* the device NAME under the CN/DN/NEAR/FAR role label: quieter case, full
   contrast, and clipped so a long customer name cannot push the page wide.
   The full name is on the header's `title`. */
.lvbeamgrid .lvsidenm {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.2px;
  text-transform: none;
  color: var(--fg);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}
.lvbeamgrid .rl { font-size: 12px; color: var(--muted); white-space: nowrap; }
.lvbeamgrid .v {
  font-size: 21px; font-weight: 650;
  font-family: Consolas, "Cascadia Mono", ui-monospace, monospace;
}
.lvbeamgrid .v.far { color: var(--warn); }
.lvbeamgrid .v.rail { color: var(--bad); }
.lvbeamgrid .v.none { color: var(--muted); font-size: 17px; }
.lvcnhead { color: var(--cn); }
.lvdnhead { color: var(--dn); }

/* ---------- key/value blocks ---------- */
.lvkv { display: grid; grid-template-columns: auto 1fr; gap: 4px 12px;
        font-size: 14px; }
.lvkv dt { color: var(--muted); font-size: 12.5px; white-space: nowrap; }
.lvkv dd { margin: 0; overflow-wrap: anywhere; }
.lvkv dd.good { color: var(--good); }
.lvkv dd.warn { color: var(--warn); }
.lvkv dd.bad  { color: var(--bad); }
.lvkv dd.pending { color: var(--muted); font-style: italic; }

#lvfoot { font-size: 11.5px; text-align: center; padding-top: 2px; }

/* the survey + location cards and the footer live OUTSIDE #lvmain (the
 * survey-where-I-am control renders in the NO-SUBJECT state too - a tech
 * standing in a field has nothing selected), so they carry their own
 * copy of the main column's gutters */
#lvsurvey, #lvgeo, #lvfoot { margin: 10px 12px; }
#lvfoot { margin-bottom: 28px; }

/* ---------- dn-dn orientation strip: WHICH end is the near column.
 * On a backhaul nothing but the URL says which radio you are standing
 * at, so the page states it and offers the fix (swap = a pure URL
 * rewrite of the same link). ---------- */
.lvorient {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
  padding: 8px 10px;
  border: 1px dashed var(--line);
  border-radius: 9px;
  font-size: 12.5px;
  color: var(--muted);
}
.lvorient strong { color: var(--fg); }
.lvorienttxt { flex: 1 1 200px; line-height: 1.35; }
.lvsvybtn.lvswap { min-height: 34px; font-size: 12.5px; }

/* ---------- picker ---------- */
#lvpick { margin: 12px; display: grid; gap: 8px; }
#lvpick label { font-size: 12px; color: var(--muted); }
#lvpickin {
  background: var(--bg3); color: var(--fg);
  border: 1px solid var(--line); border-radius: 9px;
  padding: 11px 12px; font: inherit; font-size: 16px;  /* >=16px: iOS
      zooms the whole page when a focused input is smaller */
  outline: none; width: 100%;
}
#lvpickin:focus { border-color: var(--accent); }
.lvgo {
  background: var(--accent); color: #08121c; border: 0;
  border-radius: 9px; padding: 12px; font: inherit; font-weight: 700;
  font-size: 15px; cursor: pointer; min-height: 44px;
}
#lvpickerr { color: var(--bad); font-size: 13px; margin: 0; }

/* offline / dead: the same visual language the map uses */
.lvdead { animation: lvoff 1.2s ease-in-out infinite; }
@keyframes lvoff { 50% { opacity: 0.35; } }
@media (prefers-reduced-motion: reduce) {
  .lvdead, .lvchange, .lvdot.beat { animation: none; }
  .lvdead { opacity: 0.7; }
}

/* ---------- THE POLE VIEW: wlan tabs, then a links|config toggle -------
 * TIER 1 (the WLAN tabs) and TIER 2 (the links|config toggle) are two
 * different things and are styled as two different things on purpose: the
 * tabs are ENTITIES and carry the channel colour, the toggle is a
 * FUNCTION and carries none. If both were the same chip a reader would
 * have to remember which strip was which.
 *
 * THE COLOUR STOPS AT THE TAB. `.lvpbody` below inherits nothing from the
 * selection - operator 2026-07-30, about the nav's wlan rows and equally
 * about these: "Just color the tab tho, not the whole page under the tab".
 * The four hues are the same literals `.lvchip.ch1-4` above already uses,
 * so a channel chip and a channel tab on one screen cannot disagree; they
 * are the MAP'S ASSIGNMENT (1 yellow / 2 purple / 3 green / 4 blue) in
 * THIS stylesheet's lightened-for-sunlight palette, NOT the map's literal
 * hex values - see this file's own header policy, and the two tests that
 * pin the pair (tab == chip exactly; tab hue within 25 deg of app.js's
 * CHAN_COLORS for the same channel number). Unknown channel falls through
 * to the plain border, i.e. gray keeps meaning "the radio did not answer"
 * rather than "channel 0". */
#lvpoleview { padding: 10px 12px 28px; display: grid; gap: 10px; }
.lvpwtabs, .lvpasp {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
}
.lvpasp { margin-top: 9px; }
.lvpwtab, .lvpaspbtn {
  background: var(--bg3);
  color: var(--fg);
  border: 1px solid var(--line);
  border-radius: 9px;
  /* >= 44 px of touch target, like every other control on this page - it
     is tapped outdoors, one-handed */
  min-height: 44px;
  padding: 0 13px;
  font: inherit;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 7px;
}
/* the CHANNEL BAR: a 4 px edge on the tab and nothing else, so an
   unselected tab still tells you its channel at a glance - which is the
   whole reason a single-D621 pole keeps its one tab (operator: "I think
   it's worth showing because it tells the channel with it's color") */
.lvpwtab { border-left-width: 4px; }
.lvpwtab.ch1 { border-left-color: #d8c33a; }
.lvpwtab.ch2 { border-left-color: #a173d8; }
.lvpwtab.ch3 { border-left-color: #4fae5c; }
.lvpwtab.ch4 { border-left-color: #4d7ce5; }
/* SELECTION MAY NOT TAKE THE CHANNEL EDGE, and the two rules have to be
   split for it. `.lvpwtab.ch1` and `.lvpwtab[aria-selected="true"]` are the
   SAME specificity (0,2,0), the selected rule comes LATER, and
   `border-color` is a shorthand that writes border-left-color too - so the
   selection won the cascade and the channel bar simply vanished on the
   selected tab. MEASURED on the real page before this split: a lone D621
   pole draws exactly one tab, it is ALWAYS selected (poleRebuildTabs falls
   back to tabs[0]), and its computed border-left-color read
   rgb(77, 157, 224) = var(--accent) instead of its declared #d8c33a - i.e.
   the ONE case the operator ruled on ("Lets keep the tab for a single d621
   ... it tells the channel with it's color") was the one case that never
   drew the colour. Worse than absent: #4d9de0 sits (0, 33, 5) from channel
   4's own #4d7ce5 in RGB, so a selected channel-1 tab read at a glance as
   channel 4. The selected WLAN tab therefore names its OTHER THREE edges
   and leaves the left one to the channel; the links|config toggle carries
   no channel and keeps the whole-border form. */
.lvpwtab[aria-selected="true"] {
  background: var(--bg);
  border-top-color: var(--accent);
  border-right-color: var(--accent);
  border-bottom-color: var(--accent);
  color: #fff;
  font-weight: 650;
}
.lvpaspbtn[aria-selected="true"] {
  background: var(--bg);
  border-color: var(--accent);
  color: #fff;
  font-weight: 650;
}
.lvpwtab:active, .lvpaspbtn:active { background: var(--bg2); }
.lvpwtab .lvpwch {
  font-size: 12px;
  color: var(--muted);
  font-family: Consolas, "Cascadia Mono", ui-monospace, monospace;
}
.lvpbody { margin-top: 11px; }
.lvplist { list-style: none; margin: 0; padding: 0; display: grid;
           gap: 7px; }
/* a link row is a BUTTON: tapping it re-aims THIS page at that link, which
   is the same view a map click opens - one subject, one renderer, reached
   two ways */
.lvprow {
  width: 100%;
  text-align: left;
  background: var(--bg3);
  color: var(--fg);
  border: 1px solid var(--line);
  border-left-width: 4px;
  border-radius: 9px;
  min-height: 44px;
  padding: 7px 11px;
  font: inherit;
  cursor: pointer;
}
.lvprow:active { background: var(--bg2); }
.lvprow.good { border-left-color: var(--good); }
.lvprow.warn { border-left-color: var(--warn); }
.lvprow.bad { border-left-color: var(--bad); }
.lvprowtop { display: flex; align-items: baseline; gap: 8px; }
.lvprowname { flex: 1 1 auto; min-width: 0; overflow: hidden;
              text-overflow: ellipsis; white-space: nowrap; }
.lvprowsub { font-size: 12px; color: var(--muted); margin-top: 2px;
             overflow: hidden; text-overflow: ellipsis;
             white-space: nowrap; }
.lvpempty { color: var(--muted); font-size: 13.5px; }
.lvpmember { font-size: 13px; }

/* ---------- wider screens: the same page, more columns ---------- */
@media (min-width: 620px) {
  #lvmain, #lvpoleview { max-width: 900px; margin: 0 auto; }
  /* the out-of-main cards track the same column */
  #lvsurvey, #lvgeo, #lvfoot {
    max-width: 876px; margin-left: auto; margin-right: auto;
  }
  .lvtiles { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .lvtval { font-size: 48px; }
}
