/* beez miniBMS
   Palette: light green #92CA4F, dark green #119043, blue #1BA4CF.
   Type: Trebuchet MS primary, Aileron fallback. No yellow, no orange.
   Signature: the hexagon cell (the hive) carries the brand through the UI,
   from the login mark to live status pills. */

:root {
  --green: #119043;
  --green-light: #92CA4F;
  --blue: #1BA4CF;
  --ink: #17271c;
  --muted: #5c6b61;
  --line: #d9e4da;
  --bg: #f4f8f4;
  --card: #ffffff;
  --alarm: #c0392b;
  --radius: 10px;

  /* Layout, one set of numbers for the whole console. Every page is the
     same column of the same width with the same gutter, and the pages
     that carry a navigation rail use the same rail. Before these
     existed the rail was 320px on Trends and 272px on Graphics,
     Maintenance and Dashboards, and the top bar spanned the window
     while the content sat in a narrower centred column, so the brand
     mark lined up with nothing below it.

     1600px rather than the old 1280: this is a supervision console, and
     its widest table (the control loops, fourteen columns) had about
     89px per column at 1280 and wrapped every header. Text still reads
     comfortably because the help paragraphs sit inside cards, not
     across the full column. */
  --page-max: 1600px;
  --page-pad: 18px;
  --rail: 272px;
  --gap: 14px;
}
/* Breakpoints, named here because a media query cannot read a custom
   property: 900px collapses a rail onto its main area, 720px is the
   phone layout. Use those two rather than inventing a third. */
/* The hidden attribute must always win, whatever display a class sets. */
[hidden] { display: none !important; }


* { box-sizing: border-box; }
html, body { margin: 0; height: 100%; }
body {
  font-family: "Trebuchet MS", "Aileron", Tahoma, Verdana, sans-serif;
  background: var(--bg);
  color: var(--ink);
  font-size: 15px;
}
h1, h2 { font-weight: 700; letter-spacing: 0.2px; }
button { font-family: inherit; cursor: pointer; }
input, select {
  font-family: inherit; font-size: 14px;
  padding: 8px 10px; border: 1px solid var(--line); border-radius: 8px;
  background: #fff; color: var(--ink);
}
input:focus, select:focus, button:focus-visible {
  outline: 2px solid var(--blue); outline-offset: 1px;
}
label { display: flex; flex-direction: column; gap: 4px; font-size: 13px; color: var(--muted); }

/* Brand mark: hexagon cell */
.hex {
  width: 26px; height: 26px; display: inline-block; background: var(--green);
  clip-path: polygon(25% 5%, 75% 5%, 100% 50%, 75% 95%, 25% 95%, 0% 50%);
  box-shadow: 14px 0 0 -6px var(--green-light);
}
.brand-mark { display: flex; align-items: center; gap: 14px; }
.brand-mark h1 { margin: 0; font-size: 22px; color: var(--green); font-weight: 400; }
.brand-mark h1 strong { color: var(--ink); }
.brand-mark.small h1 { font-size: 18px; }
.tagline { margin: 2px 0 0; color: var(--muted); font-size: 13px; }

/* Login */
.center-stage { min-height: 100vh; display: grid; place-items: center; }
.login-card {
  width: min(380px, 92vw); background: var(--card); border: 1px solid var(--line);
  border-top: 4px solid var(--green); border-radius: var(--radius);
  padding: 28px; display: flex; flex-direction: column; gap: 14px;
  box-shadow: 0 10px 30px rgba(17, 144, 67, 0.08);
}

/* Top bar. The bar itself spans the window, because a header that stops
   short of the edges looks like a mistake; its CONTENTS sit in the same
   column as the page below, so the brand mark begins exactly where the
   content begins and Sign out ends exactly where it ends. */
.topbar {
  background: var(--card); border-bottom: 2px solid var(--green);
  position: sticky; top: 0; z-index: 5;
}
/* Two stacked rows, never one wrapping row. The session strip is row
   one and the brand and tabs are row two, so the strip's position is
   a property of the layout rather than of how long the signed in
   username and the version string happen to be that day. */
.topbar-inner {
  max-width: var(--page-max); margin: 0 auto;
  padding: 8px var(--page-pad) 10px;
  display: flex; flex-direction: column; gap: 8px;
}
.topbar-main {
  display: flex; align-items: center; gap: 24px; flex-wrap: wrap;
}
#tabs { display: flex; gap: 4px; }
#tabs button {
  border: 0; background: transparent; padding: 8px 14px; border-radius: 8px;
  color: var(--muted); font-size: 14px;
}
#tabs button.active { background: var(--green); color: #fff; }
#tabs button:hover:not(.active) { background: var(--bg); color: var(--ink); }
.session { display: flex; align-items: center; justify-content: flex-end;
  gap: 10px; font-size: 13px; color: var(--muted); flex-wrap: wrap;
  min-height: 26px; }
/* Technical mode reads as a state, not as a verb. Off is grey and says
   OFF; on is the brand green and says ON. The word matters as much as
   the colour: an operator asked to confirm the mode is off should be
   able to answer from the text alone, without knowing which colour
   this build happens to use for which state. */
.tech-mode { font-variant-numeric: tabular-nums; }
.tech-mode.tech-off {
  background: var(--bg); color: var(--muted);
  border: 1px solid var(--line, #d8e0d8);
}
.tech-mode.tech-on {
  background: var(--green); color: #fff; border: 1px solid var(--green);
}
.dot { width: 10px; height: 10px; border-radius: 50%; background: var(--alarm); display: inline-block; }
.dot.up { background: var(--green-light); }

/* Buttons */
.btn-primary {
  background: var(--green); color: #fff; border: 0; border-radius: 8px;
  padding: 9px 16px; font-size: 14px;
}
.btn-primary:hover { background: #0d7536; }
.btn-secondary {
  background: #fff; color: var(--green); border: 1px solid var(--green);
  border-radius: 8px; padding: 8px 14px; font-size: 14px;
}
.btn-secondary:hover { background: var(--bg); }
.btn-ghost { background: transparent; border: 0; color: var(--muted); font-size: 13px; }
.btn-ghost:hover { color: var(--ink); }
.btn-write {
  background: var(--blue); color: #fff; border: 0; border-radius: 999px;
  padding: 4px 12px; font-size: 12px;
}
.btn-write:hover { background: #14839f; }
.btn-print {
  background: linear-gradient(135deg, #2bc4f0, #1499c2);
  color: #fff; border: 0; border-radius: 8px; padding: 9px 18px;
  font-size: 14px; font-weight: 600; cursor: pointer;
  box-shadow: 0 2px 8px rgba(27,164,207,0.45), inset 0 1px 0 rgba(255,255,255,0.3);
  transition: transform 0.12s ease, box-shadow 0.12s ease, background 0.12s ease;
}
.btn-print:hover {
  background: linear-gradient(135deg, #35cdf7, #1ba4cf);
  box-shadow: 0 4px 14px rgba(27,164,207,0.6), inset 0 1px 0 rgba(255,255,255,0.35);
  transform: translateY(-1px);
}
.btn-print:active {
  transform: translateY(0);
  box-shadow: 0 2px 6px rgba(27,164,207,0.5), inset 0 1px 0 rgba(255,255,255,0.2);
}

/* Panels */
/* Every page, without exception. A panel that sets its own width is a
   bug, not a special case: tools/check_dom.py checks the panels exist
   and nest correctly, and this rule is what makes them look alike. */
.panel { padding: var(--page-pad); max-width: var(--page-max);
  margin: 0 auto; }
.toolbar { display: flex; gap: 10px; align-items: end; flex-wrap: wrap; margin-bottom: 14px; }
.toolbar input[type="search"] { flex: 1; min-width: 220px; }
/* One toolbar grammar everywhere (2026.07.20-15): stacked small label
   above its control, one control geometry, controls share the bottom
   baseline, trailing text optically centred on the control row. */
.toolbar .tc { display: inline-flex; flex-direction: column; gap: 3px;
  font-size: 12px; color: var(--muted); }
.toolbar select,
.toolbar input:not([type="checkbox"]):not([type="radio"]):not([type="file"]) {
  height: 32px; padding: 4px 10px; border-radius: 8px;
  box-sizing: border-box; font-size: 13px; }
.toolbar .btn-ghost { border: 1px solid #c9dcc9; border-radius: 8px;
  padding: 5px 12px; height: 32px; box-sizing: border-box;
  color: #119043; background: #fff; font-size: 13px; }
.toolbar .btn-ghost:hover { border-color: #119043; color: #119043; }
.toolbar .btn-secondary { height: 32px; box-sizing: border-box; }
.toolbar > .muted, .toolbar > span.muted { padding-bottom: 7px; }
.muted { color: var(--muted); font-size: 13px; }
.error { color: var(--alarm); font-size: 13px; margin: 0; }

/* Points table */
.table-wrap, .chart-card, .card {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
}
.table-wrap { overflow: auto; max-height: calc(100vh - 190px); }
/* A table too wide for the column scrolls inside its own card rather
   than squeezing its columns until the headers wrap, and never makes
   the whole page scroll sideways. No border or background of its own:
   it sits inside a card that already has both. min-width:0 is what
   lets it shrink at all, since it is a flex child of the card. */
.table-scroll { overflow-x: auto; min-width: 0; }
.table-scroll > table { min-width: 640px; }
table { width: 100%; border-collapse: collapse; font-size: 14px; }
thead th {
  text-align: left; padding: 10px 12px; font-size: 12px; text-transform: uppercase;
  letter-spacing: 0.6px; color: var(--muted); background: var(--card);
  border-bottom: 2px solid var(--line); position: sticky; top: 0;
}
tbody td { padding: 9px 12px; border-bottom: 1px solid var(--line); }
tbody tr:hover { background: #f7fbf7; }
th.num, td.num { text-align: right; font-variant-numeric: tabular-nums; }
td.num { color: var(--green); font-weight: 700; }
td.num.flash { animation: flash 0.9s ease-out; }
@keyframes flash { from { background: rgba(146, 202, 79, 0.35); } to { background: transparent; } }
@media (prefers-reduced-motion: reduce) { td.num.flash { animation: none; } }

.pill {
  display: inline-block; padding: 2px 10px; border-radius: 999px; font-size: 12px;
  background: var(--bg); color: var(--muted);
  clip-path: polygon(8% 0, 92% 0, 100% 50%, 92% 100%, 8% 100%, 0 50%);
}
.pill.setpoint { background: rgba(27, 164, 207, 0.12); color: var(--blue); }
.pill.status-on { background: rgba(146, 202, 79, 0.25); color: var(--green); }
.pill.status-off { background: #eceff0; color: var(--muted); }
.pill.manual { background: rgba(146, 202, 79, 0.25); color: var(--green); }

.manual-cell { line-height: 1.4; }
.manual-detail { font-size: 12px; color: var(--muted); margin-top: 2px; }
.manual-detail.override-winning { color: var(--green); }
.manual-detail.override-overridden { color: var(--blue); }

/* Trends */
/* .chart-card is defined once, further down with the rest of the trend
   chart rules. It used to be declared here as well, with a different
   padding and a fixed height, and the later block quietly won both. */

/* Graphics */
.gfx-stage {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 16px; display: grid; place-items: center; min-height: 420px;
}
.gfx-stage svg { max-width: 100%; height: auto; }

/* Admin */
.admin-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 14px; }
.card { padding: 18px; display: flex; flex-direction: column; gap: 10px; }
.card h2 { margin: 0 0 4px; font-size: 16px; color: var(--green); }
.ctl-input-group + .ctl-input-group { margin-top: 10px; }
/* "wide" means "span the whole admin grid", so it is scoped to that
   grid. Unscoped it also applied inside the rail layouts, where it
   made a card span the navigation rail's column too and sit on top of
   the tree; that was harmless only while those layouts were flex, and
   they are grids now. Outside a grid it was always inert. */
.admin-grid .card.wide { grid-column: 1 / -1; }

/* Modal */
.modal-backdrop {
  position: fixed; inset: 0; background: rgba(23, 39, 28, 0.45);
  display: grid; place-items: center; z-index: 20;
}
.modal {
  width: min(380px, 92vw); background: var(--card); border-radius: var(--radius);
  border-top: 4px solid var(--blue); padding: 22px; display: flex; flex-direction: column; gap: 12px;
}
.modal h2 { margin: 0; font-size: 17px; }
.modal-actions { display: flex; justify-content: flex-end; gap: 10px; }

@media (max-width: 720px) {
  .tenant-col { display: none; }
  .panel { padding: 10px; }
}

/* BACnet bridge log tail in the admin panel. */
.log-tail {
  background: #17271c; color: #cfe6d2; border-radius: 8px;
  padding: 12px; font-size: 12px; line-height: 1.5;
  max-height: 220px; overflow-y: auto; white-space: pre-wrap;
  font-family: Consolas, 'Courier New', monospace; margin: 0;
}

/* Bridge editor form grid in the admin panel. */
.form-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 10px; margin: 12px 0;
}
#bridge-editor summary { cursor: pointer; color: var(--green); font-weight: bold; }
#bridges-table td button { margin-right: 4px; padding: 3px 8px; font-size: 12px; }

/* Analytics workbench layout. */
/* Rail plus main area. Four pages need this shape (Trends, Graphics,
   Maintenance, Dashboards) and each had grown its own: two grids and a
   flex row, with the rail 320px on one and 272px on the other three.
   One definition, one rail width, one breakpoint. */
.trend-layout, .gfx-wrap, .dash-grid {
  display: grid; grid-template-columns: var(--rail) 1fr;
  gap: var(--gap); align-items: start;
}
/* The rail has to span every row of the main column, and `grid-row: 1 /
   -1` does not do that. A negative line number counts back from the end
   of the EXPLICIT grid, and this grid declared no rows at all, so -1
   resolved to line 1 and the rail occupied row 1 only. Row 1 then grew
   to the rail's height, which on Trends is a tree of several thousand
   points capped at the viewport, so the tab strip in row 2 was pushed
   most of a screen down and the space above it was empty. Declaring the
   rows is what makes -1 mean what it says. There are three in the main
   column: the period toolbar, the tab strip, and whichever analysis
   pane is open (the other six are display:none and occupy no row).
   Spanning a large number instead is not the fix: an over-span creates
   that many empty tracks and the row gap between every one of them,
   which is the same blank space by another route. */
.trend-layout { grid-template-rows: auto auto auto; }
.trend-tree { grid-row: 1 / -1; position: sticky; top: 14px;
  max-height: calc(100vh - 28px); overflow: auto; }
.trend-tree input { width: 100%; margin-bottom: 8px; }
.trend-bottom-card { grid-column: 2; }
/* The analysis subnav and its panes are the grid items now, one pane
   visible at a time; the cards inside them stack in the pane rather
   than in the grid, which is why the pane carries the column and the
   gap. The rule above stays for print, where .trend-layout becomes a
   plain block and every card is laid out again from scratch. */
.trend-main, .trend-analysis-nav, .trend-analysis { grid-column: 2; }
.trend-analysis { display: flex; flex-direction: column; gap: 14px; }
/* The strip is glued to the period controls above it: they scope every
   pane below, so the switch and the scope read as one control rather
   than as two things separated by a graphic. */
.trend-analysis-nav { margin-top: 0; margin-bottom: 0; padding: 0; }
@media (max-width: 900px) {
  .trend-layout, .gfx-wrap, .dash-grid { grid-template-columns: 1fr; }
  /* One column, so the rail stops spanning and the three declared rows
     would fight the stacked order. Back to fully implicit rows. */
  .trend-layout { grid-template-rows: none; }
  .trend-tree { position: static; grid-row: auto; max-height: 40vh; }
  .trend-bottom-card { grid-column: 1; }
  .trend-main, .trend-analysis-nav, .trend-analysis { grid-column: 1; }
  #pt-tree, #gfx-tree, #mxq-tree, #dash-list {
    width: auto; max-height: 40vh; }
}
#obj-tree details { margin: 2px 0 2px 8px; }
#obj-tree summary { cursor: pointer; color: var(--green-dark, #119043); font-weight: bold; font-size: 13px; }
#obj-tree label { display: block; margin-left: 22px; font-size: 13px; padding: 1px 0; }
#obj-tree label input { margin-right: 6px; }

/* Trends polish: aligned controls, checkbox-left tree rows. */
#panel-trends .toolbar label { display: inline-flex; align-items: center; gap: 6px; margin: 0; }
#panel-trends .toolbar input[type="datetime-local"] { padding: 4px 6px; }
#obj-tree .pt-row { display: flex; align-items: flex-start; gap: 6px;
  margin-left: 22px; font-size: 13px; padding: 2px 0; }
#obj-tree .pt-row label { display: flex; align-items: flex-start; gap: 7px;
  flex: 1; margin: 0; cursor: pointer; }
#obj-tree .pt-row input[type="checkbox"] { margin: 3px 0 0; flex: none; }
#obj-tree .pt-type { font-size: 11px; padding: 1px 2px; max-width: 92px; flex: none; }
#tree-unit { width: 100%; margin-bottom: 8px; }

/* Trends shine pass: click-select rows, dots, chips, aligned toolbar. */
#obj-tree .pt-row { display: flex; align-items: center; gap: 8px;
  margin: 2px 0 2px 18px; padding: 4px 8px; font-size: 13px;
  border: 1.5px solid transparent; border-radius: 7px; cursor: pointer; }
#obj-tree .pt-row:hover { background: #eef6ee; }
#obj-tree .pt-row.sel { background: #92CA4F1f; border-color: #92CA4F; }
#obj-tree .pt-dot { width: 10px; height: 10px; border-radius: 50%;
  flex: none; background: transparent; }
#obj-tree .pt-name { flex: 1; line-height: 1.35; }
#obj-tree .pt-meta { color: #5a6b5e; font-size: 11px; }
#obj-tree .pt-unit { flex: none; font-size: 11px; color: #0d6e33;
  background: #e3f0e3; border: 1px solid #c6e0c6; border-radius: 9px;
  padding: 0 7px; }
#obj-tree .pt-type { flex: none; font-size: 11px; padding: 1px 2px;
  max-width: 88px; border: 1px solid #92CA4F; border-radius: 5px;
  color: #0d6e33; background: #fff; }
#obj-tree summary { cursor: pointer; color: #119043; font-weight: bold;
  font-size: 13px; padding: 3px 0; }
#obj-tree summary::marker { color: #92CA4F; }
#obj-tree details { margin-left: 10px; border-left: 1px solid #e0ece0;
  padding-left: 4px; }

.tc-bar { align-items: end; }
.tc-bar .tc { display: inline-flex; flex-direction: column; gap: 3px;
  font-size: 11px; color: #5a6b5e; margin: 0; }
.tc-bar .tc select, .tc-bar .tc input { font-size: 13px; }
.tc-quicks { display: inline-flex; gap: 4px; align-self: end; }
.tc-quicks .btn-ghost { border: 1px solid #92CA4F; border-radius: 12px;
  padding: 3px 10px; font-size: 12px; color: #0d6e33; }
.tc-quicks .btn-ghost:hover { background: #92CA4F33; }
#tc-load { align-self: end; }
#tc-export { align-self: end; }

.chart-card { position: relative; min-height: 430px; padding: 14px;
  border-radius: 10px; box-shadow: 0 1px 4px rgba(23, 39, 28, 0.10); }
#trend-chart { max-height: 430px; }
#chart-empty { position: absolute; inset: 0; display: flex;
  align-items: center; justify-content: center; color: #5a6b5e;
  font-size: 14px; pointer-events: none; }

/* Sites communication page. */
.site-dot { display: inline-block; width: 12px; height: 12px; border-radius: 50%; }
.site-dot.ok { background: #119043; }
.site-dot.fault { background: #17271c; outline: 2px solid #17271c33; }
.site-dot.paused { background: #1BA4CF; }
.site-dot.unknown { background: #c6d4c8; }
.site-fault-text { color: #17271c; font-weight: bold; }
#site-log { max-height: 260px; overflow: auto; background: #f3f8f3;
  border: 1px solid #d7e8d7; border-radius: 6px; padding: 10px; font-size: 12px; }

/* Classes replacing what were inline style attributes, so the CSP can drop
   'unsafe-inline' from style-src entirely. */
.gfx-generic-wrap { width: 100%; }
.gfx-generic-title { color: var(--green); margin-top: 0; }
.app-version-badge { font-size: 11px; margin-right: 10px; }
.docs-link { text-decoration: none; }
.li-path-input { min-width: 280px; }

/* Admin and section subpages (2026.08.8-31): the sub navigation now
   speaks the exact same visual grammar as the main tabs (solid green
   pill when active, quiet tint on hover), sized uniformly so every
   subtab reads as a first class navigation element. One rule set
   covers Admin, Maintenance and Bus Systems alike. */
.subnav { display: flex; gap: 4px; flex-wrap: wrap;
  background: var(--card); border: 1px solid var(--line);
  border-radius: 10px; padding: 5px; margin: 0 0 16px; }
.subnav .subtab { flex: 1 1 auto; min-width: 84px; text-align: center;
  background: transparent; border: none; border-radius: 8px;
  padding: 8px 14px; font-family: inherit; font-size: 14px;
  line-height: 1.2; color: var(--muted); cursor: pointer;
  white-space: nowrap;
  transition: background .15s ease, color .15s ease,
    box-shadow .15s ease; }
.subnav .subtab:hover:not(.active) { background: var(--bg);
  color: var(--ink); }
.subnav .subtab.active { background: var(--green); color: #fff;
  font-weight: 600; box-shadow: 0 2px 6px rgba(17, 144, 67, 0.25); }
.subnav .subtab:focus-visible { outline: 2px solid var(--blue);
  outline-offset: 1px; }
@media (max-width: 720px) {
  .subnav { flex-wrap: nowrap; overflow-x: auto;
    -webkit-overflow-scrolling: touch; }
  .subnav .subtab { flex: 0 0 auto; min-width: 0;
    padding: 8px 12px; font-size: 13px; }
}
.admin-sub { display: block; }

/* Security-rule fieldset on the create-user card (item 10). */
fieldset.sec-rules { border: 1px solid #d7e8d7; border-radius: 6px;
  padding: 10px 12px; margin: 8px 0; }
fieldset.sec-rules legend { color: #119043; font-weight: bold; padding: 0 6px; }
fieldset.sec-rules label.chk { display: flex; align-items: center; gap: 8px;
  font-weight: normal; margin: 4px 0; }
fieldset.sec-rules label.chk input { width: auto; }
.row-actions { white-space: nowrap; }
.row-actions .btn-ghost { margin-right: 4px; }

/* Runtime: time in state, time in range. */
.rt-num-input { width: 100px; }
#runtime-card table { margin-top: 8px; }

/* Inline per-point display-unit override (Points tab). */
.unit-override-sel { font-size: 11px; padding: 1px 3px; margin-left: 4px;
  border: 1px solid #d7e8d7; border-radius: 4px; background: #f6faf6;
  color: #4a5a4d; vertical-align: middle; }

/* Time-in-range distribution: stacked per-range bar charts. */
.rng-bar-card { margin-top: 14px; }
#rng-table input[type="text"] { width: 100%; }

/* Trends report printing. The header is built only for print; on screen it
   stays out of the way entirely. */
#print-report-header { display: none; }

@media print {
  @page { size: landscape; margin: 10mm; }

  .topbar, #tabs, .subnav, .trend-tree { display: none !important; }

  /* A report carries every analysis, not only the tab that happened to
     be open when Print was pressed. The panes are hidden on screen one
     at a time; on paper they are all revealed, and the per-card rules
     below then decide which ones actually produced a result and so
     earn a page. Without this, splitting the page into tabs would have
     silently reduced the report to a single graphic. */
  .trend-analysis[hidden], .trend-analysis {
    display: block !important; }

  /* Configuration controls, including their label text, are not report
     content: hide the whole toolbar/form-grid/table containers rather
     than just the input/select/button inside them, or a label like "From"
     or "Step" is left stranded with nothing next to it. The "which points
     are selected" notes and every result area are direct children of the
     card, not nested inside a toolbar, so they are unaffected. */
  .trend-main .toolbar, .trend-bottom-card .toolbar,
  .trend-bottom-card .form-grid, .trend-bottom-card table {
    display: none !important;
  }
  .trend-bottom-card > p.muted:not(.print-keep) { display: none !important; }
  #chart-empty { display: none !important; }

  #print-report-header { display: block !important; margin-bottom: 10px; }
  #print-report-header h1 { color: #119043; margin: 0 0 4px; }

  .trend-layout { display: block !important; }

  /* One graphic per page: a card that has actually produced a result
     breaks to its own page; a card never used (no chart, no rt-result
     content, no carpet/Sankey drawn) does not consume a blank page. */
  /* The trend itself is a pane now, not a fixed block inside
     .trend-main. Breaking after .trend-main would leave a page
     carrying nothing but the report header, since its only other
     child is the toolbar and a toolbar is configuration rather than
     report content. The header therefore flows into the trend, and
     the trend breaks after itself, but only once one has actually
     been drawn (drawChart marks the pane). */
  .trend-analysis.has-chart,
  .trend-bottom-card:has(.chart-card:not([hidden])),
  .trend-bottom-card:has(#rt-result:not(:empty)),
  .trend-bottom-card:has(#cp-wrap:not([hidden])),
  .trend-bottom-card:has(#sk-wrap:not([hidden])) {
    page-break-after: always; break-after: page;
    page-break-inside: avoid; break-inside: avoid;
    margin: 0 0 0;
  }
  .trend-bottom-card:last-of-type {
    page-break-after: auto !important; break-after: auto !important;
  }

  h2 { margin: 0 0 6px; }

  /* Each graphic takes the full usable height of its own page; Chart.js
     canvases already ignore their aspect ratio (maintainAspectRatio is
     off everywhere in this app) and stretch to fill whatever height they
     are given, so this alone is enough for every Chart.js-based card. */
  .chart-card { height: 165mm; page-break-inside: avoid; }
  #rt-result .chart-card, .rng-bar-card { height: auto; min-height: 110mm; }

  /* The carpet plot and Sankey diagram are custom SVG/grid renderers, not
     Chart.js, so each needs its own explicit full-height rule. */
  #cp-wrap, #sk-wrap { height: 165mm; }
  #sk-wrap svg { height: 165mm; max-height: 165mm; }
  .carpet-grid { height: 150mm !important; grid-template-rows: 20px repeat(24, 1fr) !important; }
  .carpet-cell { min-height: 0; }
}

/* Carpet plot: a day-by-hour heatmap grid. */
.carpet-grid { display: grid; gap: 1px; margin-top: 8px; }
.carpet-corner { }
.carpet-daylabel { font-size: 9px; text-align: center; color: #5a6b5e;
  overflow: hidden; white-space: nowrap; }
.carpet-hourlabel { font-size: 10px; color: #5a6b5e; text-align: right;
  padding-right: 4px; white-space: nowrap; }
.carpet-cell { min-height: 11px; border-radius: 1px; }
.carpet-cell-empty { background: repeating-linear-gradient(45deg, #eee, #eee 3px, #fff 3px, #fff 6px); }
.carpet-legend { display: flex; align-items: center; gap: 2px; margin-bottom: 10px;
  font-size: 12px; color: #5a6b5e; }
.carpet-legend-swatch { width: 20px; height: 14px; }

/* Trends top toolbar: two rows, pinned to the top of the trend-main column
   as the long page of analysis cards scrolls, so the period/aggregate/
   chart controls are always reachable without scrolling back up. */
.tc-bar {
  position: sticky; top: 14px; z-index: 5;
  background: var(--card); flex-direction: column;
  align-items: stretch; gap: 8px; padding: 10px 12px;
  border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.08);
}
.tc-row1, .tc-row2 {
  display: flex; gap: 10px; align-items: center; flex-wrap: wrap;
}

/* Sankey flow chart wrapper. */
#sk-wrap { margin-top: 8px; }
#sk-wrap svg { width: 100%; height: auto; max-height: 520px; }
#sk-parent-config table { margin: 8px 0; }

/* Alarms dashboard. */
.al-summary { margin: 10px 0; }
.al-summary-pill { display: inline-block; background: var(--bg); border: 1px solid var(--line);
  border-radius: 999px; padding: 4px 12px; margin-right: 6px; font-size: 13px; }

/* AI-proposed action cards. */
.pa-card { border: 1px solid var(--line); border-radius: 8px; padding: 12px 14px;
  margin-bottom: 10px; background: #fff; }
.pa-card p { margin: 4px 0; }
.pill.approved { background: rgba(146, 202, 79, 0.25); color: var(--green); }
.pill.rejected { background: #eceff0; color: var(--muted); }

/* Alarms tab toolbar: sticky like the Trends toolbar, but a single row
   (no row1/row2 split, that structure is specific to Trends). */
.alarms-bar {
  position: sticky; top: 14px; z-index: 5;
  background: var(--card); padding: 10px 12px;
  border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.08);
  flex-wrap: wrap;
}

/* Phone-width layout: the AI and Alarms tabs are the ones meant to be
   used one-handed, on the move, so this pass focuses there and on the
   chrome every tab shares (top nav, cards, buttons), rather than trying
   to force the wide analysis grids (Trends, Admin tables) into a phone
   width where they would only become harder to read. */
@media (max-width: 600px) {
  .topbar-inner { padding: 8px 10px; gap: 8px; }
  .topbar .brand-mark.small h1 { font-size: 15px; }
  #tabs { gap: 2px; }
  #tabs button { padding: 7px 9px; font-size: 13px; }
  .session { gap: 6px; font-size: 12px; }
  .session .docs-link { display: none; }  /* the guide is a desktop reference, not a phone task */

  /* .panel padding is already 10px from the 720px block below this
     width; it was set twice to the same value. */
  .card { padding: 12px; }
  .form-grid { grid-template-columns: 1fr; }

  /* Every primary action gets a full-width, thumb-sized tap target. */
  #ai-ask, #al-run-basic, #al-run-advanced, .pa-approve, .pa-reject,
  #ai-refresh-pending, #al-detect {
    width: 100%; padding: 12px; font-size: 15px;
  }
  .toolbar, .alarms-bar { flex-direction: column; align-items: stretch; }
  .toolbar > *, .alarms-bar > * { width: 100%; }
  .toolbar label, .alarms-bar label { display: flex; flex-direction: column; gap: 4px; }

  .pa-card { padding: 10px; }
  .al-summary-pill { display: block; margin: 4px 0; text-align: center; }
}

/* Weekly schedule calendar viewer. */
.week-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 6px; margin-top: 8px; }
.week-day { border: 1px solid var(--line); border-radius: 6px; padding: 6px; font-size: 12px; min-height: 60px; }
.week-day-name { font-weight: bold; color: var(--green); text-transform: capitalize; margin-bottom: 4px; }
.week-entry { padding: 2px 0; border-bottom: 1px dashed var(--line); }
.week-entry:last-child { border-bottom: none; }
@media (max-width: 600px) {
  .week-grid { grid-template-columns: repeat(2, 1fr); }
}

/* Alarms dashboard cards (2026.07.19-6) */
.al-dash { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 12px; margin: 0 0 14px; }
.al-card { background: var(--card); border: 1px solid var(--line); border-radius: 10px;
  padding: 14px; border-top: 4px solid var(--line); cursor: pointer; }
.al-card .n { font-size: 30px; font-weight: 700; }
.al-card .t { color: var(--muted); font-size: 13px; }
.al-card.c-critical { border-top-color: #c0392b; }
.al-card.c-warning { border-top-color: #e0a800; }
.al-card.c-fault { border-top-color: #7d5ba6; }
.al-card.c-device { border-top-color: #1BA4CF; }
.al-card.c-info { border-top-color: #92CA4F; }
.al-card.active { outline: 2px solid var(--accent, #119043); }

@media (max-width: 640px) {
  .al-dash { grid-template-columns: repeat(2, 1fr); }
}

/* Points page usability + Bus Systems (2026.07.19-7) */
tr.kind-sensor { background: rgba(146, 202, 79, .08); }
tr.kind-setpoint { background: rgba(27, 164, 207, .10); }
tr.kind-status { background: rgba(125, 91, 166, .08); }
.group-row { background: var(--bg); font-weight: 600; cursor: pointer; }
.group-row .caret { display: inline-block; width: 14px; color: var(--muted); }
th .sort-lbl { cursor: pointer; user-select: none; white-space: nowrap; }
th .filter-btn { background: none; border: none; cursor: pointer; color: var(--muted);
  padding: 0 2px; font-size: 12px; }
th .filter-btn.active { color: #119043; font-weight: 700; }
.col-pop { position: absolute; z-index: 60; background: var(--card);
  border: 1px solid var(--line); border-radius: 8px; padding: 8px 10px;
  max-height: 280px; overflow: auto; box-shadow: 0 6px 20px rgba(0, 0, 0, .18);
  min-width: 170px; }
.col-pop label { display: block; font-size: 13px; padding: 2px 0; }
.col-pop .pop-links { margin-bottom: 6px; font-size: 12px; }
.col-pop .pop-links a { cursor: pointer; color: #1BA4CF; margin-right: 10px; }
.cov-dot { width: 10px; height: 10px; border-radius: 50%; display: inline-block;
  background: #c8cfc9; }
.cov-live { background: #119043; }
.cov-sub { background: #1BA4CF; }
.cov-dead { background: #e0a800; }
.cov-fail { background: #c0392b; }
.cov-none { background: transparent; border: 1px solid var(--line); }
.bt-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin: 12px 0; }
.bt-wrap { max-height: 420px; overflow: auto; }

@media (max-width: 640px) {
  .bt-grid { grid-template-columns: 1fr; }
}

/* ---- ISA-18.2 lifecycle chips (Alarms tab, live engine, 2026.07.19-10) */
.isa { display: inline-block; padding: 2px 8px; border-radius: 10px;
       font-size: 11px; font-weight: 700; letter-spacing: .3px;
       color: #fff; white-space: nowrap; }
.isa-unack { background: #c0392b; animation: isa-blink 1.2s ease-in-out infinite; }
.isa-acked { background: #119043; }
.isa-rtn_unack { background: #e0a800; }
.isa-shelved { background: #5a6b5e; }
@keyframes isa-blink { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }
.row-shelved { opacity: .55; }
.al-acts { white-space: nowrap; }
.al-acts .btn-mini { margin-right: 4px; }
.btn-mini {
  background: #fff; color: var(--green); border: 1px solid var(--green);
  border-radius: 6px; padding: 3px 9px; font-size: 12px;
}
.btn-mini:hover { background: var(--bg); }
.btn-mini:disabled { opacity: .5; }

/* ---- Alarm quality panel (2026.07.19-13) */
.al-card.al-warn { border-color: #c0392b; }
.al-card.al-warn .n { color: #c0392b; }
.flood-child td:first-child { padding-left: 26px; }

/* ---- Graphics navigator (2026.07.20-4) ---- */
/* Shape defined once with .trend-layout and .dash-grid above. */
#gfx-tree { width: var(--rail); max-height: 74vh; overflow: auto;
  border: 1px solid #d7e8d7; border-radius: 8px; padding: 8px;
  background: #fff; }
.gfx-main { flex: 1; min-width: 0; }
#gfx-crumbs { margin: 0 0 10px; }
.gfx-crumb { background: none; border: none; color: #119043;
  font-weight: bold; cursor: pointer; padding: 0; font: inherit; }
.gfx-crumb:hover { text-decoration: underline; }
.gfx-crumb-sep { color: #5a6b5e; margin: 0 6px; }
#gfx-tree details { margin: 2px 0 2px 6px; }
#gfx-tree summary { cursor: pointer; font-weight: bold; color: #119043;
  user-select: none; }
#gfx-tree summary .muted { font-weight: normal; }
.gfx-eq { display: block; width: 100%; text-align: left; background: none;
  border: none; padding: 2px 6px; cursor: pointer; border-radius: 4px;
  font: inherit; }
.gfx-eq:hover { background: #eef6ee; }
.gfx-eq .dot { display: inline-block; width: 8px; height: 8px;
  border-radius: 50%; background: #92CA4F; margin-right: 6px; }
.gfx-eq .dot.off { background: #d7e8d7; }
.gfx-eq .alm { background: #c62828; color: #fff; border-radius: 8px;
  padding: 0 6px; font-size: 11px; margin-left: 6px; }
.gfx-link { cursor: pointer; }
.gfx-link:hover { opacity: .78; outline: 2px solid #1BA4CF;
  outline-offset: 2px; }
.gfx-tiles { display: grid; gap: 10px;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); }
.gfx-tile { border: 1px solid #d7e8d7; border-radius: 8px;
  padding: 12px; background: #fff; cursor: pointer; text-align: left;
  font: inherit; }
.gfx-tile:hover { border-color: #119043; }
.gfx-tile h4 { margin: 0 0 4px; color: #119043; }
.gfx-sibs { margin: 10px 0 0; }
.gfx-sibs .btn-mini { margin: 0 6px 6px 0; }

/* ---- Points navigator (2026.07.20-5) ---- */
#pt-tree { width: var(--rail); max-height: 74vh; overflow: auto;
  border: 1px solid #d7e8d7; border-radius: 8px; padding: 8px;
  background: #fff; }
#pt-tree details { margin: 2px 0 2px 6px; }
#pt-tree summary { cursor: pointer; font-weight: bold; color: #119043;
  user-select: none; }
#pt-tree summary .muted { font-weight: normal; }
.pt-gfx { float: right; color: #1BA4CF; font-weight: bold; }
#pt-navchip .pill { padding: 2px 8px; }

/* ---- Maintenance hub tree (design parity, 2026.07.20-13) ---- */
#mxq-tree { width: var(--rail); max-height: 74vh; overflow: auto;
  border: 1px solid #d7e8d7; border-radius: 8px; padding: 8px;
  background: #fff; }
#mxq-tree details { margin: 2px 0 2px 6px; }
#mxq-tree summary { cursor: pointer; font-weight: bold; color: #119043;
  user-select: none; }
#mxq-tree summary .muted { font-weight: normal; }

/* ---- Tree view tabs (2026.07.20-26; -29 unifies them with the page
   sub tab grammar: left packed, left aligned, underline active). ---- */
.tree-tabs { display: flex; gap: 4px; border-bottom: 2px solid #d7e8d7;
  margin-bottom: 8px; flex-wrap: wrap; }
.tree-tabs .tt { flex: none; background: transparent; border: none;
  border-bottom: 3px solid transparent; padding: 6px 10px;
  font: inherit; font-size: 13px; color: #4a5a4d; cursor: pointer;
  text-align: left; }
.tree-tabs .tt:hover { color: #119043; }
.tree-tabs .tt.on { color: #119043; border-bottom-color: #119043;
  font-weight: bold; }

/* ---- Trends tree and scoped point list (2026.07.20-29) ---- */
#tr-tree { max-height: 46vh; overflow: auto; border: 1px solid #d7e8d7;
  border-radius: 8px; padding: 8px; background: #fff;
  margin-bottom: 8px; }
#tr-tree details { margin: 2px 0 2px 6px; }
#tr-tree summary { cursor: pointer; font-weight: bold; color: #119043;
  user-select: none; }
#tr-tree summary .muted { font-weight: normal; }
#tr-navchip .pill { padding: 2px 8px; }
.tr-eq-head { margin: 8px 0 2px; font-size: 12px; font-weight: bold;
  color: #0d6e33; }

.modal-quick { display: flex; gap: 10px; margin: 4px 0 8px; }
.modal-quick .btn-secondary { flex: 1; }

/* ---- Dashboards (2026.07.20-34) ---- */
/* Shape defined once with .trend-layout and .gfx-wrap above. */
#dash-list { width: var(--rail); border: 1px solid #d7e8d7; border-radius: 8px; padding: 8px;
  background: #fff; max-height: 74vh; overflow: auto; }
#dash-view { min-height: 200px; }
.dash-print-head { display: none; }
.dash-graphic svg { width: 100%; height: auto; display: block; }

@media print {
  header, #tabs, .panel, .modal-backdrop { display: none !important; }
  #panel-dash { display: block !important; }
  #panel-dash .no-print, #dash-list { display: none !important; }
  .dash-grid { display: block; }
  .dash-print-head { display: block; border-bottom: 3px solid #119043;
    margin-bottom: 12px; padding-bottom: 8px; }
  .dash-print-head h1 { color: #119043; margin: 0 0 4px; }
  body { background: #fff; }
  .ai-view, canvas, .dash-graphic { break-inside: avoid; }
  #dash-view { min-height: 0; }
}

/* ---- Authored room and floor graphics (2026.07.20-30; -31 makes
   the wrapper stretch: the stage is a centering grid, so a plain div
   shrink fits and collapsed the inner width 100% SVG). ---- */
.gfx-authored { margin: 0 0 12px; width: 100%; justify-self: stretch; }
.gfx-authored svg { width: 100%; height: auto; display: block; }

/* Disclosure rows: the expand triangle and the label share one line
   (2026.07.20-28). */
summary > .gfx-eq { display: inline; width: auto; padding: 2px 4px; }

/* ---- Control Engine (C1) ---- */
#ctl-src { width: 100%; font-family: Consolas, monospace; font-size: 13px;
  background: #0f1713; color: #d6efe0; border: 1px solid #2a3b32;
  border-radius: 6px; padding: 8px; box-sizing: border-box; }
.ctl-err { color: #b03030; min-height: 1.2em; font-size: 13px; }
#ctl-ev-table td, #ctl-ev-table th { padding: 2px 10px; }

/* The Scan network action: prominent by design, brand green. */
.btn-scan {
  background: linear-gradient(180deg, #92CA4F 0%, #119043 100%);
  color: #fff; font-weight: bold; border: none;
  padding: 9px 18px; border-radius: 8px; cursor: pointer;
  box-shadow: 0 2px 6px rgba(17, 144, 67, 0.35);
  font-family: 'Trebuchet MS', sans-serif;
}
.btn-scan:hover { filter: brightness(1.06);
  box-shadow: 0 3px 9px rgba(17, 144, 67, 0.45); }
.btn-scan:active { transform: translateY(1px); }
.scan-out-box {
  border: 1px solid #92CA4F; border-radius: 8px;
  background: #92CA4F14; padding: 8px 12px; margin-top: 6px;
}


/* ---- AI provider table and modern controls (2026.07.20-86) ---- */
.ai-table { width: 100%; border-collapse: collapse; margin: 6px 0 12px; }
.ai-table th { text-align: left; font-size: 12px; text-transform: uppercase;
  letter-spacing: .04em; color: var(--muted); font-weight: 600;
  padding: 8px 10px; border-bottom: 2px solid var(--green-light); }
.ai-table td { padding: 8px 10px; border-bottom: 1px solid #e6ece7;
  vertical-align: middle; }
.ai-table tr:hover td { background: #f5faf6; }
.ai-table td:first-child { font-weight: 600; white-space: nowrap; }
.ai-table input[type="password"], .ai-table select {
  height: 34px; border: 1px solid #cfd8d1; border-radius: 8px;
  padding: 0 10px; font: inherit; background: #fff; width: 100%;
  box-sizing: border-box; }
.ai-table input[type="password"]:focus, .ai-table select:focus {
  outline: none; border-color: var(--green);
  box-shadow: 0 0 0 3px rgba(17,144,67,.15); }
.ai-table .cell-flex { display: flex; gap: 6px; align-items: center; }

.chip { display: inline-block; padding: 3px 10px; border-radius: 999px;
  font-size: 12px; font-weight: 600; white-space: nowrap; }
.chip-ok { background: rgba(17,144,67,.12); color: var(--green); }
.chip-off { background: #eef0ee; color: var(--muted); }

.btn-2026 { height: 34px; padding: 0 14px; border-radius: 8px;
  border: 1px solid var(--green); background: #fff; color: var(--green);
  font: inherit; font-weight: 600; cursor: pointer;
  transition: background .15s, color .15s, box-shadow .15s; }
.btn-2026:hover:not(:disabled) { background: var(--green); color: #fff;
  box-shadow: 0 2px 8px rgba(17,144,67,.25); }
.btn-2026:disabled { opacity: .45; cursor: default; }

.bz-switch { position: relative; display: inline-block; width: 40px;
  height: 22px; vertical-align: middle; }
.bz-switch input { opacity: 0; width: 0; height: 0; }
.bz-switch .track { position: absolute; inset: 0; border-radius: 999px;
  background: #cfd8d1; transition: background .15s; cursor: pointer; }
.bz-switch .track::after { content: ""; position: absolute; top: 3px;
  left: 3px; width: 16px; height: 16px; border-radius: 50%;
  background: #fff; transition: transform .15s;
  box-shadow: 0 1px 3px rgba(0,0,0,.25); }
.bz-switch input:checked + .track { background: var(--green); }
.bz-switch input:checked + .track::after { transform: translateX(18px); }
.bz-switch input:disabled + .track { opacity: .4; cursor: default; }

.bz-radio { appearance: none; width: 20px; height: 20px;
  border: 2px solid #cfd8d1; border-radius: 50%; cursor: pointer;
  vertical-align: middle; transition: border-color .15s; }
.bz-radio:checked { border-color: var(--green);
  background: radial-gradient(circle, var(--green) 0 45%, transparent 50%); }
.bz-radio:disabled { opacity: .4; cursor: default; }

.ai-fdd-panel { display: flex; gap: 14px; align-items: center;
  flex-wrap: wrap; border: 1px solid #e0e8e2; border-radius: 10px;
  padding: 10px 14px; background: #fafcfa; margin-top: 4px; }
.ai-fdd-panel b { color: var(--green); }


/* ---- Inline alarm explain panel (2026.07.20-90) ---- */
.al-expand > td { padding: 0 !important; background: #f7fbf8; }
.al-expand-box { padding: 12px 16px; border-left: 3px solid var(--green);
  margin: 6px 0 10px; line-height: 1.5; }
.al-expand-box pre.al-evidence { white-space: pre-wrap;
  background: #fff; border: 1px solid #e0e8e2; border-radius: 8px;
  padding: 10px; font-size: 12px; max-height: 320px; overflow: auto; }
.al-expand-box details summary { cursor: pointer; }

/* ---- Bus Systems, Transport sub-tab (2026.08.1-3) ---- */
.tstate { display: inline-block; padding: 2px 10px; border-radius: 10px;
          font-size: 12px; font-weight: 600; }
.ts-ver { background: #119043; color: #fff; }
.ts-sub { background: #1BA4CF; color: #fff; }
.ts-def { background: #e4efe6; color: #2e4033; }
.ts-bad { background: var(--alarm, #c0392b); color: #fff; }
.tv-table { width: 100%; }
.tv-table td, .tv-table th { padding: 4px 8px; }
.tv-result { max-width: 320px; }

/* ---- MQTT inspector (2026.08.1-4) ---- */
.mq-topic { font-family: monospace; font-size: 12px; }
.mq-payload { font-family: monospace; font-size: 12px; max-width: 420px;
              overflow-wrap: anywhere; }

/* Relations canvas (REL-001/002, 2026.08.4-1) */
#rc-flow { height: 540px; border: 1px solid #d5e6d8; border-radius: 8px;
  background: #fbfdfb; position: relative; overflow: hidden; }
#rc-flow .drawflow .drawflow-node { background: #ffffff;
  border: 2px solid #119043; border-radius: 8px; min-width: 130px;
  padding: 6px 10px; font-family: "Trebuchet MS", sans-serif;
  font-size: 13px; color: #222222; box-shadow: 0 1px 4px rgba(0,0,0,.08); }
#rc-flow .drawflow-node.selected { border-color: #1BA4CF; }
#rc-flow .drawflow-node .input,
#rc-flow .drawflow-node .output { background: #92CA4F;
  border: 2px solid #119043; height: 12px; width: 12px; }
#rc-flow .connection .main-path { stroke: #119043; stroke-width: 3px; }
#rc-flow .connection.rel-supplies-air .main-path { stroke: #1BA4CF; }
#rc-flow .connection.rel-supplies-chw .main-path { stroke: #0E6E33; }
#rc-flow .connection.rel-supplies-hw .main-path { stroke: #666666; }
#rc-flow .connection.rel-supplies-power .main-path { stroke: #92CA4F; }
#rc-flow .connection.rel-shares-plant .main-path { stroke: #999999;
  stroke-dasharray: 6 4; }
.rc-chip { display: inline-flex; align-items: center; gap: 5px;
  margin-right: 12px; font-size: 12px; }
.rc-dot { width: 11px; height: 11px; border-radius: 3px;
  display: inline-block; }

/* Equipment editor v2 (CTRL-201/202/203, 2026.08.5-1) */
.ctl-eq-row td { background: #f2f8ec; border-top: 2px solid #92CA4F; }
.ctl-eq-wrap { display: flex; gap: 14px; align-items: flex-start;
  padding: 8px 4px; }
.ctl-pool { flex: 0 0 270px; max-height: 340px; overflow-y: auto; }
.ctl-lines { flex: 1; min-width: 0; }
.ctl-chip { display: inline-block; margin: 3px; padding: 3px 9px;
  border: 1px solid #92CA4F; border-radius: 12px; background: #fff;
  cursor: grab; user-select: none; touch-action: none;
  font-family: "Trebuchet MS", sans-serif; font-size: 0.85em; }
.ctl-chip b { color: #119043; font-weight: 600; margin-left: 4px; }
.ctl-chip i { color: #1BA4CF; font-style: normal; margin-left: 4px;
  font-size: 0.85em; }
.ctl-chip.sel { background: #92CA4F; color: #fff; }
.ctl-chip.sel b, .ctl-chip.sel i { color: #fff; }
.ctl-ghost { position: fixed; z-index: 999; pointer-events: none;
  opacity: 0.85; box-shadow: 0 2px 8px rgba(17, 144, 67, 0.35); }
.ctl-line { border: 1px solid #dde7d2; border-radius: 8px;
  padding: 6px 10px; margin-bottom: 8px; background: #fff; }
.ctl-line-head { margin-bottom: 4px; }
.ctl-line-slots { display: flex; gap: 10px; flex-wrap: wrap; }
.ctl-slot { flex: 1; min-width: 130px; border: 1.5px dashed #1BA4CF;
  border-radius: 8px; padding: 5px 9px; cursor: pointer;
  background: #f6fbfd; }
.ctl-slot em { font-style: normal; font-weight: 700; color: #1BA4CF;
  margin-right: 6px; font-size: 0.8em; }
.ctl-slot:hover { background: #e3f3f9; }
.ctl-line-cfg { margin-top: 5px; font-size: 0.88em; }
.ctl-line-cfg input { font-family: inherit; }

/* Setpoint sources and chain badges (CTRL-204/205/206, 2026.08.5-2) */
.ctl-sp-block { flex: 1; min-width: 150px; display: flex; gap: 5px;
  align-items: center; border: 1.5px dashed #92CA4F; border-radius: 8px;
  padding: 4px 8px; background: #f6fbf0; }
.ctl-sp-block .ctl-slot { border: none; background: transparent;
  padding: 0 2px; min-width: 60px; }
.ctl-spsrc, .ctl-spcas { font-family: inherit; font-size: 0.85em; }
.ctl-badge { display: inline-block; margin-left: 8px; padding: 1px 8px;
  border-radius: 10px; background: #1BA4CF; color: #fff;
  font-size: 0.75em; }
.ctl-badge.ctl-feeds { background: #119043; }

/* dash-v2 (DASH-001/002, 2026.08.6-1) */
.dash-card { background: #fff; border: 1px solid #e0e0e0;
  border-radius: 10px; padding: 10px 12px; overflow: auto;
  font-family: "Trebuchet MS", sans-serif;
  box-shadow: 0 2px 6px rgba(17, 144, 67, 0.10); }
.grid-stack-item.ui-draggable-dragging .dash-card,
.grid-stack-item.ui-resizable-resizing .dash-card {
  box-shadow: 0 6px 16px rgba(27, 164, 207, 0.25);
  border-color: #1BA4CF; }
.grid-stack:not(.grid-stack-static) .dash-card {
  border-style: dashed; border-color: #92CA4F; }
.dash-w-title { font-weight: 700; color: #119043;
  margin-bottom: 4px; }
.dash-gauge { width: 100%; max-width: 220px; display: block;
  margin: 0 auto; }
.dash-gauge-v { font-size: 17px; font-weight: 700; fill: #333333; }
.dash-gauge-u { font-size: 8px; fill: #666666; }
.dash-gauge-mm { font-size: 7px; fill: #999999; }
.dash-kpi-v { font-size: 2.1em; font-weight: 700; color: #119043; }
.dash-kpi-u { color: #666666; margin-left: 6px; }
.dash-status-row { display: flex; gap: 8px; flex-wrap: wrap; }
.dash-status { border-radius: 8px; padding: 6px 12px; color: #ffffff;
  display: flex; flex-direction: column; align-items: center; }
.dash-status.st-ok { background: #119043; }
.dash-status.st-active { background: #1BA4CF;
  border: 2px dashed #ffffff; }
.dash-status.st-stale { background: #999999; }
/* end dash-v2 */

/* browser device dots (BRW-005, BBB-025): blue native IP, light green
   routed, dark green gateway; brand palette only. */
.bbdot { display: inline-block; width: 10px; height: 10px;
  border-radius: 50%; vertical-align: middle; margin-right: 4px; }
.bbdot-ip { background: #1BA4CF; }
.bbdot-routed { background: #92CA4F; }
.bbdot-gw { background: #119043; }
.bb-net td { font-weight: 700; color: #119043;
  background: #f2f8f3; }

.bb-prop-failed td { color: #999999; }
#bb-props td:nth-child(2) { word-break: break-word; max-width: 420px; }

td.bb-editable { cursor: text; border-bottom: 1px dashed #92CA4F; }
td.bb-editable:hover { background: #f2f8f3; }

#broker-banner { background: #c0392b; color: #ffffff;
  padding: 8px 16px; font-weight: bold; text-align: center; }

.qc-targets { max-height: 220px; overflow: auto; padding: 6px;
  border: 1px solid #e0e0e0; border-radius: 4px; }
.qc-targets label { display: inline-block; margin: 2px 10px 2px 0; }

.btn-danger {
  background: #fff; color: var(--alarm); border: 1px solid var(--alarm);
  border-radius: 8px; padding: 8px 14px; font-size: 14px;
}
.btn-danger:hover { background: #fdecea; }
#bb-ask-counts td { padding: 3px 10px 3px 0; font-size: 13px; }

/* Telegram pairing (2026.08.8-31): the code a person reads off the
   screen and types into their phone earns display size; project
   membership is picked with the same chip in step 2 and in every
   connected user row. */
.bb-code-box { border: 1px solid var(--green-light); border-radius: 10px;
  background: rgba(146, 202, 79, 0.10); padding: 14px 16px;
  margin: 6px 0 10px; display: flex; align-items: center; gap: 16px;
  flex-wrap: wrap; }
.bb-code { font-size: 34px; font-weight: 700; letter-spacing: 6px;
  color: var(--green); font-variant-numeric: tabular-nums; }
.bb-projpick-wrap { display: flex; gap: 10px; align-items: baseline;
  flex-wrap: wrap; margin: 0 0 10px; }
.bb-projpick { display: flex; gap: 6px; flex-wrap: wrap; }
.bb-proj-chip { display: inline-flex; flex-direction: row;
  align-items: center; gap: 5px; margin: 2px 0; padding: 3px 10px;
  border: 1px solid var(--green-light); border-radius: 12px;
  background: #fff; font-size: 12px; color: #0d6e33;
  cursor: pointer; white-space: nowrap; }
.bb-proj-chip input { margin: 0; }
.bb-proj-chip:has(input:checked) { background: rgba(146, 202, 79, 0.25); }
.bb-projcell { max-width: 340px; }
.bb-trusted-label { padding-bottom: 4px; }
#bb-users-table .row-actions .btn-mini { margin: 2px 4px 2px 0; }

#bb-log-table td, #bb-log-table th { font-size: 12px; }

#bb-tls-auto-msg { white-space: pre-line; }

/* Locale flags: nine SVGs drawn here and inlined, because the
   console ships offline and vendors everything it uses. They are
   icons beside a language name, never the only cue: the native
   name is what somebody looking for their own language reads,
   and a flag is not a language (English is not only spoken in
   one country). Generated, not hand edited. */
.flag { display: inline-block; width: 21px; height: 14px;
  border-radius: 2px; background-size: cover; flex: 0 0 auto;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .18); }
.flag-fr { background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2060%2040%22%3E%3Crect%20x%3D%220%22%20width%3D%2220%22%20height%3D%2240%22%20fill%3D%22%23002395%22%2F%3E%3Crect%20x%3D%2220%22%20width%3D%2220%22%20height%3D%2240%22%20fill%3D%22%23FFFFFF%22%2F%3E%3Crect%20x%3D%2240%22%20width%3D%2220%22%20height%3D%2240%22%20fill%3D%22%23ED2939%22%2F%3E%3C%2Fsvg%3E"); }
.flag-de { background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2060%2040%22%3E%3Crect%20y%3D%220%22%20width%3D%2260%22%20height%3D%2213.33%22%20fill%3D%22%23000000%22%2F%3E%3Crect%20y%3D%2213.33%22%20width%3D%2260%22%20height%3D%2213.33%22%20fill%3D%22%23DD0000%22%2F%3E%3Crect%20y%3D%2226.67%22%20width%3D%2260%22%20height%3D%2213.33%22%20fill%3D%22%23FFCE00%22%2F%3E%3C%2Fsvg%3E"); }
.flag-id { background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2060%2040%22%3E%3Crect%20y%3D%220%22%20width%3D%2260%22%20height%3D%2220%22%20fill%3D%22%23CE1126%22%2F%3E%3Crect%20y%3D%2220%22%20width%3D%2260%22%20height%3D%2220%22%20fill%3D%22%23FFFFFF%22%2F%3E%3C%2Fsvg%3E"); }
.flag-th { background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2060%2040%22%3E%3Crect%20y%3D%220%22%20width%3D%2260%22%20height%3D%226.667%22%20fill%3D%22%23A51931%22%2F%3E%3Crect%20y%3D%226.667%22%20width%3D%2260%22%20height%3D%226.667%22%20fill%3D%22%23F4F5F8%22%2F%3E%3Crect%20y%3D%2213.33%22%20width%3D%2260%22%20height%3D%2213.33%22%20fill%3D%22%232D2A4A%22%2F%3E%3Crect%20y%3D%2226.67%22%20width%3D%2260%22%20height%3D%226.667%22%20fill%3D%22%23F4F5F8%22%2F%3E%3Crect%20y%3D%2233.33%22%20width%3D%2260%22%20height%3D%226.667%22%20fill%3D%22%23A51931%22%2F%3E%3C%2Fsvg%3E"); }
.flag-ja { background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2060%2040%22%3E%3Crect%20width%3D%2260%22%20height%3D%2240%22%20fill%3D%22%23FFFFFF%22%2F%3E%3Ccircle%20cx%3D%2230.0%22%20cy%3D%2220.0%22%20r%3D%2212%22%20fill%3D%22%23BC002D%22%2F%3E%3C%2Fsvg%3E"); }
.flag-zh-cn { background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2060%2040%22%3E%3Crect%20width%3D%2260%22%20height%3D%2240%22%20fill%3D%22%23EE1C25%22%2F%3E%3Cpolygon%20points%3D%2210%2C4%2011.3%2C8.15%2015.7%2C8.15%2012.2%2C10.7%2013.5%2C14.9%2010%2C12.3%206.47%2C14.9%207.82%2C10.7%204.29%2C8.15%208.65%2C8.15%22%20fill%3D%22%23FFDE00%22%2F%3E%3Cpolygon%20points%3D%2220.7%2C2.12%2020.6%2C3.57%2022%2C4.07%2020.6%2C4.47%2020.6%2C5.92%2019.7%2C4.72%2018.3%2C5.12%2019.2%2C3.97%2018.4%2C2.77%2019.8%2C3.27%22%20fill%3D%22%23FFDE00%22%2F%3E%3Cpolygon%20points%3D%2225.4%2C7.59%2024.8%2C8.88%2025.8%2C9.91%2024.3%2C9.68%2023.7%2C11%2023.5%2C9.54%2022%2C9.31%2023.3%2C8.65%2023.1%2C7.22%2024.1%2C8.25%22%20fill%3D%22%23FFDE00%22%2F%3E%3Cpolygon%20points%3D%2225.9%2C14.3%2024.7%2C15.2%2025.2%2C16.6%2024%2C15.8%2022.9%2C16.7%2023.3%2C15.3%2022.1%2C14.4%2023.5%2C14.4%2023.9%2C13%2024.4%2C14.4%22%20fill%3D%22%23FFDE00%22%2F%3E%3Cpolygon%20points%3D%2220.7%2C18.1%2020.6%2C19.6%2022%2C20.1%2020.6%2C20.5%2020.6%2C21.9%2019.7%2C20.7%2018.3%2C21.1%2019.2%2C20%2018.4%2C18.8%2019.8%2C19.3%22%20fill%3D%22%23FFDE00%22%2F%3E%3C%2Fsvg%3E"); }
.flag-ko { background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2060%2040%22%3E%3Crect%20width%3D%2260%22%20height%3D%2240%22%20fill%3D%22%23FFFFFF%22%2F%3E%3Cpath%20d%3D%22M22.0%2C20.0%20a8%2C8%200%200%201%2016%2C0%20a4%2C4%200%200%200%20-8%2C0%20a4%2C4%200%200%201%20-8%2C0%20z%22%20fill%3D%22%23CD2E3A%22%2F%3E%3Cpath%20d%3D%22M22.0%2C20.0%20a4%2C4%200%200%201%208%2C0%20a4%2C4%200%200%200%208%2C0%20a8%2C8%200%200%201%20-16%2C0%20z%22%20fill%3D%22%230047A0%22%2F%3E%3Cg%20transform%3D%22translate%289%2C9%29%20rotate%28-56%29%22%20fill%3D%22%23000000%22%3E%3Crect%20x%3D%22-5%22%20y%3D%22-2.6%22%20width%3D%2210%22%20height%3D%221.5%22%2F%3E%3Crect%20x%3D%22-5%22%20y%3D%22-0.75%22%20width%3D%2210%22%20height%3D%221.5%22%2F%3E%3Crect%20x%3D%22-5%22%20y%3D%221.1%22%20width%3D%2210%22%20height%3D%221.5%22%2F%3E%3C%2Fg%3E%3Cg%20transform%3D%22translate%289%2C31%29%20rotate%2856%29%22%20fill%3D%22%23000000%22%3E%3Crect%20x%3D%22-5%22%20y%3D%22-2.6%22%20width%3D%2210%22%20height%3D%221.5%22%2F%3E%3Crect%20x%3D%22-5%22%20y%3D%22-0.75%22%20width%3D%2210%22%20height%3D%221.5%22%2F%3E%3Crect%20x%3D%22-5%22%20y%3D%221.1%22%20width%3D%2210%22%20height%3D%221.5%22%2F%3E%3C%2Fg%3E%3Cg%20transform%3D%22translate%2851%2C9%29%20rotate%2856%29%22%20fill%3D%22%23000000%22%3E%3Crect%20x%3D%22-5%22%20y%3D%22-2.6%22%20width%3D%2210%22%20height%3D%221.5%22%2F%3E%3Crect%20x%3D%22-5%22%20y%3D%22-0.75%22%20width%3D%2210%22%20height%3D%221.5%22%2F%3E%3Crect%20x%3D%22-5%22%20y%3D%221.1%22%20width%3D%2210%22%20height%3D%221.5%22%2F%3E%3C%2Fg%3E%3Cg%20transform%3D%22translate%2851%2C31%29%20rotate%28-56%29%22%20fill%3D%22%23000000%22%3E%3Crect%20x%3D%22-5%22%20y%3D%22-2.6%22%20width%3D%2210%22%20height%3D%221.5%22%2F%3E%3Crect%20x%3D%22-5%22%20y%3D%22-0.75%22%20width%3D%2210%22%20height%3D%221.5%22%2F%3E%3Crect%20x%3D%22-5%22%20y%3D%221.1%22%20width%3D%2210%22%20height%3D%221.5%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E"); }
.flag-hi { background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2060%2040%22%3E%3Crect%20y%3D%220%22%20width%3D%2260%22%20height%3D%2213.33%22%20fill%3D%22%23FF9933%22%2F%3E%3Crect%20y%3D%2213.33%22%20width%3D%2260%22%20height%3D%2213.33%22%20fill%3D%22%23FFFFFF%22%2F%3E%3Crect%20y%3D%2226.67%22%20width%3D%2260%22%20height%3D%2213.33%22%20fill%3D%22%23138808%22%2F%3E%3Ccircle%20cx%3D%2230.0%22%20cy%3D%2220.0%22%20r%3D%226%22%20fill%3D%22none%22%20stroke%3D%22%23000080%22%20stroke-width%3D%221%22%2F%3E%3Ccircle%20cx%3D%2230.0%22%20cy%3D%2220.0%22%20r%3D%221.1%22%20fill%3D%22%23000080%22%2F%3E%3Cline%20x1%3D%2230.0%22%20y1%3D%2220.0%22%20x2%3D%2236%22%20y2%3D%2220%22%20stroke%3D%22%23000080%22%20stroke-width%3D%220.4%22%2F%3E%3Cline%20x1%3D%2230.0%22%20y1%3D%2220.0%22%20x2%3D%2235.8%22%20y2%3D%2221.55%22%20stroke%3D%22%23000080%22%20stroke-width%3D%220.4%22%2F%3E%3Cline%20x1%3D%2230.0%22%20y1%3D%2220.0%22%20x2%3D%2235.2%22%20y2%3D%2223%22%20stroke%3D%22%23000080%22%20stroke-width%3D%220.4%22%2F%3E%3Cline%20x1%3D%2230.0%22%20y1%3D%2220.0%22%20x2%3D%2234.24%22%20y2%3D%2224.24%22%20stroke%3D%22%23000080%22%20stroke-width%3D%220.4%22%2F%3E%3Cline%20x1%3D%2230.0%22%20y1%3D%2220.0%22%20x2%3D%2233%22%20y2%3D%2225.2%22%20stroke%3D%22%23000080%22%20stroke-width%3D%220.4%22%2F%3E%3Cline%20x1%3D%2230.0%22%20y1%3D%2220.0%22%20x2%3D%2231.55%22%20y2%3D%2225.8%22%20stroke%3D%22%23000080%22%20stroke-width%3D%220.4%22%2F%3E%3Cline%20x1%3D%2230.0%22%20y1%3D%2220.0%22%20x2%3D%2230%22%20y2%3D%2226%22%20stroke%3D%22%23000080%22%20stroke-width%3D%220.4%22%2F%3E%3Cline%20x1%3D%2230.0%22%20y1%3D%2220.0%22%20x2%3D%2228.45%22%20y2%3D%2225.8%22%20stroke%3D%22%23000080%22%20stroke-width%3D%220.4%22%2F%3E%3Cline%20x1%3D%2230.0%22%20y1%3D%2220.0%22%20x2%3D%2227%22%20y2%3D%2225.2%22%20stroke%3D%22%23000080%22%20stroke-width%3D%220.4%22%2F%3E%3Cline%20x1%3D%2230.0%22%20y1%3D%2220.0%22%20x2%3D%2225.76%22%20y2%3D%2224.24%22%20stroke%3D%22%23000080%22%20stroke-width%3D%220.4%22%2F%3E%3Cline%20x1%3D%2230.0%22%20y1%3D%2220.0%22%20x2%3D%2224.8%22%20y2%3D%2223%22%20stroke%3D%22%23000080%22%20stroke-width%3D%220.4%22%2F%3E%3Cline%20x1%3D%2230.0%22%20y1%3D%2220.0%22%20x2%3D%2224.2%22%20y2%3D%2221.55%22%20stroke%3D%22%23000080%22%20stroke-width%3D%220.4%22%2F%3E%3Cline%20x1%3D%2230.0%22%20y1%3D%2220.0%22%20x2%3D%2224%22%20y2%3D%2220%22%20stroke%3D%22%23000080%22%20stroke-width%3D%220.4%22%2F%3E%3Cline%20x1%3D%2230.0%22%20y1%3D%2220.0%22%20x2%3D%2224.2%22%20y2%3D%2218.45%22%20stroke%3D%22%23000080%22%20stroke-width%3D%220.4%22%2F%3E%3Cline%20x1%3D%2230.0%22%20y1%3D%2220.0%22%20x2%3D%2224.8%22%20y2%3D%2217%22%20stroke%3D%22%23000080%22%20stroke-width%3D%220.4%22%2F%3E%3Cline%20x1%3D%2230.0%22%20y1%3D%2220.0%22%20x2%3D%2225.76%22%20y2%3D%2215.76%22%20stroke%3D%22%23000080%22%20stroke-width%3D%220.4%22%2F%3E%3Cline%20x1%3D%2230.0%22%20y1%3D%2220.0%22%20x2%3D%2227%22%20y2%3D%2214.8%22%20stroke%3D%22%23000080%22%20stroke-width%3D%220.4%22%2F%3E%3Cline%20x1%3D%2230.0%22%20y1%3D%2220.0%22%20x2%3D%2228.45%22%20y2%3D%2214.2%22%20stroke%3D%22%23000080%22%20stroke-width%3D%220.4%22%2F%3E%3Cline%20x1%3D%2230.0%22%20y1%3D%2220.0%22%20x2%3D%2230%22%20y2%3D%2214%22%20stroke%3D%22%23000080%22%20stroke-width%3D%220.4%22%2F%3E%3Cline%20x1%3D%2230.0%22%20y1%3D%2220.0%22%20x2%3D%2231.55%22%20y2%3D%2214.2%22%20stroke%3D%22%23000080%22%20stroke-width%3D%220.4%22%2F%3E%3Cline%20x1%3D%2230.0%22%20y1%3D%2220.0%22%20x2%3D%2233%22%20y2%3D%2214.8%22%20stroke%3D%22%23000080%22%20stroke-width%3D%220.4%22%2F%3E%3Cline%20x1%3D%2230.0%22%20y1%3D%2220.0%22%20x2%3D%2234.24%22%20y2%3D%2215.76%22%20stroke%3D%22%23000080%22%20stroke-width%3D%220.4%22%2F%3E%3Cline%20x1%3D%2230.0%22%20y1%3D%2220.0%22%20x2%3D%2235.2%22%20y2%3D%2217%22%20stroke%3D%22%23000080%22%20stroke-width%3D%220.4%22%2F%3E%3Cline%20x1%3D%2230.0%22%20y1%3D%2220.0%22%20x2%3D%2235.8%22%20y2%3D%2218.45%22%20stroke%3D%22%23000080%22%20stroke-width%3D%220.4%22%2F%3E%3C%2Fsvg%3E"); }
.flag-en { background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2060%2040%22%3E%3Crect%20width%3D%2260%22%20height%3D%2240%22%20fill%3D%22%23012169%22%2F%3E%3Cpath%20d%3D%22M0%2C0%20L60%2C40%20M60%2C0%20L0%2C40%22%20stroke%3D%22%23FFFFFF%22%20stroke-width%3D%228%22%2F%3E%3Cpath%20d%3D%22M0%2C0%20L60%2C40%20M60%2C0%20L0%2C40%22%20stroke%3D%22%23C8102E%22%20stroke-width%3D%224%22%2F%3E%3Cpath%20d%3D%22M30.0%2C0%20V40%20M0%2C20.0%20H60%22%20stroke%3D%22%23FFFFFF%22%20stroke-width%3D%2213%22%2F%3E%3Cpath%20d%3D%22M30.0%2C0%20V40%20M0%2C20.0%20H60%22%20stroke%3D%22%23C8102E%22%20stroke-width%3D%228%22%2F%3E%3C%2Fsvg%3E"); }

/* The language picker. A native select cannot show an image in an
   option, and Windows has no flag glyphs, so an emoji flag renders
   there as a boxed two letter code. This is therefore a button and a
   list rather than a select, and it carries the listbox roles and the
   keyboard behaviour a select would have given for free. */
.lang-pick { position: relative; }
.lang-btn { display: inline-flex; align-items: center; gap: 6px; }
.lang-menu {
  position: absolute; right: 0; top: calc(100% + 4px); z-index: 20;
  margin: 0; padding: 4px; list-style: none; min-width: 190px;
  background: var(--card); border: 1px solid var(--line, #d8e0d8);
  border-radius: 10px; box-shadow: 0 8px 24px rgba(0, 0, 0, .16);
  max-height: 60vh; overflow: auto;
}
.lang-menu li {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 10px; border-radius: 7px; cursor: pointer;
  font-size: 13px; color: var(--ink); white-space: nowrap;
}
.lang-menu li:hover, .lang-menu li.here { background: var(--bg); }
.lang-menu li[aria-selected="true"] { font-weight: 600; }
.lang-menu li .tick { margin-left: auto; color: var(--green); }
.lang-menu li .part { color: var(--muted); font-size: 12px; }

/* Back to top: fixed, appears once the page has scrolled past about one
   mouse wheel notch and hides again within that same distance of the top. */
#back-to-top {
  position: fixed; right: 22px; bottom: 22px; z-index: 500;
  width: 44px; height: 44px; border-radius: 50%; border: none;
  background: var(--green); color: #fff; font-size: 20px; line-height: 1;
  box-shadow: 0 2px 10px rgba(0, 0, 0, .25);
  display: none; align-items: center; justify-content: center;
}
#back-to-top:hover { background: #0d6e33; }
#back-to-top.show { display: flex; }
@media (max-width: 720px) {
  #back-to-top { right: 14px; bottom: 14px; width: 40px; height: 40px; }
}
