/* =========================================================
   ui-next-concept.css

   GENERATED by docs/ui-concept/tools/port_concept_css.py.
   Do not hand-edit: change the concept and re-run, or the two drift
   and the concept stops being the source of truth.

   concept.css and concept-pages.css, verbatim, with every selector
   scoped to html[data-ui="next"]. Loaded after ui-next.css so the concept's
   own components win where both define one.
   ========================================================= */

/* ---------- concept.css ---------- */
/* =========================================================
   CT Insight - platform shell.  FINAL CONCEPT
   Offline. Not wired into the app. See README.md.

   Stripe-grade restraint, CaptureTech palette and type.

   Carried over from the landing system:
     brand colours, the ink ramp, Mont + Roboto, the easing curve,
     the "controls are solid, never gradients" rule.

   Deliberately not carried over: glass. Landing is layered translucency over
   a tinted field, which reads beautifully across six marketing pages and
   becomes forty blurred rectangles across forty console screens. The app
   trades landing's MATERIAL for landing's PALETTE:
     - Flat white surfaces on one near-white field. No backdrop-filter.
     - Separation is a 1px hairline; shadow is one 1px contact shadow.
     - Radii step down hard: 10px panels, 7px controls, 6px menu items.
     - Colour is reserved for meaning.
     - Four type sizes. Mont for headings and every number, Roboto for the rest.
   ========================================================= */


/* ---------------------------------------------------------
   LAYER 1 - brand tokens, verbatim from landing.css
   --------------------------------------------------------- */
html[data-ui="next"]{
  --cyan:#00baff;
  --cyan-dark:#0092c8;
  --blue:#0064cd;
  --red:#f03c5a;
  --red-dark:#d42846;

  --ink:#0a0f17;
  --ink-2:#2b3542;
  --ink-3:#5c6878;
  --ink-4:#8b97a6;
  --line:rgba(10,25,50,0.09);
  --line-soft:rgba(10,25,50,0.055);

  --font-head:'Mont', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --font-body:'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --ease:cubic-bezier(0.32, 0.72, 0, 1);

  /* ---- LAYER 2 - app scale ---- */
  --surface:#ffffff;
  --field:#f7f9fb;
  --hover:rgba(10,25,50,0.035);
  --tint:rgba(0,100,205,0.07);

  --r-panel:10px;
  --r-ctl:7px;
  --r-item:6px;
  --r-chip:5px;

  --shadow:0 1px 2px rgba(10,25,50,0.05);
  --shadow-pop:0 1px 2px rgba(10,25,50,0.06), 0 12px 32px -10px rgba(10,25,50,0.24);

  --app-font:14px;
  --app-lh:1.5;
  --pad:18px;
  --gap:14px;
  --rail-w:236px;
  --topbar-h:56px;
}

/* ---------------------------------------------------------
   LAYER 3 - base
   --------------------------------------------------------- */
html[data-ui="next"] *{box-sizing:border-box;}
html[data-ui="next"]{-webkit-text-size-adjust:100%;}

/* ---------- Scrollbars ----------
   Hidden everywhere, scrolling untouched. Applied to `*` rather than just the
   document because most of the scrolling in this app happens inside a pane - the rail, a dialog body, a wide table, the workflow toolbox, the manual
   contents - and hiding only the page's bar would leave those looking
   inconsistent with it.

   Three declarations because three engines disagree: `scrollbar-width` is the
   standard one (Firefox, and now Chromium), `::-webkit-scrollbar` is what older
   WebKit and Blink honour, and `-ms-overflow-style` covers legacy Edge.

   Worth being clear about the cost, since it is a real one: the scrollbar is the
   only thing that says a region scrolls at all and how far through it you are.
   With it gone, an overflowing panel looks identical to one that simply ends - so anything that scrolls needs to make that obvious some other way, by
   clipping a row mid-height rather than landing flush on a boundary. The panes
   in this concept are built that way; it is a constraint to carry forward, not
   a free change. */
html[data-ui="next"] *{ scrollbar-width:none; -ms-overflow-style:none; }
html[data-ui="next"] *::-webkit-scrollbar{ width:0; height:0; display:none; }

html[data-ui="next"] body{
  margin:0;
  font-family:var(--font-body);
  font-size:var(--app-font);
  line-height:var(--app-lh);
  color:var(--ink-2);
  background:var(--field);
  -webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale;
  min-height:100vh;
}

html[data-ui="next"] img{max-width:100%;height:auto;display:block;}
html[data-ui="next"] a{color:inherit;text-decoration:none;}
html[data-ui="next"] button{font-family:inherit;}
html[data-ui="next"] :focus-visible{outline:2px solid var(--blue);outline-offset:2px;border-radius:3px;}

html[data-ui="next"] h1,
html[data-ui="next"] h2,
html[data-ui="next"] h3,
html[data-ui="next"] h4{font-family:var(--font-head);font-weight:700;color:var(--ink);margin:0;}
html[data-ui="next"] h1{font-size:30px;line-height:1.15;letter-spacing:-0.025em;}
html[data-ui="next"] h2{font-size:20px;line-height:1.2;letter-spacing:-0.018em;}
html[data-ui="next"] h3{font-size:14px;line-height:1.3;letter-spacing:-0.006em;}
html[data-ui="next"] h4{font-size:13px;line-height:1.3;}

html[data-ui="next"] p{margin:0 0 10px;}
html[data-ui="next"] p:last-child{margin-bottom:0;}
html[data-ui="next"] .muted{color:var(--ink-3);}
html[data-ui="next"] .dim{color:var(--ink-4);}
html[data-ui="next"] .num{font-variant-numeric:tabular-nums;}

html[data-ui="next"] .section-title{
  font-family:var(--font-head);font-weight:700;
  font-size:12.5px;letter-spacing:0.01em;color:var(--ink-3);
  margin:0 0 10px;
}

/* ---------- Buttons ---------- */
/* The `hidden` attribute has to actually hide things. The UA rule for it is
   `[hidden]{display:none}`, which any explicit `display` in this stylesheet
   outranks - and .btn sets display:inline-flex, so a button marked hidden stayed
   on screen while every script that asked `el.hidden` was told it was gone. The
   operator dialog showed Edit, Cancel, Save and Close simultaneously because of
   it, and the check that was supposed to catch that read the attribute rather
   than the computed style, so it agreed with the bug. */
html[data-ui="next"] [hidden]{display:none !important;}

html[data-ui="next"] .btn{
  display:inline-flex;align-items:center;justify-content:center;gap:6px;
  font-family:var(--font-head);font-weight:700;
  font-size:13px;line-height:1;
  padding:8px 13px;
  border-radius:var(--r-ctl);
  border:1px solid transparent;
  cursor:pointer;white-space:nowrap;
  transition:background-color .15s ease, border-color .15s ease, color .15s ease;
  /* The icon is 14px and the line box is 13px, so a button WITH an icon came out
     1.5px taller than one without - which meant any row mixing the two sat
     slightly out of line, and the operator dialog changed height by exactly that
     much when its footer swapped Edit (icon) for Cancel/Save (no icon).
     Pinning the height decouples it from whether there happens to be an svg. */
  min-height:32px;
}
html[data-ui="next"] .btn-sm{padding:6px 10px;font-size:12.5px;min-height:28px;}
html[data-ui="next"] .btn svg{width:14px;height:14px;flex-shrink:0;}
/* Vertical centring of icon and text. Mont (the buttons' face) sits its capitals about 0.06em above the
   middle of its line, so the icon beside the words looked low (the + of New). Lifting the icon by the same 0.06em puts
   it on the capitals' centre whatever the button size; a transform, so no layout or height changes. */
html[data-ui="next"] .btn > svg{transform:translateY(-.06em);}
html[data-ui="next"] .btn-ghost{background:var(--surface);color:var(--ink-2);border-color:var(--line);box-shadow:var(--shadow);}
html[data-ui="next"] .btn-ghost:hover{background:#fcfdfe;border-color:rgba(10,25,50,0.16);color:var(--ink);}
html[data-ui="next"] .btn-primary{background:var(--red);color:#fff;}
html[data-ui="next"] .btn-primary:hover{background:var(--red-dark);}
html[data-ui="next"] .btn-blue{background:var(--blue);color:#fff;}
html[data-ui="next"] .btn-blue:hover{background:#00509f;}
html[data-ui="next"] .btn-quiet{background:transparent;color:var(--ink-3);}
html[data-ui="next"] .btn-quiet:hover{background:var(--hover);color:var(--ink);}

/* ---------- Chips ---------- */
html[data-ui="next"] .chip{
  display:inline-flex;align-items:center;gap:5px;
  font-family:var(--font-body);font-weight:500;
  font-size:11.5px;line-height:1.4;
  padding:2px 7px;border-radius:var(--r-chip);
  border:1px solid var(--line);
  background:var(--surface);color:var(--ink-3);
  white-space:nowrap;max-width:100%;
}
/* On a phone a long chip (a domain, a reason, a full address) wraps inside what holds it instead of
   pushing the page sideways; a short one still fits on its line, and on a wider screen none wraps. */
@media (max-width:640px){
  html[data-ui="next"] .chip{white-space:normal;overflow-wrap:break-word;}
}
html[data-ui="next"] .chip-on{background:rgba(0,100,205,0.06);border-color:rgba(0,100,205,0.16);color:var(--blue);}
html[data-ui="next"] .chip-off{color:var(--ink-4);}
/* Impersonation is a warning-level state, not a failure. Amber, so it never
   competes with a real breach in the status palette. */
html[data-ui="next"] .chip-warn{background:rgba(169,103,11,0.07);border-color:rgba(169,103,11,0.22);color:#a9670b;}

/* ---------- Panels ---------- */
html[data-ui="next"] .panel{
  background:var(--surface);
  border:1px solid var(--line);
  border-radius:var(--r-panel);
  box-shadow:var(--shadow);
}
html[data-ui="next"] .panel-head{
  display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:12px var(--pad);
  border-bottom:1px solid var(--line-soft);
  min-height:46px;
}
/* The head's right hand side (Panel Actions): one group, right-aligned, wrapping when it runs out of room. */
html[data-ui="next"] .panel-actions{display:flex;align-items:center;justify-content:flex-end;flex-wrap:wrap;gap:6px 8px;margin-left:auto;min-width:0;}
html[data-ui="next"] .panel-body{padding:var(--pad);}

html[data-ui="next"] .panel.is-link{cursor:pointer;transition:border-color .15s ease;}
html[data-ui="next"] .panel.is-link:hover{border-color:rgba(10,25,50,0.18);}

html[data-ui="next"] .grid{display:grid;gap:var(--gap);}
html[data-ui="next"] .grid-4{grid-template-columns:repeat(4,minmax(0,1fr));}
html[data-ui="next"] .grid-2-1{grid-template-columns:minmax(0,2.1fr) minmax(300px,1fr);align-items:start;}

/* ---------- Stat tiles ---------- */
html[data-ui="next"] .stat{padding:15px var(--pad) 13px;display:flex;flex-direction:column;}
html[data-ui="next"] .stat .stat-label{font-size:12.5px;font-weight:500;color:var(--ink-3);margin-bottom:6px;}
html[data-ui="next"] .stat .stat-value{
  font-family:var(--font-head);font-weight:700;
  font-size:27px;line-height:1.05;letter-spacing:-0.025em;color:var(--ink);
  font-variant-numeric:tabular-nums;
}
html[data-ui="next"] .stat .stat-value.sm{font-size:18px;letter-spacing:-0.012em;line-height:1.25;}
/* The delta and the descriptor are separated by a rule, not a gap.
   "+8  231 active now" set adjacent reads as the single number 8,231. */
html[data-ui="next"] .stat .stat-foot{margin-top:8px;font-size:12px;color:var(--ink-4);display:flex;align-items:center;gap:9px;flex-wrap:wrap;}
html[data-ui="next"] .stat .stat-foot .sep{width:1px;height:11px;background:var(--line);flex-shrink:0;}
html[data-ui="next"] .stat .spark{margin-top:auto;padding-top:12px;display:block;width:100%;height:30px;overflow:visible;}

html[data-ui="next"] .delta{display:inline-flex;align-items:center;gap:3px;font-weight:500;font-variant-numeric:tabular-nums;}
html[data-ui="next"] .delta svg{width:11px;height:11px;}
html[data-ui="next"] .delta-up{color:#12806a;}
html[data-ui="next"] .delta-down{color:var(--red-dark);}
html[data-ui="next"] .delta-flat{color:var(--ink-4);}

/* ---------- Status - reserved, never a series colour ---------- */
html[data-ui="next"] .status{display:inline-flex;align-items:center;gap:6px;font-size:12.5px;font-weight:500;}
html[data-ui="next"] .status svg{width:13px;height:13px;flex-shrink:0;}
html[data-ui="next"] .status-good{color:#12806a;}
html[data-ui="next"] .status-warn{color:#a9670b;}
html[data-ui="next"] .status-crit{color:var(--red-dark);}
html[data-ui="next"] .status-idle{color:var(--ink-4);}

/* ---------- KPI health tiles ----------
   Mirrors KpiHealthPanel.razor. Deliberately dense: twelve rules have to fit
   on screen without scrolling the panel, and each one is four short facts, so
   a tile only needs to be big enough to hold its number. auto-fill means the
   grid gains columns when the rail collapses instead of stretching the tiles. */
html[data-ui="next"] .kpi-group{margin-bottom:14px;}
html[data-ui="next"] .kpi-group:last-of-type{margin-bottom:0;}
html[data-ui="next"] .kpi-group-label{
  font-size:10.5px;letter-spacing:0.07em;text-transform:uppercase;
  font-weight:500;color:var(--ink-4);margin-bottom:6px;
}
html[data-ui="next"] .kpi-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(190px,1fr));gap:8px;}

html[data-ui="next"] .kpi-tile{
  background:var(--surface);
  border:1px solid var(--line);
  border-left:3px solid var(--ink-4);
  border-radius:8px;
  padding:7px 10px 8px;
  min-width:0;
  transition:border-color .12s ease, background-color .12s ease;
}
html[data-ui="next"] .kpi-tile .k-head{display:flex;align-items:center;justify-content:space-between;gap:6px;}
html[data-ui="next"] .kpi-tile .k-name{
  font-size:11.5px;font-weight:500;color:var(--ink-3);line-height:1.35;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;
}
html[data-ui="next"] .kpi-tile .k-icons{display:inline-flex;align-items:center;gap:4px;flex-shrink:0;line-height:1;}
html[data-ui="next"] .kpi-tile .k-icons svg{width:11px;height:11px;display:block;}
html[data-ui="next"] .kpi-tile .k-info{color:#c2c9d1;cursor:help;}
/* Value and threshold share a baseline. The threshold is what makes the value
   mean anything, so it sits beside it rather than on a third line. */
html[data-ui="next"] .kpi-tile .k-line{display:flex;align-items:baseline;gap:6px;margin-top:1px;min-width:0;}
html[data-ui="next"] .kpi-tile .k-val{
  font-family:var(--font-head);font-weight:700;
  font-size:15px;line-height:1.2;letter-spacing:-0.012em;
  color:var(--ink);font-variant-numeric:tabular-nums;white-space:nowrap;
}
html[data-ui="next"] .kpi-tile .k-unit{font-family:var(--font-body);font-weight:400;font-size:10.5px;color:var(--ink-4);margin-left:1px;}
html[data-ui="next"] .kpi-tile .k-thr{
  font-size:10.5px;color:var(--ink-4);font-variant-numeric:tabular-nums;
  margin-left:auto;white-space:nowrap;
}
html[data-ui="next"] .kpi-tile .k-breach{color:#a9670b;}

html[data-ui="next"] .kpi-tile.is-healthy{border-left-color:#12806a;}
html[data-ui="next"] .kpi-tile.is-healthy .k-icons{color:#12806a;}
html[data-ui="next"] .kpi-tile.is-warning{border-left-color:#a9670b;background:#fffdf5;}
html[data-ui="next"] .kpi-tile.is-warning .k-icons{color:#a9670b;}
html[data-ui="next"] .kpi-tile.is-warning .k-val{color:#a9670b;}
html[data-ui="next"] .kpi-tile.is-breaching{border-left-color:var(--red-dark);background:#fff7f8;}
html[data-ui="next"] .kpi-tile.is-breaching .k-icons{color:var(--red-dark);}
html[data-ui="next"] .kpi-tile.is-breaching .k-val{color:var(--red-dark);}
html[data-ui="next"] .kpi-tile.is-unknown{border-left-color:#cfd4da;background:#fbfbfc;}
html[data-ui="next"] .kpi-tile.is-unknown .k-icons{color:var(--ink-4);}
html[data-ui="next"] .kpi-tile.is-disabled{border-left-color:#adb5bd;opacity:.6;}
html[data-ui="next"] .kpi-tile.is-disabled .k-icons{color:var(--ink-4);}

html[data-ui="next"] .kpi-legend{
  display:flex;align-items:center;justify-content:space-between;gap:12px;
  margin-top:14px;padding-top:11px;border-top:1px solid var(--line-soft);
  font-size:11.5px;color:var(--ink-4);
}
html[data-ui="next"] .kpi-legend .keys{display:flex;gap:14px;flex-wrap:wrap;}
html[data-ui="next"] .kpi-legend .keys span{display:inline-flex;align-items:center;gap:5px;}
html[data-ui="next"] .kpi-legend .keys i{width:7px;height:7px;border-radius:2px;display:inline-block;}

/* ---------- Rail ---------- */
html[data-ui="next"] .rail{
  position:fixed;left:0;top:0;bottom:0;width:var(--rail-w);
  display:flex;flex-direction:column;
  background:var(--surface);
  border-right:1px solid var(--line);
  z-index:40;
  transition:width .22s var(--ease);
}
html[data-ui="next"] .rail .brand{
  display:flex;align-items:center;gap:8px;
  height:var(--topbar-h);padding:0 16px;flex-shrink:0;
  font-family:var(--font-head);font-weight:700;font-size:16px;
  letter-spacing:-0.018em;color:var(--ink);
}
html[data-ui="next"] .rail .brand img{height:20px;width:auto;flex-shrink:0;}
html[data-ui="next"] .rail nav{padding:10px;overflow-y:auto;overflow-x:hidden;flex:1;}
/* --ink-3, not --ink-4. Measuring the dark variant put a number on this one
   too: --ink-4 (#8b97a6) on white is 2.97:1, and this is 11px uppercase - small
   text, which needs 4.5:1. --ink-3 gives 5.66:1 and still reads as a quiet
   label. It had been sitting there failing quietly since the first draft. */
html[data-ui="next"] .rail .nav-group{
  font-size:11px;letter-spacing:0.06em;text-transform:uppercase;color:var(--ink-3);
  font-weight:500;padding:14px 9px 5px;
}
html[data-ui="next"] .rail .nav-group:first-child{padding-top:2px;}

html[data-ui="next"] .rail a.item,
html[data-ui="next"] .rail button.item{
  display:flex;align-items:center;gap:10px;width:100%;
  padding:7px 9px;margin-bottom:1px;border-radius:var(--r-item);
  font-family:var(--font-body);font-weight:500;font-size:13.5px;
  color:var(--ink-2);background:none;border:0;text-align:left;cursor:pointer;
  transition:background-color .12s ease, color .12s ease;
}
html[data-ui="next"] .rail .item > svg{width:15px;height:15px;flex-shrink:0;color:var(--ink-4);transition:color .12s ease;}
html[data-ui="next"] .rail .item:hover{background:var(--hover);}
html[data-ui="next"] .rail .item:hover > svg{color:var(--ink-3);}
html[data-ui="next"] .rail .item.active{background:var(--tint);color:var(--blue);font-weight:700;}
html[data-ui="next"] .rail .item.active > svg{color:var(--blue);}
html[data-ui="next"] .rail .item .trail{margin-left:auto;flex-shrink:0;}

/* A parent with children: the label navigates, the chevron expands. Two
   targets in one row, because in the real layout the parent is a page too
   (/operations/dashboard) - collapsing the group must not cost you the link. */
html[data-ui="next"] .rail .item-row{display:flex;align-items:center;gap:1px;}
html[data-ui="next"] .rail .item-row > .item{flex:1;min-width:0;margin-bottom:0;}
html[data-ui="next"] .rail .item-row > .item .label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
html[data-ui="next"] .rail .chev{
  flex-shrink:0;width:26px;height:31px;border-radius:var(--r-item);
  display:inline-flex;align-items:center;justify-content:center;
  background:none;border:0;cursor:pointer;color:var(--ink-4);
  transition:background-color .12s ease, color .12s ease;
}
html[data-ui="next"] .rail .chev:hover{background:var(--hover);color:var(--ink-2);}
html[data-ui="next"] .rail .chev svg{width:12px;height:12px;transition:transform .2s var(--ease);}
html[data-ui="next"] .rail .nav-item.open > .item-row > .chev svg{transform:rotate(90deg);}

/* The group opens and closes instead of appearing and vanishing.

   Animated with grid-template-rows 0fr -> 1fr rather than max-height, because
   max-height needs a number larger than the content, and any number larger than
   the content means the close begins with a pause while the invisible slack
   collapses. 0fr -> 1fr resolves to the exact content height, so a two-item
   group and a four-item group both take the same time and neither stalls.

   The outer element carries the height and NOTHING else, and .submenu-in has no
   VERTICAL margin or padding at all. That part is not cosmetic: a grid item's
   margin sits inside its own row, so the 1px/2px it used to carry kept the
   closed group at 3px instead of 0. Padding would have done the same, since a
   border-box height of 0 still cannot be smaller than its own padding. The
   items inside already have their own vertical rhythm, so nothing was lost. */
html[data-ui="next"] .rail .submenu{
  display:grid;
  grid-template-rows:0fr;
  transition:grid-template-rows .24s var(--ease);
}
html[data-ui="next"] .rail .submenu > .submenu-in{
  /* min-height:0 is load-bearing. A grid item's default min-height is auto,
     which refuses to shrink below its content, and the row would never close. */
  min-height:0;
  overflow:hidden;
  opacity:0;
  transition:opacity .16s ease;
  /* One hairline down the left, indented to the parent icon's centre. The
     children hang off it, which is cheaper to read than four indented rows. */
  margin-left:17px;
  padding-left:9px;
  border-left:1px solid var(--line-soft);
}
html[data-ui="next"] .rail .nav-item.open > .submenu{grid-template-rows:1fr;}
/* Opacity trails the height slightly on the way in and leads on the way out, so
   the links fade rather than sliding out from under the row above them. */
html[data-ui="next"] .rail .nav-item.open > .submenu > .submenu-in{opacity:1;}
html[data-ui="next"] .rail .submenu .item{font-size:13px;padding:6px 9px;}
html[data-ui="next"] .rail .submenu .item > svg{width:14px;height:14px;}

html[data-ui="next"] .rail .rail-foot{padding:10px;border-top:1px solid var(--line-soft);flex-shrink:0;}

html[data-ui="next"] .count{
  display:inline-flex;align-items:center;justify-content:center;
  min-width:18px;height:18px;padding:0 5px;border-radius:9px;
  font-family:var(--font-head);font-weight:700;font-size:10.5px;
  background:rgba(240,60,90,0.1);color:var(--red-dark);
}

/* ---------- Collapsed rail ----------
   Menu structure is unchanged: same items, same order, same groups. Only the
   labels go. A collapse that also reorganises the nav costs the spatial memory
   built in the expanded state, which is most of what a nav is for.
   --rail-w is redefined on <body>, so rail width and shell padding follow from
   one declaration and cannot disagree. */
html[data-ui="next"] body.rail-collapsed{--rail-w:60px;}

html[data-ui="next"] .rail.collapsed .brand{justify-content:center;padding:0;gap:0;}
html[data-ui="next"] .rail.collapsed .brand .word{display:none;}
html[data-ui="next"] .rail.collapsed nav{padding:10px 6px;}
html[data-ui="next"] .rail.collapsed .item{justify-content:center;padding:9px 0;gap:0;}
html[data-ui="next"] .rail.collapsed .item > svg{width:17px;height:17px;}
html[data-ui="next"] .rail.collapsed .item-row > .item{flex:1;}
html[data-ui="next"] .rail.collapsed .chev{display:none;}

/* Specificity matters here, and getting it wrong is what broke this before.
   `.rail .nav-item.open > .submenu` is (0,4,0); a plain `.rail.collapsed
   .submenu` is only (0,3,0), so an OPEN group kept its submenu displayed when
   the rail collapsed - 206px of children laid out inside a 60px rail. The
   collapsed rule has to out-specify the open rule, not just follow it. */
/* Superseded by the flyout rule below, which needs these to stay in the tree so
   they remain focusable. Kept as a comment rather than deleted because the
   collapsed rail reads as though submenus are simply gone, and they are not. */

/* Visually hidden, NOT display:none. An icon-only link whose label leaves the
   box tree has no accessible name - the nav announces as N unnamed links. */
html[data-ui="next"] .rail.collapsed .item .label{
  position:absolute;width:1px;height:1px;
  padding:0;margin:-1px;overflow:hidden;
  clip-path:inset(50%);white-space:nowrap;border:0;
}

/* The group header cannot show text at 60px, so it becomes the rule it was
   already implying. Keeping the element preserves vertical rhythm. */
html[data-ui="next"] .rail.collapsed .nav-group{
  height:1px;padding:0;margin:11px 6px 10px;
  background:var(--line-soft);overflow:hidden;text-indent:-999em;
}
html[data-ui="next"] .rail.collapsed .nav-group:first-child{margin-top:2px;}


@media (prefers-reduced-motion:reduce){
  html[data-ui="next"] .rail .submenu,
html[data-ui="next"] .rail .submenu > .submenu-in,
html[data-ui="next"] .rail .chev svg{transition:none;}
}

/* Collapsed, a parent icon is all there is, so its children are unreachable by
   clicking. A hover/focus flyout gives them back without putting anything
   permanent in the 60px rail. Remove this block if you want the collapsed rail
   to be strictly top-level-only. */
html[data-ui="next"] .rail.collapsed nav{overflow:visible;}
html[data-ui="next"] .rail.collapsed .nav-item{position:relative;}
/* The flyout is always laid out and always focusable; only its visibility is
   conditional. It used to be display:none until :hover, which meant its links
   were out of the tab order, which meant :focus-within could never fire - the
   keyboard half of that selector was dead, and the collapsed rail's submenus
   were reachable by mouse only. Hiding with opacity instead keeps them in the
   tab order, so tabbing into one reveals it. */
html[data-ui="next"] .rail.collapsed .nav-item > .submenu{
  display:block;
  position:absolute;left:calc(100% + 8px);top:-6px;z-index:70;
  min-width:186px;margin:0;padding:5px;
  background:var(--surface);
  border:1px solid var(--line);border-left:1px solid var(--line);
  border-radius:var(--r-panel);
  box-shadow:var(--shadow-pop);
  opacity:0;pointer-events:none;
  transition:opacity .13s ease;
}
html[data-ui="next"] .rail.collapsed .nav-item:hover > .submenu,
html[data-ui="next"] .rail.collapsed .nav-item:focus-within > .submenu{
  opacity:1;pointer-events:auto;
}
/* The flyout sets display:block, which makes grid-template-rows inert - but the
   inner wrapper is still at opacity 0 with an indent and a hairline it does not
   want out here. Without this the collapsed rail's flyout would open completely
   invisible. */
html[data-ui="next"] .rail.collapsed .nav-item:hover > .submenu > .submenu-in,
html[data-ui="next"] .rail.collapsed .nav-item:focus-within > .submenu > .submenu-in{
  opacity:1;overflow:visible;
  margin:0;padding:0;border-left:0;
  transition:none;
}
html[data-ui="next"] .rail.collapsed .nav-item:hover > .submenu .item,
html[data-ui="next"] .rail.collapsed .nav-item:focus-within > .submenu .item{
  justify-content:flex-start;padding:7px 9px;gap:10px;
}
html[data-ui="next"] .rail.collapsed .nav-item:hover > .submenu .label,
html[data-ui="next"] .rail.collapsed .nav-item:focus-within > .submenu .label{
  position:static;width:auto;height:auto;margin:0;overflow:visible;
  clip-path:none;white-space:nowrap;
}
/* The count is a corner dot on a collapsed icon; inside the flyout it is a
   normal row, so it goes back to being a number. */
html[data-ui="next"] .rail.collapsed .nav-item:hover > .submenu .count,
html[data-ui="next"] .rail.collapsed .nav-item:focus-within > .submenu .count{
  position:static;min-width:18px;height:18px;padding:0 5px;border-radius:9px;
  font-size:10.5px;color:var(--red-dark);background:rgba(240,60,90,0.1);
  box-shadow:none;margin-left:auto;
}
/* A hairline bridge across the gap, so the pointer can travel from icon to
   flyout without passing over the page and dismissing it. */
html[data-ui="next"] .rail.collapsed .nav-item:hover > .submenu::before{
  content:'';position:absolute;left:-10px;top:0;width:10px;height:100%;
}

/* The alert count still has to be visible - it is the one thing in the nav
   that changes on its own. It shrinks to a dot on the icon. */
html[data-ui="next"] .rail.collapsed .item{position:relative;}
html[data-ui="next"] .rail.collapsed .item .count{
  position:absolute;top:5px;right:9px;
  min-width:8px;height:8px;padding:0;border-radius:4px;
  font-size:0;color:transparent;background:var(--red);
  box-shadow:0 0 0 2px var(--surface);
}
html[data-ui="next"] .rail.collapsed .rail-foot{padding:10px 6px;}

/* ---------- Topbar ---------- */
html[data-ui="next"] .topbar{
  position:sticky;top:0;z-index:30;
  height:var(--topbar-h);
  display:flex;align-items:center;gap:8px;
  padding:0 16px 0 12px;
  background:var(--surface);
  border-bottom:1px solid var(--line);
}
html[data-ui="next"] .topbar .spacer{margin-left:auto;}
html[data-ui="next"] .topbar > *{flex-shrink:0;}
html[data-ui="next"] .topbar .div{width:1px;height:22px;background:var(--line);margin:0 4px;}
html[data-ui="next"] .topbar .impersonating{flex-shrink:1;min-width:0;overflow:hidden;}
@media (max-width:1240px){ html[data-ui="next"] .topbar .impersonating{display:none;} }

html[data-ui="next"] .icon-btn{
  width:30px;height:30px;border-radius:var(--r-ctl);
  display:inline-flex;align-items:center;justify-content:center;
  background:transparent;border:0;
  color:var(--ink-3);cursor:pointer;position:relative;
  transition:background-color .12s ease, color .12s ease;
}
html[data-ui="next"] .icon-btn:hover{background:var(--hover);color:var(--ink);}
html[data-ui="next"] .icon-btn[aria-expanded="true"]{background:var(--hover);color:var(--ink);}
html[data-ui="next"] .icon-btn svg{width:16px;height:16px;}
html[data-ui="next"] .icon-btn .dot-badge{
  position:absolute;top:5px;right:6px;width:6px;height:6px;border-radius:50%;
  background:var(--red);box-shadow:0 0 0 2px var(--surface);
}

/* ---------- Scope selectors ----------
   Not native <select>. A warehouse picker needs a search field, a current-value
   check, grouping and a keyboard path; a native select gives none of those and
   renders differently on every OS. The trigger states both what is being
   scoped and to what, because "Tilburg DC-2" alone is not self-describing. */
html[data-ui="next"] .scope{
  display:inline-flex;align-items:center;gap:7px;
  height:32px;padding:0 9px 0 11px;
  border:1px solid var(--line);border-radius:var(--r-ctl);
  background:var(--surface);cursor:pointer;
  transition:border-color .15s ease, background-color .15s ease;
  max-width:230px;
}
html[data-ui="next"] .scope:hover{border-color:rgba(10,25,50,0.2);}
html[data-ui="next"] .scope[aria-expanded="true"]{border-color:var(--blue);box-shadow:var(--focus-ring);}
html[data-ui="next"] .scope .s-label{font-size:11.5px;color:var(--ink-4);white-space:nowrap;}
html[data-ui="next"] .scope .s-value{
  font-size:13px;font-weight:500;color:var(--ink);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;
}
html[data-ui="next"] .scope .s-chev{width:11px;height:11px;color:var(--ink-4);flex-shrink:0;margin-left:1px;}

/* ---------- User chip ---------- */
html[data-ui="next"] .userchip{
  display:inline-flex;align-items:center;gap:8px;
  padding:4px 7px 4px 4px;border-radius:var(--r-ctl);
  background:transparent;border:0;cursor:pointer;
  transition:background-color .12s ease;
}
html[data-ui="next"] .userchip:hover,
html[data-ui="next"] .userchip[aria-expanded="true"]{background:var(--hover);}
html[data-ui="next"] .userchip .avatar{
  width:26px;height:26px;border-radius:50%;flex-shrink:0;
  display:flex;align-items:center;justify-content:center;
  font-family:var(--font-head);font-weight:700;font-size:11px;color:#fff;
  background:var(--blue);
}
html[data-ui="next"] .userchip .who{line-height:1.2;white-space:nowrap;text-align:left;}
html[data-ui="next"] .userchip .who b{display:block;font-family:var(--font-body);font-weight:500;font-size:12.5px;color:var(--ink);}
html[data-ui="next"] .userchip .who span{font-size:11px;color:var(--ink-4);}
html[data-ui="next"] .userchip .u-chev{width:11px;height:11px;color:var(--ink-4);flex-shrink:0;}
@media (max-width:900px){ html[data-ui="next"] .userchip .who{display:none;} }

/* ---------- Popovers ----------
   One component behind every menu on the page: scope pickers, notifications,
   profile. Same geometry, same motion, same keyboard contract. */
html[data-ui="next"] .pop{
  position:absolute;z-index:80;
  min-width:200px;
  background:var(--surface);
  border:1px solid var(--line);
  border-radius:var(--r-panel);
  box-shadow:var(--shadow-pop);
  padding:5px;
  opacity:0;visibility:hidden;transform:translateY(-4px) scale(.985);
  transform-origin:top right;
  transition:opacity .13s ease, transform .13s var(--ease), visibility .13s;
}
html[data-ui="next"] .pop.open{opacity:1;visibility:visible;transform:none;}
html[data-ui="next"] .pop-head{
  font-size:10.5px;letter-spacing:0.07em;text-transform:uppercase;
  color:var(--ink-4);padding:7px 9px 5px;
}
html[data-ui="next"] .pop-sep{height:1px;background:var(--line-soft);margin:5px -5px;}
html[data-ui="next"] .pop-list{max-height:288px;overflow-y:auto;margin:0;padding:0;list-style:none;}

html[data-ui="next"] .pop-item{
  display:flex;align-items:center;gap:9px;width:100%;
  padding:7px 9px;border-radius:var(--r-item);
  font-size:13px;color:var(--ink-2);
  background:none;border:0;text-align:left;cursor:pointer;
  transition:background-color .1s ease;
}
html[data-ui="next"] .pop-item > svg{width:15px;height:15px;flex-shrink:0;color:var(--ink-4);}
html[data-ui="next"] .pop-item:hover,
html[data-ui="next"] .pop-item.cursor{background:var(--hover);}
html[data-ui="next"] .pop-item.cursor{outline:none;}
html[data-ui="next"] .pop-item.selected{color:var(--blue);font-weight:500;}
html[data-ui="next"] .pop-item.selected > svg{color:var(--blue);}
html[data-ui="next"] .pop-item .tick{margin-left:auto;width:14px;height:14px;color:var(--blue);flex-shrink:0;}
html[data-ui="next"] .pop-item .meta{margin-left:auto;font-size:11.5px;color:var(--ink-4);flex-shrink:0;}
html[data-ui="next"] .pop-item .stack{min-width:0;line-height:1.3;}
html[data-ui="next"] .pop-item .stack b{display:block;font-weight:500;color:var(--ink);font-size:13px;}
html[data-ui="next"] .pop-item .stack span{display:block;font-size:11.5px;color:var(--ink-4);}
html[data-ui="next"] .pop-item.danger{color:var(--red-dark);}
html[data-ui="next"] .pop-item.danger > svg{color:var(--red-dark);}
html[data-ui="next"] .pop-item.danger:hover,
html[data-ui="next"] .pop-item.danger.cursor{background:rgba(240,60,90,0.06);}

html[data-ui="next"] .pop-search{padding:4px 4px 6px;}
html[data-ui="next"] .pop-search input{
  width:100%;font-family:var(--font-body);font-size:13px;color:var(--ink);
  background:var(--surface);border:1px solid var(--line);
  border-radius:var(--r-ctl);padding:6px 10px;
}
html[data-ui="next"] .pop-search input:focus{outline:none;border-color:var(--blue);box-shadow:var(--focus-ring);}
html[data-ui="next"] .pop-empty{padding:10px 9px;font-size:12.5px;color:var(--ink-4);}

/* The profile menu leads with who you are, so "signed in as" never has to be
   guessed from a two-letter avatar. */
html[data-ui="next"] .pop-user{display:flex;align-items:center;gap:10px;padding:8px 9px 9px;}
html[data-ui="next"] .pop-user .avatar{
  width:32px;height:32px;border-radius:50%;flex-shrink:0;
  display:flex;align-items:center;justify-content:center;
  font-family:var(--font-head);font-weight:700;font-size:12.5px;color:#fff;background:var(--blue);
}
html[data-ui="next"] .pop-user .who{min-width:0;line-height:1.3;}
html[data-ui="next"] .pop-user .who b{display:block;font-weight:500;font-size:13px;color:var(--ink);}
html[data-ui="next"] .pop-user .who span{display:block;font-size:11.5px;color:var(--ink-4);overflow:hidden;text-overflow:ellipsis;}

/* ---------- Page frame ----------
   No fixed max-width. Collapsing the rail frees 176px and the content takes
   all of it: the KPI grid gains a column, the chart plots wider. A capped
   column would have parked that space in a margin and made the collapse
   cosmetic. */
html[data-ui="next"] .shell{padding-left:var(--rail-w);min-height:100vh;transition:padding-left .22s var(--ease);}
html[data-ui="next"] .page{padding:20px 22px 40px;}
html[data-ui="next"] .page-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin-bottom:20px;}
html[data-ui="next"] .page-head .sub{color:var(--ink-4);font-size:12.5px;margin-top:3px;}
html[data-ui="next"] .page-section{margin-bottom:20px;}

/* ---------- Charts ----------
   Fixed categorical order, never cycled: blue, cyan, amber. Validated on the
   light surface for lightness, chroma, CVD separation, the normal-vision floor
   and 3:1 contrast. Brand cyan #00baff is NOT in the set - 2.16:1 cannot carry
   a mark - so charts use the stepped #009bd0. Brand red is reserved for status
   and is never a series. */

html[data-ui="next"] .legend{display:flex;flex-wrap:wrap;gap:14px;}
html[data-ui="next"] .legend .key{display:inline-flex;align-items:center;gap:6px;font-size:12px;color:var(--ink-3);}
html[data-ui="next"] .legend .key i{width:8px;height:8px;border-radius:2px;display:inline-block;flex-shrink:0;}

html[data-ui="next"] .axis-y text,
html[data-ui="next"] .axis-x text{font-family:var(--font-body);font-size:10px;fill:var(--ink-4);}
html[data-ui="next"] .gridline{stroke:var(--line-soft);stroke-width:1;shape-rendering:crispEdges;}
html[data-ui="next"] .axis-line{stroke:var(--line);stroke-width:1;shape-rendering:crispEdges;}

html[data-ui="next"] .bar-seg{stroke:var(--surface);stroke-width:1.25;transition:opacity .12s ease;}
html[data-ui="next"] .chart.hovering .bar-seg{opacity:.35;}
html[data-ui="next"] .chart .col.on .bar-seg{opacity:1;}
html[data-ui="next"] .chart .col rect.hit{fill:transparent;}
html[data-ui="next"] .chart .col.on rect.hit{fill:rgba(10,25,50,0.04);}
/* One selective direct label: the peak. Never a number on every bar. */
html[data-ui="next"] .peak-label{font-family:var(--font-head);font-weight:700;font-size:10px;fill:var(--ink-3);}
html[data-ui="next"] .peak-tick{stroke:var(--line);stroke-width:1;}

html[data-ui="next"] .spark-line{fill:none;stroke:#009bd0;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round;}
html[data-ui="next"] .spark-band{fill:rgba(10,25,50,0.045);}
html[data-ui="next"] .spark-dot{fill:#009bd0;stroke:var(--surface);stroke-width:1.75;}

html[data-ui="next"] .hbar{display:flex;flex-direction:column;gap:13px;}
html[data-ui="next"] .hbar .row{display:grid;grid-template-columns:1fr auto;gap:6px;align-items:baseline;}
html[data-ui="next"] .hbar .name{font-size:12.5px;color:var(--ink-2);}
html[data-ui="next"] .hbar .val{font-family:var(--font-head);font-weight:700;font-size:12.5px;color:var(--ink);font-variant-numeric:tabular-nums;}
html[data-ui="next"] .hbar .pct{color:var(--ink-4);font-weight:400;font-family:var(--font-body);margin-left:4px;}
html[data-ui="next"] .hbar .track{grid-column:1 / -1;height:6px;border-radius:3px;background:rgba(10,25,50,0.05);overflow:hidden;}
html[data-ui="next"] .hbar .fill{display:block;height:100%;border-radius:3px;transition:width .3s var(--ease);}

html[data-ui="next"] .tooltip{
  position:absolute;pointer-events:none;opacity:0;
  transform:translate(-50%,-100%) translateY(-10px);
  background:var(--ink);
  color:#fff;border-radius:var(--r-ctl);padding:8px 10px;
  font-size:11.5px;line-height:1.5;white-space:nowrap;
  box-shadow:0 6px 18px -6px rgba(10,25,50,0.4);
  transition:opacity .1s ease;z-index:50;
}
html[data-ui="next"] .tooltip b{color:#fff;font-family:var(--font-head);font-size:11.5px;}
html[data-ui="next"] .tooltip .tt-row{display:flex;align-items:center;gap:6px;}
html[data-ui="next"] .tooltip .tt-row i{width:7px;height:7px;border-radius:2px;flex-shrink:0;}
html[data-ui="next"] .tooltip .tt-row span{margin-left:auto;padding-left:18px;font-variant-numeric:tabular-nums;}
html[data-ui="next"] .tooltip .tt-total{margin-top:4px;padding-top:4px;border-top:1px solid rgba(255,255,255,.18);}

html[data-ui="next"] .table-toggle{
  font-family:var(--font-body);font-weight:500;font-size:12px;
  color:var(--ink-4);background:none;border:0;cursor:pointer;padding:0;
  transition:color .12s ease;
}
html[data-ui="next"] .table-toggle:hover{color:var(--ink-2);}
html[data-ui="next"] table.data{width:100%;border-collapse:collapse;font-size:12px;margin-top:12px;}
html[data-ui="next"] table.data th{text-align:right;font-weight:500;font-size:11px;color:var(--ink-4);padding:5px 8px;border-bottom:1px solid var(--line);}
html[data-ui="next"] table.data th:first-child,
html[data-ui="next"] table.data td:first-child{text-align:left;}
html[data-ui="next"] table.data td{text-align:right;padding:5px 8px;border-bottom:1px solid var(--line-soft);color:var(--ink-2);font-variant-numeric:tabular-nums;}

/* ---------- Feed ---------- */
html[data-ui="next"] .feed .ev{display:flex;align-items:center;gap:11px;padding:9px var(--pad);border-top:1px solid var(--line-soft);transition:background-color .12s ease;}
html[data-ui="next"] .feed .ev:first-child{border-top:none;}
html[data-ui="next"] .feed .ev:hover{background:var(--hover);}
html[data-ui="next"] .feed .ev .marker{width:6px;height:6px;border-radius:50%;flex-shrink:0;}
html[data-ui="next"] .feed .ev .body{flex:1;min-width:0;}
html[data-ui="next"] .feed .ev .body b{font-family:var(--font-body);font-weight:500;color:var(--ink);font-size:13.5px;}
html[data-ui="next"] .feed .ev .body span{font-size:12px;color:var(--ink-4);margin-left:8px;}
html[data-ui="next"] .feed .ev time{font-size:12px;color:var(--ink-4);white-space:nowrap;font-variant-numeric:tabular-nums;}

/* ---------- Login ---------- */
html[data-ui="next"] .auth{min-height:100vh;display:flex;flex-direction:column;background:var(--field);}
html[data-ui="next"] .auth-head{
  display:flex;align-items:center;justify-content:space-between;gap:18px;
  max-width:1080px;width:100%;margin:0 auto;padding:18px 24px;
}
html[data-ui="next"] .auth-head .brand{display:flex;align-items:center;gap:8px;font-family:var(--font-head);font-weight:700;font-size:17px;letter-spacing:-0.018em;color:var(--ink);}
html[data-ui="next"] .auth-head .brand img{height:22px;width:auto;}

html[data-ui="next"] .lang-switch{display:flex;align-items:center;border:1px solid var(--line);border-radius:var(--r-ctl);overflow:hidden;}
html[data-ui="next"] .lang-switch a{font-family:var(--font-body);font-weight:500;font-size:12px;color:var(--ink-3);padding:5px 10px;transition:background-color .12s ease, color .12s ease;}
html[data-ui="next"] .lang-switch a:hover:not(.lang-active){background:var(--hover);}
html[data-ui="next"] .lang-switch a.lang-active{background:var(--tint);color:var(--blue);font-weight:700;}

html[data-ui="next"] .auth-body{
  flex:1;display:grid;grid-template-columns:1fr 1fr;
  gap:clamp(40px,6vw,96px);align-items:center;
  max-width:1080px;width:100%;margin:0 auto;padding:clamp(24px,4vw,48px) 24px 48px;
}
html[data-ui="next"] .auth-pitch h1{margin-bottom:14px;}
html[data-ui="next"] .auth-pitch .lede{font-size:15px;line-height:1.6;color:var(--ink-3);max-width:44ch;margin-bottom:26px;}
html[data-ui="next"] .auth-points{list-style:none;margin:0;padding:0;}
html[data-ui="next"] .auth-points li{
  display:flex;align-items:flex-start;gap:10px;
  padding:9px 0;border-top:1px solid var(--line-soft);
  color:var(--ink-2);font-size:13.5px;
}
html[data-ui="next"] .auth-points li:first-child{border-top:none;}
html[data-ui="next"] .auth-points li svg{width:15px;height:15px;flex-shrink:0;margin-top:2px;color:var(--ink-4);}
html[data-ui="next"] .auth-trust{margin-top:26px;display:flex;gap:26px;flex-wrap:wrap;font-size:12px;color:var(--ink-4);}
html[data-ui="next"] .auth-trust b{font-family:var(--font-head);font-weight:700;color:var(--ink-3);}

html[data-ui="next"] .auth-card{
  background:var(--surface);
  border:1px solid var(--line);
  border-radius:var(--r-panel);
  box-shadow:var(--shadow);
  padding:clamp(24px,2.6vw,30px);
  max-width:400px;width:100%;justify-self:end;
}
html[data-ui="next"] .auth-card h2{font-size:19px;margin-bottom:5px;}
html[data-ui="next"] .auth-card .card-lede{color:var(--ink-3);font-size:13.5px;margin-bottom:22px;}

html[data-ui="next"] .field{margin-bottom:13px;}
html[data-ui="next"] .field label{display:block;font-size:12.5px;font-weight:500;color:var(--ink-2);margin-bottom:5px;}
html[data-ui="next"] .field input[type=email],
html[data-ui="next"] .field input[type=password]{
  width:100%;font-family:var(--font-body);font-size:14px;color:var(--ink);
  background:var(--surface);border:1px solid var(--line);
  border-radius:var(--r-ctl);padding:9px 11px;
  transition:border-color .15s ease, box-shadow .15s ease;
}
html[data-ui="next"] .field input::placeholder{color:var(--ink-4);}
html[data-ui="next"] .field input:focus{outline:none;border-color:var(--blue);box-shadow:var(--focus-ring);}
html[data-ui="next"] .field-row{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:14px 0 16px;}
html[data-ui="next"] .check{display:inline-flex;align-items:center;gap:7px;font-size:13px;color:var(--ink-3);cursor:pointer;}
html[data-ui="next"] .check input{accent-color:var(--blue);width:14px;height:14px;}
html[data-ui="next"] .link{font-weight:500;font-size:13px;color:var(--blue);}
html[data-ui="next"] .link:hover{text-decoration:underline;text-underline-offset:2px;}

html[data-ui="next"] .or{display:flex;align-items:center;gap:10px;margin:18px 0;color:var(--ink-4);font-size:12px;}
html[data-ui="next"] .or::before,
html[data-ui="next"] .or::after{content:'';flex:1;height:1px;background:var(--line-soft);}

html[data-ui="next"] .notice{
  border-radius:var(--r-ctl);padding:12px 14px;margin-bottom:18px;
  background:rgba(240,60,90,0.05);border:1px solid rgba(240,60,90,0.2);
}
html[data-ui="next"] .notice .notice-head{display:flex;align-items:center;gap:7px;font-weight:500;font-size:13px;color:var(--red-dark);margin-bottom:4px;}
html[data-ui="next"] .notice .notice-head svg{width:14px;height:14px;flex-shrink:0;}
html[data-ui="next"] .notice p{font-size:12.5px;color:var(--ink-2);margin:0;}
html[data-ui="next"] .notice .hint{margin-top:6px;color:var(--ink-3);}

html[data-ui="next"] .auth-foot{
  max-width:1080px;width:100%;margin:0 auto;padding:16px 24px 24px;
  display:flex;align-items:center;justify-content:space-between;gap:16px;
  border-top:1px solid var(--line-soft);font-size:12px;color:var(--ink-4);
}
html[data-ui="next"] .auth-foot a:hover{color:var(--ink-2);}
html[data-ui="next"] .auth-foot .links{display:flex;gap:18px;}


/* =========================================================
   Brand presence - third attempt: the blue lives in the rail

   Two rejected versions before this one, and both are worth keeping on the
   record because each was wrong in its own way.

     1. A DARK RAIL. Rejected outright. A dark rail is not "more brand", it is a
        different material - it changes what the product is made of, on the one
        surface the user looks past all day.
     2. A FLAT TINT ON THE RAIL PLUS A WASH ON THE PAGE. Half of it landed. The
        wash - soft brand radials with no edges - was the part that worked. The
        flat #eef4fb rail was the part that did not, and the reason is that a
        flat fill and a wash are not the same idea at two strengths. A flat fill
        makes the rail A COLOURED PANEL; a wash makes it LIT. One reads as a
        component that has been painted, the other as light in the room.

   So this version keeps one move and moves the other:

     - THE PAGE GROUND GOES BACK TO PLAIN. No wash behind the panels. The
       content area is neutral again, which is what the data wants anyway.
     - THE WASH MOVES INTO THE RAIL. Same device, same two brand hues, re-aimed
       at a 236px column instead of a 1500px window: cyan blooming from the top
       corner behind the logo, blue settling into the bottom, and a faint third
       against the right edge to keep the middle from going dead white.

   The rail's BASE STAYS WHITE. That is the whole difference from attempt 2, and
   it has a practical dividend as well as a visual one: every rule the rail
   already had was tuned against white, so --hover, --tint and the hairlines all
   keep working untouched. The previous version had to restate three of them,
   which is the usual sign that a change went a layer too deep.

   Percentages, not pixels, on every radial - the rail collapses to 64px and the
   gradient has to re-aim itself when it does.

   What is still not touched: panels, tables, charts, the KPI grid, form fields
   and every status colour. The instruments stay exactly as they were.
   ========================================================= */
html[data-ui="next"][data-brand="on"] .rail{
  background:
    radial-gradient(150% 56% at 0% -2%,    rgba(0,186,255,.16), transparent 74%),
    radial-gradient(150% 60% at 100% 102%, rgba(0,100,205,.12), transparent 76%),
    radial-gradient(120% 40% at 92% 36%,   rgba(0,186,255,.06), transparent 72%),
    var(--surface);
  /* The one edge that changes: a blue hairline instead of a grey one, so the
     rail's boundary belongs to the same idea as its fill. */
  border-right-color:rgba(0,100,205,.10);
}

/* ---------- The mark in the rail ----------
   The same C that frames the copy on the sign-in page, brought into the app so
   the two do not feel like different products. On login it encloses the text;
   here it sits in the empty column BELOW the navigation, which is the closest
   equivalent the rail has to open space.

   Everything is proportional to one number, exactly as on the sign-in page: the
   ring is 359.9% of the mark box's width, offset -19.4% vertically, and the
   stroke is 17.7% of the ring's diameter. Those constants come from the logo
   file via landing.css and are not eyeballed, which is why they are written out
   rather than rounded.

   No clipping is needed anywhere. The mark bleeds off the left edge and the
   bottom, and the rail is fixed to both - so the overflow is off-screen rather
   than over the content. That matters: putting overflow on .rail would have
   clipped the collapsed rail's flyout submenus, which are the one thing that
   deliberately escapes it.

   No perpetual drift either, unlike the sign-in page. A slow ambient animation
   is fine on a page someone looks at for ten seconds; in the chrome of a tool
   they keep open all day it is movement in the corner of the eye with nothing
   to say. This moves only in response to the cursor. */
html[data-ui="next"] .rail-mark{ display:none; }
html[data-ui="next"][data-brand="on"] .rail-mark{
  --mw:150px;
  display:block;
  position:absolute;
  left:-40px; bottom:60px;
  width:var(--mw);
  aspect-ratio:290 / 752;
  z-index:0;
  pointer-events:none;
}
/* Nothing to put it in at 60px, and a mark that has to be cropped to fit is no
   longer the mark. */
html[data-ui="next"] .rail.collapsed .rail-mark{ display:none; }

html[data-ui="next"] .rail-c{
  position:absolute; display:block;
  left:0; top:-19.4%;
  width:359.9%;
  aspect-ratio:1;
  box-sizing:border-box;
  border:calc(var(--mw) * 3.599 * 0.177) solid rgba(0,155,208,.09);
  border-radius:50%;
  /* The six points that form the C's angled tips, measured off the logo file. */
  clip-path:polygon(0% 0%, 15.3% 14.0%, 28.8% 26.1%, 28.8% 73.9%, 15.3% 86.0%, 0% 100%);

  /* Fades out as it rises, so the arc is ground down where the rail is empty and
     has dissolved by the time it reaches the menu.

     The mask is on THE RING, not on the mark box, and that is the whole point of
     the two extra lines under it. The ring is far taller than its mark box and
     overflows it in every direction; a mask on the box still masks the overflow,
     but `mask` shorthand leaves mask-repeat at `repeat`, so the gradient TILES
     and the arc comes back to full strength exactly where it clears the box - which here is exactly where the nav is. Masking the ring against its own
     bounds, once, removes the tiling and the guesswork together. */
  -webkit-mask:linear-gradient(0deg, #000 20%, rgba(0,0,0,.55) 58%, transparent 92%) no-repeat 0 0 / 100% 100%;
  mask:linear-gradient(0deg, #000 20%, rgba(0,0,0,.55) 58%, transparent 92%) no-repeat 0 0 / 100% 100%;

  /* --px/--py are written on <html> by mark-pointer.js, so this inherits them
     without anything having to know the element exists. Both live on this one
     element rather than being split across two as they are on the sign-in page
     - there is no drift animation here to take ownership of `rotate`.

     Half the sign-in page's travel. The mark there is nearly a thousand pixels
     across in an open page; this one is a third of that in a 236px column, and
     the same distance would walk it out of the rail. */
  translate:calc(var(--px, 0) * 14px) calc(var(--py, 0) * 9px);
  rotate:calc(var(--px, 0) * 2deg + var(--py, 0) * 0.9deg);
  transition:translate 520ms cubic-bezier(.22,.68,0,1),
             rotate    520ms cubic-bezier(.22,.68,0,1);
}

@media (prefers-reduced-motion:reduce){
  /* The mark stays - it is composition, not animation. Only the response goes. */
  html[data-ui="next"] .rail-c{ translate:none; rotate:none; transition:none; }
}

/* =========================================================
   Small screens

   The concept was desktop-only, and measurably so: at 390px the rail still took
   236px of a 390px viewport, the content column was 154px wide, the four-up stat
   row rendered as four 17px columns, and the document was 800px against a 390px
   window. The app it mirrors is better than that - CaptureTechLayout.razor uses
   Bootstrap's d-none/d-sm-inline on the picker labels - so shipping the concept
   as it stood would have been a regression on a phone.

   Three ideas, in order of how much they do:

     1. BELOW 1024px THE RAIL LEAVES THE LAYOUT. It stops being a column the page
        is padded around and becomes a drawer over it. --rail-w goes to 0, so
        every existing offset follows without a single one being restated.
     2. GRIDS STEP DOWN rather than squeezing. Four to two to one, at widths
        chosen so a stat card never goes below about 150px, which is where its
        number starts wrapping.
     3. THE TOPBAR SHEDS RATHER THAN SHRINKS. Labels first, then the whole scope
        picker text, because a picker you cannot read is worse than one you have
        to open to read.
   ========================================================= */

/* ---- Grids ---- */
@media (max-width:1180px){
  html[data-ui="next"] .grid-4{grid-template-columns:repeat(2,minmax(0,1fr));}
  html[data-ui="next"] .grid-2-1{grid-template-columns:minmax(0,1fr);}
}
@media (max-width:640px){
  html[data-ui="next"] .grid-4{grid-template-columns:minmax(0,1fr);}
}

/* ---- The rail becomes a drawer ---- */
@media (max-width:1023px){
  /* Zero width means the shell's padding-left, the topbar's offset and every
     fixed calc() built on --rail-w all resolve to the full viewport. Nothing
     else has to know the rail moved. */
  html[data-ui="next"] body{--rail-w:0px;}
  html[data-ui="next"] body.rail-collapsed{--rail-w:0px;}

  html[data-ui="next"] .rail{
    width:236px;
    translate:-100% 0;
    transition:translate .24s var(--ease);
    z-index:90;
    box-shadow:none;
  }
  /* The drawer is opened by the same button that used to collapse the rail. */
  html[data-ui="next"] body.rail-open .rail{translate:0 0;box-shadow:var(--shadow-pop);}

  /* Collapsed-icon mode is meaningless here: the rail is either over the page or
     off it, so the 60px variant and its hover flyouts are switched off. */
  html[data-ui="next"] .rail.collapsed{width:236px;}
  html[data-ui="next"] .rail.collapsed .brand .word,
html[data-ui="next"] .rail.collapsed .item .label{position:static;width:auto;height:auto;margin:0;clip-path:none;overflow:visible;}
  html[data-ui="next"] .rail.collapsed .item{justify-content:flex-start;padding:8px 9px;gap:9px;}
  html[data-ui="next"] .rail.collapsed .chev{display:none;}
  html[data-ui="next"] .rail.collapsed .nav-item > .submenu{display:grid;}
  html[data-ui="next"] .rail.collapsed .nav-group{height:auto;padding:0 9px;margin:11px 0 4px;text-indent:0;background:none;}

  html[data-ui="next"] .rail-scrim{
    position:fixed;inset:0;z-index:85;
    background:rgba(10,18,30,.42);
    opacity:0;visibility:hidden;
    transition:opacity .2s ease, visibility .2s;
  }
  html[data-ui="next"] body.rail-open .rail-scrim{opacity:1;visibility:visible;}

  html[data-ui="next"] .page{padding:16px 14px 32px;}
  html[data-ui="next"] .page-head{flex-direction:column;align-items:flex-start;gap:10px;}
}

/* ---- The topbar sheds ---- */
@media (max-width:1240px){ html[data-ui="next"] .topbar .impersonating{display:none;} }
@media (max-width:900px){
  html[data-ui="next"] .userchip .who{display:none;}
  html[data-ui="next"] .topbar .s-label{display:none;}
  html[data-ui="next"] .topbar{padding:0 10px;gap:6px;}
  html[data-ui="next"] .topbar .div{display:none;}
}
@media (max-width:600px){
  /* Below this the two pickers cannot both show a value, so the company one
     goes: a warehouse is switched many times a session, a company almost never,
     and it is still reachable from the drawer's own scope row. */
  html[data-ui="next"] #company-btn{display:none;}
  html[data-ui="next"] .scope{max-width:44vw;}
  html[data-ui="next"] .scope .s-value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
}

/* ---- Popovers ----
   A closed .pop hides with `visibility`, which keeps it in layout. On a phone
   its 250-320px min-width sat past the right edge and stretched the document to
   800px on a 390px screen: the whole page scrolled sideways because of menus
   nobody had opened. Taking it out of flow when closed fixes the overflow
   without losing the transition, which runs on opacity and transform. */
html[data-ui="next"] .pop{display:none;}
html[data-ui="next"] .pop.open,
html[data-ui="next"] .pop.closing{display:block;}
@media (max-width:600px){
  html[data-ui="next"] .pop{max-width:calc(100vw - 20px);}
}

/* ---- Dialogs ---- */
@media (max-width:700px){
  html[data-ui="next"] .dlg-scrim{padding:0;align-items:flex-end;}
  html[data-ui="next"] .dlg,
html[data-ui="next"] .dlg.s,
html[data-ui="next"] .dlg.m,
html[data-ui="next"] .dlg.l{
    max-width:100%;max-height:92vh;
    border-radius:var(--r-panel) var(--r-panel) 0 0;
    border-bottom:0;
  }
  /* A dialog that fills the width has no room for two columns. */
  html[data-ui="next"] .dlg .grid[style*="repeat(2"],
html[data-ui="next"] .dlg .form-grid{grid-template-columns:minmax(0,1fr) !important;}
  html[data-ui="next"] .qv-strip{grid-auto-flow:row;grid-auto-columns:auto;}
  html[data-ui="next"] .dlg-foot{flex-wrap:wrap;}
}

/* ---------- concept-pages.css ---------- */
/* =========================================================
   CT Insight - shared page primitives
   Loaded after concept.css. Same tokens, same rules.

   concept.css is the SHELL and the system: tokens, rail, topbar, popovers,
   panels, charts. This file is what the individual pages are built from:
   tables, forms, toolbars, tabs, empty states, canvases.

   The split is deliberate. If a pattern only ever served one screen it would
   live in that screen, not here - everything below is used by at least two
   pages, which is the test for whether it belongs in a shared layer at all.
   ========================================================= */

/* ---------- Toolbar: search + filters above a table ----------
   Filters live in one row above the data, never in a sidebar or a modal. */
html[data-ui="next"] .toolbar{
  display:flex;align-items:center;gap:8px;flex-wrap:wrap;
  padding:10px var(--pad);border-bottom:1px solid var(--line-soft);
}
html[data-ui="next"] .toolbar .grow{flex:1;min-width:0;}
html[data-ui="next"] .search{
  display:flex;align-items:center;gap:7px;
  height:32px;padding:0 10px;min-width:190px;max-width:320px;flex:1;
  border:1px solid var(--line);border-radius:var(--r-ctl);background:var(--surface);
  transition:border-color .15s ease, box-shadow .15s ease;
}
html[data-ui="next"] .search:focus-within{border-color:var(--blue);box-shadow:var(--focus-ring);}
html[data-ui="next"] .search svg{width:14px;height:14px;color:var(--ink-4);flex-shrink:0;}
html[data-ui="next"] .search input{border:0;outline:none;background:none;font-family:var(--font-body);font-size:13px;color:var(--ink);width:100%;}
html[data-ui="next"] .search input::placeholder{color:var(--ink-4);}

/* ---------- Segmented control ---------- */
html[data-ui="next"] .seg{display:inline-flex;border:1px solid var(--line);border-radius:var(--r-ctl);overflow:hidden;background:var(--surface);}
html[data-ui="next"] .seg button{
  font-family:var(--font-body);font-weight:500;font-size:12.5px;
  color:var(--ink-3);background:none;border:0;padding:6px 11px;cursor:pointer;
  border-right:1px solid var(--line);transition:background-color .12s ease, color .12s ease;
}
html[data-ui="next"] .seg button:last-child{border-right:0;}
html[data-ui="next"] .seg button:hover{background:var(--hover);}
html[data-ui="next"] .seg button.on{background:var(--tint);color:var(--blue);font-weight:700;}

/* ---------- Tabs ---------- */
html[data-ui="next"] .tabs{display:flex;gap:2px;border-bottom:1px solid var(--line);margin-bottom:18px;}
html[data-ui="next"] .tabs :is(button,a){
  font-family:var(--font-body);font-weight:500;font-size:13.5px;
  color:var(--ink-3);background:none;border:0;cursor:pointer;
  padding:9px 12px;border-bottom:2px solid transparent;margin-bottom:-1px;
  transition:color .12s ease, border-color .12s ease;
}
html[data-ui="next"] .tabs :is(button,a):hover{color:var(--ink);}
html[data-ui="next"] .tabs :is(button,a).on{color:var(--blue);border-bottom-color:var(--blue);font-weight:700;}

/* ---------- Data table ----------
   Rows, not cards. Sticky header, tabular figures, one hairline per row. */
html[data-ui="next"] .tbl-wrap{overflow-x:auto;}
html[data-ui="next"] table.tbl{width:100%;border-collapse:collapse;font-size:13px;}
html[data-ui="next"] table.tbl th{
  position:sticky;top:0;z-index:1;background:var(--surface);
  text-align:left;font-weight:500;font-size:11.5px;color:var(--ink-4);
  padding:9px var(--pad);border-bottom:1px solid var(--line);white-space:nowrap;
}
html[data-ui="next"] table.tbl th.num,
html[data-ui="next"] table.tbl td.num{text-align:right;font-variant-numeric:tabular-nums;}
html[data-ui="next"] table.tbl td{
  padding:9px var(--pad);border-bottom:1px solid var(--line-soft);
  color:var(--ink-2);vertical-align:middle;
}
html[data-ui="next"] table.tbl tbody tr{transition:background-color .1s ease;}
html[data-ui="next"] table.tbl tbody tr:hover{background:var(--hover);cursor:pointer;}
html[data-ui="next"] table.tbl tbody tr:last-child td{border-bottom:0;}
html[data-ui="next"] table.tbl td b{font-weight:500;color:var(--ink);}
html[data-ui="next"] table.tbl td .sub{display:block;font-size:11.5px;color:var(--ink-4);}
html[data-ui="next"] .tbl-foot{
  display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:10px var(--pad);border-top:1px solid var(--line-soft);
  font-size:12px;color:var(--ink-4);
}
html[data-ui="next"] .pager{display:flex;gap:4px;}
html[data-ui="next"] .pager button{
  min-width:26px;height:26px;padding:0 7px;border-radius:var(--r-item);
  border:1px solid var(--line);background:var(--surface);
  font-family:var(--font-body);font-size:12px;color:var(--ink-3);cursor:pointer;
}
html[data-ui="next"] .pager button:hover{background:var(--hover);color:var(--ink);}
html[data-ui="next"] .pager button.on{background:var(--tint);border-color:rgba(0,100,205,0.2);color:var(--blue);font-weight:500;}

/* ---------- Dot status, for inside table rows ---------- */
html[data-ui="next"] .dot{display:inline-flex;align-items:center;gap:6px;white-space:nowrap;font-size:12.5px;}
html[data-ui="next"] .dot::before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor;flex-shrink:0;}
html[data-ui="next"] .dot-good{color:#12806a;}
html[data-ui="next"] .dot-warn{color:#a9670b;}
html[data-ui="next"] .dot-crit{color:var(--red-dark);}
html[data-ui="next"] .dot-idle{color:var(--ink-4);}
/* Fifth state, and the only one added for the Identity history: the app gives
   "Disconnected" its own colour (bg-info) because it is not a failure and not a
   clean logout. Mapped onto --blue, which the system already owns. */
html[data-ui="next"] .dot-info{color:var(--blue);}

/* ---------- Forms ---------- */
html[data-ui="next"] .form{max-width:640px;}
html[data-ui="next"] .form-section{margin-bottom:22px;}
html[data-ui="next"] .form-section > h4{margin-bottom:3px;}
html[data-ui="next"] .form-section > .hint{font-size:12.5px;color:var(--ink-4);margin-bottom:12px;}
html[data-ui="next"] .form-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;}
html[data-ui="next"] .form-grid.one{grid-template-columns:1fr;}
html[data-ui="next"] .inp{
  width:100%;font-family:var(--font-body);font-size:13.5px;color:var(--ink);
  background:var(--surface);border:1px solid var(--line);
  border-radius:var(--r-ctl);padding:8px 10px;
  transition:border-color .15s ease, box-shadow .15s ease;
}
html[data-ui="next"] .inp:focus{outline:none;border-color:var(--blue);box-shadow:var(--focus-ring);}
html[data-ui="next"] .inp::placeholder{color:var(--ink-4);}
html[data-ui="next"] select.inp{appearance:none;-webkit-appearance:none;cursor:pointer;padding-right:28px;}
html[data-ui="next"] .lbl{display:block;font-size:12.5px;font-weight:500;color:var(--ink-2);margin-bottom:5px;}
html[data-ui="next"] .help{font-size:11.5px;color:var(--ink-4);margin-top:4px;}

/* A settings row: label + description left, control right. */
html[data-ui="next"] .set-row{
  display:flex;align-items:center;justify-content:space-between;gap:20px;
  padding:13px var(--pad);border-top:1px solid var(--line-soft);
}
html[data-ui="next"] .set-row:first-child{border-top:0;}
html[data-ui="next"] .set-row .set-text{min-width:0;}
html[data-ui="next"] .set-row .set-text b{display:block;font-weight:500;font-size:13.5px;color:var(--ink);}
html[data-ui="next"] .set-row .set-text span{display:block;font-size:12px;color:var(--ink-4);margin-top:1px;}
html[data-ui="next"] .set-row .set-ctl{flex-shrink:0;}

/* Switch. The knob moves, so on/off is never colour alone. */
html[data-ui="next"] .switch{position:relative;display:inline-block;width:36px;height:20px;flex-shrink:0;}
html[data-ui="next"] .switch input{opacity:0;width:0;height:0;}
html[data-ui="next"] .switch .track{
  position:absolute;inset:0;border-radius:10px;cursor:pointer;
  background:rgba(10,25,50,0.15);transition:background-color .16s ease;
}
html[data-ui="next"] .switch .track::before{
  content:"";position:absolute;left:2px;top:2px;width:16px;height:16px;
  border-radius:50%;background:#fff;box-shadow:0 1px 2px rgba(10,25,50,0.25);
  transition:transform .16s var(--ease);
}
html[data-ui="next"] .switch input:checked + .track{background:var(--blue);}
html[data-ui="next"] .switch input:checked + .track::before{transform:translateX(16px);}
html[data-ui="next"] .switch input:focus-visible + .track{box-shadow:0 0 0 3px rgba(0,100,205,0.2);}
/* A switch with its words beside it (Switch Text): one label, so the words toggle it too. A long text
   wraps beside the knob instead of pushing the row wider. */
html[data-ui="next"] .switch-field{display:inline-flex;align-items:center;gap:8px;min-width:0;max-width:100%;cursor:pointer;}
html[data-ui="next"] .switch-field .switch-text{font-size:13px;line-height:1.35;color:var(--ink-2);user-select:none;overflow-wrap:anywhere;}
html[data-ui="next"] .switch-field.is-disabled{cursor:default;}
html[data-ui="next"] .switch-field.is-disabled .switch-text{color:var(--ink-4);}

/* ---------- Empty state ----------
   A named cause and one action. "No data" alone never says whether something
   is broken or simply not configured yet. */
html[data-ui="next"] .empty{display:flex;flex-direction:column;align-items:center;text-align:center;padding:44px 20px;}
html[data-ui="next"] .empty > svg{width:26px;height:26px;color:var(--ink-4);margin-bottom:12px;}
html[data-ui="next"] .empty b{font-family:var(--font-head);font-size:14px;color:var(--ink);display:block;margin-bottom:4px;}
html[data-ui="next"] .empty span{font-size:13px;color:var(--ink-4);max-width:44ch;display:block;margin-bottom:16px;}

/* ---------- Map / canvas split ---------- */
html[data-ui="next"] .canvas-split{display:grid;grid-template-columns:minmax(0,1fr) 300px;gap:var(--gap);align-items:start;}
html[data-ui="next"] .canvas{
  position:relative;height:520px;border-radius:var(--r-panel);
  border:1px solid var(--line);background:var(--surface);overflow:hidden;
}
html[data-ui="next"] .canvas .canvas-tools{
  position:absolute;top:10px;left:10px;z-index:2;
  display:flex;gap:4px;padding:4px;border-radius:var(--r-ctl);
  background:var(--surface);border:1px solid var(--line);box-shadow:var(--shadow);
}
html[data-ui="next"] .canvas .canvas-legend{
  position:absolute;bottom:10px;left:10px;z-index:2;
  display:flex;gap:12px;padding:7px 10px;border-radius:var(--r-ctl);
  background:rgba(255,255,255,.94);border:1px solid var(--line);box-shadow:var(--shadow);
  font-size:11.5px;color:var(--ink-3);
}
html[data-ui="next"] .canvas .canvas-legend i{width:8px;height:8px;border-radius:2px;display:inline-block;margin-right:5px;}
@media (max-width:1080px){ html[data-ui="next"] .canvas-split{grid-template-columns:minmax(0,1fr);} }

/* ---------- Page-level state (access denied, no warehouse) ---------- */
html[data-ui="next"] .state-page{max-width:520px;margin:9vh auto 0;text-align:center;}
html[data-ui="next"] .state-page .icon{
  width:44px;height:44px;border-radius:50%;margin:0 auto 16px;
  display:flex;align-items:center;justify-content:center;
  background:rgba(240,60,90,0.07);color:var(--red-dark);
}
html[data-ui="next"] .state-page .icon.warn{background:rgba(169,103,11,0.08);color:#a9670b;}
html[data-ui="next"] .state-page .icon svg{width:21px;height:21px;}
html[data-ui="next"] .state-page h2{margin-bottom:8px;}
html[data-ui="next"] .state-page p{color:var(--ink-3);font-size:13.5px;margin-bottom:20px;}
html[data-ui="next"] .state-actions{display:flex;gap:8px;justify-content:center;}

/* ---------- Notifications ----------
   Unread carries a dot AND heavier text; read loses both. Never colour alone. */
html[data-ui="next"] .pop-bar{
  display:flex;align-items:center;justify-content:space-between;gap:10px;
  padding:7px 9px 6px;
}
html[data-ui="next"] .pop-bar .t{font-size:10.5px;letter-spacing:0.07em;text-transform:uppercase;color:var(--ink-4);}
html[data-ui="next"] .pop-bar button{
  font-family:var(--font-body);font-weight:500;font-size:11.5px;
  color:var(--blue);background:none;border:0;cursor:pointer;padding:3px 6px;border-radius:5px;
}
html[data-ui="next"] .pop-bar button:hover:not(:disabled){background:var(--tint);}
html[data-ui="next"] .pop-bar button:disabled{color:var(--ink-4);cursor:default;}
html[data-ui="next"] .notif .n-dot{width:7px;height:7px;border-radius:50%;flex-shrink:0;background:var(--blue);}
html[data-ui="next"] .notif.read .n-dot{background:transparent;box-shadow:inset 0 0 0 1px var(--line);}
html[data-ui="next"] .notif.read .stack b{font-weight:400;color:var(--ink-3);}

/* ---------- Component sheet only ---------- */
html[data-ui="next"] .sheet-item{padding:18px var(--pad);border-top:1px solid var(--line-soft);}
html[data-ui="next"] .sheet-item:first-child{border-top:0;}
html[data-ui="next"] .sheet-item > h4{margin-bottom:3px;}
html[data-ui="next"] .sheet-item > .why{font-size:12.5px;color:var(--ink-4);margin-bottom:14px;max-width:74ch;}
html[data-ui="next"] .sheet-demo{display:flex;align-items:center;gap:10px;flex-wrap:wrap;}
/* The row is built for chips and buttons sitting side by side. A full-width
   component in it takes its own line rather than being squeezed between them. */
html[data-ui="next"] .sheet-demo > .lic,
html[data-ui="next"] .sheet-demo > .tbl-wrap,
html[data-ui="next"] .sheet-demo > .tbl-foot{flex:1 1 100%;min-width:0;}
html[data-ui="next"] .sheet-used{font-size:11.5px;color:var(--ink-4);margin-top:12px;}
html[data-ui="next"] .sheet-used code{
  font-family:var(--font-mono);font-size:11px;
  background:rgba(10,25,50,0.05);padding:1px 5px;border-radius:4px;color:var(--ink-3);
}

/* ---------- Dialogs ----------
   One component behind every modal in the app: confirmations, create/edit
   forms and read-only detail sheets. Three widths (s/m/l), one header, one
   footer, one motion.

   The app's ConfirmationModal centres a 2x-size icon in a tinted circle above
   the message. That reads as an alert poster rather than a decision: the
   destructive path deserves emphasis, but a 48px circle on every "are you
   sure" trains people to click through it. Here the icon is 16px inline with
   the title, and the weight goes on the button that actually does the thing. */
html[data-ui="next"] .dlg-scrim{
  position:fixed;inset:0;z-index:100;
  display:flex;align-items:flex-start;justify-content:center;
  padding:8vh 20px 20px;
  background:rgba(10,18,30,0.42);
  opacity:0;visibility:hidden;transition:opacity .15s ease, visibility .15s;
  overflow-y:auto;
}
html[data-ui="next"] .dlg-scrim.open{opacity:1;visibility:visible;}

html[data-ui="next"] .dlg{
  background:var(--surface);
  border:1px solid var(--line);
  border-radius:var(--r-panel);
  box-shadow:0 2px 4px rgba(10,25,50,.06), 0 24px 60px -18px rgba(10,25,50,.42);
  width:100%;max-width:460px;
  transform:translateY(-8px) scale(.99);
  transition:transform .17s var(--ease);
  display:flex;flex-direction:column;max-height:84vh;
}
html[data-ui="next"] .dlg-scrim.open .dlg{transform:none;}
html[data-ui="next"] .dlg.s{max-width:400px;}
html[data-ui="next"] .dlg.m{max-width:560px;}
html[data-ui="next"] .dlg.l{max-width:820px;}

html[data-ui="next"] .dlg-head{
  display:flex;align-items:center;gap:9px;
  padding:13px var(--pad);border-bottom:1px solid var(--line-soft);flex-shrink:0;
}
html[data-ui="next"] .dlg-head h3{flex:1;min-width:0;}
html[data-ui="next"] .dlg-head > svg{width:16px;height:16px;flex-shrink:0;color:var(--ink-4);}
html[data-ui="next"] .dlg-head.danger > svg{color:var(--red-dark);}
html[data-ui="next"] .dlg-head.danger h3{color:var(--red-dark);}
html[data-ui="next"] .dlg-x{
  width:26px;height:26px;border-radius:var(--r-item);flex-shrink:0;
  display:inline-flex;align-items:center;justify-content:center;
  background:none;border:0;color:var(--ink-4);cursor:pointer;
  transition:background-color .12s ease, color .12s ease;
}
html[data-ui="next"] .dlg-x:hover{background:var(--hover);color:var(--ink);}
html[data-ui="next"] .dlg-x svg{width:14px;height:14px;}

html[data-ui="next"] .dlg-body{padding:var(--pad);overflow-y:auto;}
html[data-ui="next"] .dlg-body > p{font-size:13.5px;color:var(--ink-2);}
html[data-ui="next"] .dlg-body .sub{font-size:12.5px;color:var(--ink-4);margin-top:6px;}
html[data-ui="next"] .dlg-body.flush{padding:0;}

html[data-ui="next"] .dlg-foot{
  display:flex;align-items:center;justify-content:flex-end;gap:8px;
  padding:12px var(--pad);border-top:1px solid var(--line-soft);flex-shrink:0;
}
html[data-ui="next"] .dlg-foot .left{margin-right:auto;font-size:12px;color:var(--ink-4);}

/* Read-only detail sheet: a definition list, not a form. Inputs that cannot
   be edited still look editable, and people try. */
html[data-ui="next"] .dl{display:grid;grid-template-columns:auto 1fr;gap:7px 18px;margin:0;font-size:13px;}
html[data-ui="next"] .dl dt{color:var(--ink-4);white-space:nowrap;}
html[data-ui="next"] .dl dd{margin:0;min-width:0;overflow-wrap:anywhere;color:var(--ink);font-variant-numeric:tabular-nums;}
html[data-ui="next"] .dl-group{margin-bottom:16px;}
html[data-ui="next"] .dl-group:last-child{margin-bottom:0;}
html[data-ui="next"] .dl-group > h4{margin-bottom:8px;font-size:12px;color:var(--ink-3);}

/* The destructive-confirm gate. The app asks for an admin password before a
   company can be deleted; that stays, because it is the only thing standing
   between a mis-click and a tenant disappearing. */
html[data-ui="next"] .dlg-danger-note{
  border-radius:var(--r-ctl);padding:11px 13px;margin-bottom:14px;
  background:rgba(240,60,90,0.05);border:1px solid rgba(240,60,90,0.2);
  font-size:12.5px;color:var(--ink-2);
}
html[data-ui="next"] .dlg-danger-note b{display:block;color:var(--red-dark);font-weight:500;margin-bottom:4px;}
html[data-ui="next"] .dlg-danger-note ul{margin:0;padding-left:18px;}
html[data-ui="next"] .dlg-danger-note li{margin-bottom:2px;}

/* Trigger list on the dialogs draft page */
html[data-ui="next"] .dlg-trigger{
  display:flex;align-items:center;justify-content:space-between;gap:14px;
  padding:11px var(--pad);border-top:1px solid var(--line-soft);
  cursor:pointer;transition:background-color .12s ease;width:100%;
  background:none;border-left:0;border-right:0;border-bottom:0;text-align:left;
}
html[data-ui="next"] .dlg-trigger:first-child{border-top:0;}
html[data-ui="next"] .dlg-trigger:hover{background:var(--hover);}
html[data-ui="next"] .dlg-trigger .t{min-width:0;}
html[data-ui="next"] .dlg-trigger .t b{display:block;font-weight:500;font-size:13.5px;color:var(--ink);}
html[data-ui="next"] .dlg-trigger .t span{display:block;font-size:12px;color:var(--ink-4);margin-top:1px;}

/* ---------- Selection action bar ----------
   Shared/SelectionActionBar.razor. Appears when rows are selected, and is
   sidebar-aware: `left` follows --rail-w, so it lines up with the content
   whether the rail is open or collapsed.

   The app's version is a gradient with round icon-only buttons and a
   data-tooltip. Two changes here. The gradient goes, because nothing else in
   the system has one. And the buttons carry their label: five unlabelled
   circles at the bottom of the screen is a memory test, and the actions behind
   them (force logout, delete) are not ones to guess at. */
html[data-ui="next"] .selbar{
  position:fixed;left:var(--rail-w);right:0;bottom:0;z-index:60;
  background:var(--surface);
  border-top:1px solid var(--line);
  box-shadow:0 -4px 18px -8px rgba(10,25,50,.18);
  transform:translateY(100%);opacity:0;visibility:hidden;
  transition:transform .22s var(--ease), opacity .18s ease, visibility .22s, left .22s var(--ease);
}
html[data-ui="next"] .selbar.show{transform:none;opacity:1;visibility:visible;}
html[data-ui="next"] .selbar-in{
  display:flex;align-items:center;gap:10px;
  padding:10px 22px;max-width:1400px;margin:0 auto;
}
html[data-ui="next"] .selbar .count{font-size:13px;font-weight:500;color:var(--ink);white-space:nowrap;}
html[data-ui="next"] .selbar .sep{width:1px;height:22px;background:var(--line);flex-shrink:0;}
html[data-ui="next"] .selbar .acts{display:flex;align-items:center;gap:6px;flex-wrap:wrap;}
html[data-ui="next"] .selbar .clear{
  width:30px;height:30px;border-radius:var(--r-ctl);flex-shrink:0;
  display:inline-flex;align-items:center;justify-content:center;
  background:none;border:0;color:var(--ink-4);cursor:pointer;
  transition:background-color .12s ease,color .12s ease;
}
html[data-ui="next"] .selbar .clear:hover{background:var(--hover);color:var(--ink);}
html[data-ui="next"] .selbar .clear svg{width:14px;height:14px;}

/* A disabled action states why it is disabled rather than just greying out:
   "Send Message" only makes sense for one operator, so the tooltip says so. */
html[data-ui="next"] .act{
  display:inline-flex;align-items:center;gap:6px;
  height:30px;padding:0 11px;border-radius:var(--r-ctl);
  font-family:var(--font-body);font-weight:500;font-size:12.5px;
  background:var(--surface);border:1px solid var(--line);color:var(--ink-2);
  cursor:pointer;white-space:nowrap;position:relative;
  transition:background-color .12s ease,border-color .12s ease,color .12s ease;
}
html[data-ui="next"] .act svg{width:14px;height:14px;color:var(--ink-4);}
html[data-ui="next"] .act:hover:not(:disabled){background:#fcfdfe;border-color:rgba(10,25,50,.18);color:var(--ink);}
html[data-ui="next"] .act:hover:not(:disabled) svg{color:var(--ink-3);}
html[data-ui="next"] .act:disabled{opacity:.45;cursor:default;}
html[data-ui="next"] .act.danger{color:var(--red-dark);}
html[data-ui="next"] .act.danger svg{color:var(--red-dark);}
html[data-ui="next"] .act.danger:hover:not(:disabled){background:rgba(240,60,90,.05);border-color:rgba(240,60,90,.25);color:var(--red-dark);}

html[data-ui="next"] .act[data-tip]:hover::after{
  content:attr(data-tip);
  position:absolute;bottom:calc(100% + 7px);left:50%;transform:translateX(-50%);
  background:var(--ink);color:#fff;font-size:11.5px;font-weight:400;
  padding:5px 8px;border-radius:6px;white-space:nowrap;pointer-events:none;
  box-shadow:0 4px 12px -4px rgba(10,25,50,.4);
}

/* Selected rows are tinted, and the checkbox is the source of truth. */
html[data-ui="next"] table.tbl tbody tr.sel{background:var(--tint);}
html[data-ui="next"] table.tbl tbody tr.sel:hover{background:rgba(0,100,205,.1);}
html[data-ui="next"] table.tbl td input[type=checkbox]:not(.switch input),
html[data-ui="next"] table.tbl th input[type=checkbox]:not(.switch input){
  width:14px;height:14px;accent-color:var(--blue);cursor:pointer;vertical-align:middle;
}
/* The bar overlays the page bottom, so the last rows need somewhere to go. */
html[data-ui="next"] .page.has-selbar{padding-bottom:74px;}

/* =========================================================
   LOADING
   Three states, chosen by how long the wait is and whether the user is
   already reading something. Getting this wrong is worse than doing nothing:
   a skeleton that flashes for 80ms reads as a glitch, and one that replaces
   data the user was mid-sentence on is actively hostile.
   ========================================================= */

/* ---------- 1. Route progress bar ----------
   For navigation. Only appears if the new page takes longer than ~200ms;
   below that nothing is shown at all. It sits on the topbar's bottom edge so
   it never moves layout. */
html[data-ui="next"] .route-bar{
  position:fixed;top:calc(var(--topbar-h) - 2px);left:var(--rail-w);right:0;
  height:2px;z-index:70;pointer-events:none;
  background:transparent;overflow:hidden;
  opacity:0;transition:opacity .15s ease, left .22s var(--ease);
}
html[data-ui="next"] .route-bar.on{opacity:1;}
html[data-ui="next"] .route-bar i{
  display:block;height:100%;width:0;
  background:var(--blue);
  transition:width .25s var(--ease);
}

/* ---------- 2. Skeleton ----------
   Only for a region with no previous content. It has to match the real
   geometry - a skeleton of the wrong height just moves the layout shift from
   "on load" to "on settle". */
html[data-ui="next"] .skel{
  background:linear-gradient(90deg, rgba(10,25,50,.05) 25%, rgba(10,25,50,.085) 37%, rgba(10,25,50,.05) 63%);
  background-size:400% 100%;
  animation:skel-shimmer 1.3s ease-in-out infinite;
  border-radius:5px;
}
@keyframes skel-shimmer{0%{background-position:100% 50%}100%{background-position:0 50%}}
/* A shimmer is motion in the corner of the eye. Under reduced-motion it
   becomes a flat block, which still communicates "pending". */
@media (prefers-reduced-motion:reduce){
  html[data-ui="next"] .skel{animation:none;background:rgba(10,25,50,.06);}
}
html[data-ui="next"] .skel-line{height:11px;}
html[data-ui="next"] .skel-line.sm{height:9px;}
html[data-ui="next"] .skel-num{height:24px;width:64px;border-radius:6px;}
html[data-ui="next"] .skel-pill{height:18px;width:74px;border-radius:9px;}
html[data-ui="next"] .skel-chart{height:236px;border-radius:6px;}
html[data-ui="next"] .skel-spark{height:30px;margin-top:12px;}

/* The static chrome - headings, toolbar, table headers - is not skeletoned.
   It is known before the request returns, so painting it immediately removes
   most of the perceived wait for free.

   The placeholder bar has to occupy the same LINE BOX as the text it stands in
   for, not just look about right. A 13px cell has a 19.5px line-height, so an
   11px bar needs 4.25px of margin either side. Skipping this made skeleton rows
   30px against real rows of 39px - a 47px jump on a five-row table, which is
   the layout shift a skeleton exists to prevent. */
html[data-ui="next"] table.tbl tr.skel-row td .skel-line{height:11px;margin:4.25px 0;}

/* Residual: real rows measure 39.375px against the skeleton's 38.5px, because
   the status chip in the last column is an inline-flex box that sits slightly
   taller than a plain text line. 0.875px per row - about 4px across a five-row
   table, roughly 1%. Closing it exactly means hard-coding a margin derived from
   one table's content, which drifts the moment a column changes. Left as is
   deliberately; 47px was the bug, 4px is not. */

/* ---------- 3. Refetching ----------
   For data that is already on screen: a filter change, a tab switch, a
   warehouse switch. The old numbers stay, dimmed and inert, with an
   indeterminate bar on the panel edge. Replacing them with a skeleton throws
   away a correct answer to show a placeholder, and the flicker makes a fast
   request feel slower than it is. */
html[data-ui="next"] .is-refetching{position:relative;}

/* Descendant, not child. The class lands on a panel for a tab or filter change
   and on #page for a warehouse or company change - one selector has to cover
   both, and under #page the dimmable parts are two levels down inside
   .page-section. An earlier version used `>` and silently did nothing at all on
   scope changes, which is the failure mode you never notice, because a page
   that does not dim looks exactly like a page that has already finished. */
html[data-ui="next"] .is-refetching .panel-body,
html[data-ui="next"] .is-refetching .tbl-wrap,
html[data-ui="next"] .is-refetching .tbl-foot,
html[data-ui="next"] .is-refetching .kpi-grid,
html[data-ui="next"] .is-refetching .canvas-split,
html[data-ui="next"] .is-refetching .rows,
html[data-ui="next"] .is-refetching .feed{
  opacity:.45;pointer-events:none;transition:opacity .15s ease;
}
/* Panel heads, tab strips and toolbars keep full contrast and stay live. They
   describe what is being fetched rather than being the thing fetched, and the
   quickest correction to a mis-click is another click - taking the control away
   mid-flight is what makes an interface feel like it is fighting you. */

/* The sweep belongs to a panel that is ITSELF the thing being refetched - a tab
   or a filter change, where the wait is local and the panel edge is where the
   eye already is.

   It deliberately does not cascade to panels inside a page-level refetch. An
   earlier version added `.is-refetching > .page-section > .panel::after`, which
   meant a warehouse change put a sweep on every panel wrapped in a section and
   none on the panels that happen to sit directly under #page - the same event
   drawn two different ways on two pages, decided by markup nesting nobody chose
   for that reason. A page-level wait has one cause, so it gets one indicator:
   the route bar at the top. */
html[data-ui="next"] .panel.is-refetching::after{
  content:'';position:absolute;left:0;right:0;top:0;height:2px;
  border-radius:var(--r-panel) var(--r-panel) 0 0;
  background:linear-gradient(90deg, transparent, var(--blue), transparent);
  background-size:40% 100%;background-repeat:no-repeat;
  animation:refetch-sweep 1.1s linear infinite;
}
@keyframes refetch-sweep{0%{background-position:-40% 0}100%{background-position:140% 0}}
@media (prefers-reduced-motion:reduce){
  html[data-ui="next"] .panel.is-refetching::after{animation:none;background:var(--blue);opacity:.5;}
}

/* Inline spinner, for a button that is doing something. */
html[data-ui="next"] .spin{
  display:inline-block;width:13px;height:13px;flex-shrink:0;
  border:2px solid rgba(255,255,255,.35);border-top-color:#fff;border-radius:50%;
  animation:spin .7s linear infinite;
}
html[data-ui="next"] .btn-ghost .spin{border-color:rgba(10,25,50,.2);border-top-color:var(--ink-3);}
@keyframes spin{to{transform:rotate(360deg)}}

html[data-ui="next"] .load-demo{display:grid;grid-template-columns:repeat(auto-fill,minmax(430px,1fr));gap:var(--gap);align-items:start;}

/* =========================================================
   Configuration - licence rows, save bar, workflow designer
   Used by configuration.js. See the note at the top of that file for why these
   three exist.
   ========================================================= */

/* ---------- Licence key ----------
   Key and action on one line, result underneath. The result gets its own row
   rather than sitting beside the button because three of the seven outcomes are
   a full sentence, and a status that reflows the control it belongs to is worse
   than one that pushes the page down. */
html[data-ui="next"] .lic{ margin-bottom:16px; }
html[data-ui="next"] .lic:last-child{ margin-bottom:0; }
html[data-ui="next"] .lic-row{ display:flex; gap:8px; align-items:center; }
html[data-ui="next"] .lic-row .inp{ font-family:var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); letter-spacing:.02em; }
html[data-ui="next"] .lic-row .btn{ flex-shrink:0; }
html[data-ui="next"] .lic-out:empty{ display:none; }
html[data-ui="next"] .lic-out{ margin-top:9px; }

/* Detail only appears once a key has resolved to a real licence. Tabular so the
   four facts line up rather than reading as a sentence.

   Four stated columns, not `auto-fit`. auto-fit with a 140px minimum wanted
   4x140 + 3x18 of gap = 614px and had 604 - ten pixels short - so it quietly
   dropped to three and left Devices orphaned on a row of its own, in a block
   with 250px of unused width beside it.

   The lesson is not "lower the minimum". It is that auto-fit's fallback for four
   items is 3+1, and an orphan is the one arrangement that always looks like a
   mistake. Stating the count means the columns get narrower under pressure
   instead of one item falling off the end, and minmax(0,1fr) lets them do that
   without overflowing. */
/* `flex` here is not decoration, it is the fix for a trap.
   `container-type:inline-size` applies inline-size containment, which means the
   box can no longer be sized BY ITS OWN CONTENT. Drop it into a flex row where
   it had been content-sized and it collapses to zero - and then every container
   query on it reports "narrow" on a 600px-wide panel, which is how the 2x2
   fallback fired on the component sheet while there was plenty of room.
   `flex:1 1 100%` gives it a definite basis, and is simply ignored where it is
   not a flex item. */
html[data-ui="next"] .lic{ container-type:inline-size; flex:1 1 100%; min-width:0; }
html[data-ui="next"] .lic-detail{
  display:grid; grid-template-columns:repeat(4, minmax(0, 1fr));
  gap:6px 18px; margin-top:9px;
  padding-top:9px; border-top:1px solid var(--line-soft);
}
/* A container query, because what constrains this is the panel it sits in, not
   the window - the same row is 604px wide on a full-width panel and half that
   in a two-up. And when four genuinely will not fit it goes to 2x2, which is
   balanced, rather than back to the 3+1 that started this. */
@container (max-width: 460px){
  html[data-ui="next"] .lic-detail{ grid-template-columns:repeat(2, minmax(0, 1fr)); }
}
html[data-ui="next"] .lic-detail span{ font-size:12px; color:var(--ink-2); }
html[data-ui="next"] .lic-detail b{
  display:block; font-weight:500; font-size:11px; color:var(--ink-4);
  text-transform:uppercase; letter-spacing:.04em; margin-bottom:1px;
}

/* ---------- Save bar ----------
   Sticky to the bottom of the viewport, not the bottom of the page: the
   configuration tabs are long, and a Save you have to scroll to find is a Save
   people stop trusting. Hidden until something is dirty. */
html[data-ui="next"] .savebar{
  position:sticky; bottom:0; z-index:5;
  display:flex; align-items:center; justify-content:space-between; gap:16px;
  margin-top:var(--gap);
  padding:11px var(--pad);
  background:var(--surface);
  border:1px solid var(--line);
  border-radius:var(--r-panel);
  box-shadow:0 -1px 3px rgba(10,25,50,.06), var(--shadow);
}
html[data-ui="next"] .savebar[hidden]{ display:none; }
html[data-ui="next"] .savebar-msg{ min-width:0; font-size:12.5px; }
html[data-ui="next"] .savebar-act{ display:flex; gap:8px; flex-shrink:0; }

/* A spinner on a light button. The base .spin is white-on-blue, which is
   invisible on .btn-ghost. */
html[data-ui="next"] .spin.dark{
  border-color:rgba(10,25,50,.2); border-top-color:var(--ink-3);
  width:12px; height:12px; margin-right:6px;
}

/* ---------- Workflow designer ----------
   Toolbox left, canvas right - the arrangement of the real modal, and the one
   that matches the gesture: you take from the palette and put it in the work. */
html[data-ui="next"] .wf{
  display:grid; grid-template-columns:266px minmax(0,1fr);
  gap:14px; align-items:stretch;
  height:min(52vh, 420px);
}
html[data-ui="next"] .wf-pane-head{
  display:flex; align-items:center; justify-content:space-between; gap:8px;
  padding:8px 11px; border-bottom:1px solid var(--line);
  font-size:11px; font-weight:500; letter-spacing:.05em; text-transform:uppercase;
  color:var(--ink-4); background:var(--field);
  border-radius:var(--r-ctl) var(--r-ctl) 0 0;
}
html[data-ui="next"] .wf-tools,
html[data-ui="next"] .wf-canvas-wrap{
  display:flex; flex-direction:column; min-height:0;
  border:1px solid var(--line); border-radius:var(--r-ctl); background:var(--surface);
}
html[data-ui="next"] .wf-tools-body,
html[data-ui="next"] .wf-canvas{ flex:1; overflow-y:auto; padding:8px; min-height:0; }

html[data-ui="next"] .wf-cat{ margin-bottom:4px; }
html[data-ui="next"] .wf-cat-head{
  display:flex; align-items:center; gap:7px; width:100%;
  padding:6px 7px; border:0; background:none; cursor:pointer;
  font-family:var(--font-body); font-size:12.5px; font-weight:500; color:var(--ink-2);
  border-radius:var(--r-item);
}
html[data-ui="next"] .wf-cat-head:hover{ background:var(--hover); }
html[data-ui="next"] .wf-chev{ display:inline-flex; color:var(--ink-4); transition:transform .14s var(--ease); }
html[data-ui="next"] .wf-chev svg{ width:13px; height:13px; }
html[data-ui="next"] .wf-cat:not(.closed) .wf-chev{ transform:rotate(90deg); }
html[data-ui="next"] .wf-cat.closed .wf-cat-body{ display:none; }
html[data-ui="next"] .wf-count{ margin-left:auto; font-size:11px; color:var(--ink-4); font-variant-numeric:tabular-nums; }
html[data-ui="next"] .wf-dot{ width:7px; height:7px; border-radius:2px; flex-shrink:0; display:inline-block; }

html[data-ui="next"] .wf-block{
  display:flex; align-items:center; gap:8px;
  padding:6px 8px; margin:2px 0 2px 14px;
  border:1px solid transparent; border-radius:var(--r-item);
  cursor:grab; transition:background-color .12s ease, border-color .12s ease;
}
html[data-ui="next"] .wf-block:hover{ background:var(--hover); border-color:var(--line); }
html[data-ui="next"] .wf-block:focus-visible{ outline:2px solid var(--blue); outline-offset:1px; }
html[data-ui="next"] .wf-block.dragging{ opacity:.4; }
html[data-ui="next"] .wf-block-text{ min-width:0; line-height:1.3; flex:1; }
html[data-ui="next"] .wf-block-text b{ display:block; font-weight:500; font-size:12.5px; color:var(--ink); }
html[data-ui="next"] .wf-block-text span{ display:block; font-size:11px; color:var(--ink-4); }
html[data-ui="next"] .wf-add{ opacity:0; color:var(--ink-4); flex-shrink:0; }
html[data-ui="next"] .wf-add svg{ width:13px; height:13px; display:block; }
html[data-ui="next"] .wf-block:hover .wf-add,
html[data-ui="next"] .wf-block:focus-visible .wf-add{ opacity:1; }

html[data-ui="next"] .wf-step{
  display:flex; align-items:center; gap:9px;
  padding:8px 10px;
  border:1px solid var(--line); border-left:3px solid var(--line);
  border-radius:var(--r-item); background:var(--surface);
}
html[data-ui="next"] .wf-n{
  width:19px; height:19px; flex-shrink:0;
  display:inline-flex; align-items:center; justify-content:center;
  border-radius:50%; background:var(--field); border:1px solid var(--line);
  font-size:10.5px; color:var(--ink-3); font-variant-numeric:tabular-nums;
}
html[data-ui="next"] .wf-step-act{ display:flex; gap:2px; flex-shrink:0; }
html[data-ui="next"] .wf-step-act .act{ width:24px; height:24px; font-size:12px; line-height:1; }

/* The gap between steps, and where a dragged block will land. Collapsed to a
   couple of pixels at rest so the list is not full of empty lanes; it opens only
   for the one gap being targeted. */
html[data-ui="next"] .wf-drop{ height:6px; border-radius:3px; transition:height .12s ease, background-color .12s ease; }
html[data-ui="next"] .wf-drop.on{ height:22px; background:var(--tint); border:1px dashed rgba(0,100,205,.45); }

html[data-ui="next"] .wf-empty{
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  text-align:center; height:100%; padding:24px; gap:4px;
  border:1px dashed var(--line); border-radius:var(--r-ctl);
}
html[data-ui="next"] .wf-empty b{ font-family:var(--font-head); font-size:13.5px; color:var(--ink); }
html[data-ui="next"] .wf-empty span{ font-size:12.5px; color:var(--ink-4); max-width:34ch; }

html[data-ui="next"] .wf-name{ margin-bottom:14px; max-width:340px; }

@media (max-width:760px){
  html[data-ui="next"] .wf{ grid-template-columns:minmax(0,1fr); height:auto; }
  html[data-ui="next"] .wf-tools-body{ max-height:180px; }
  html[data-ui="next"] .wf-canvas{ min-height:200px; }
}

/* =========================================================
   Support - FAQ accordion and the manual viewer
   ========================================================= */

/* ---------- FAQ ----------
   One hairline between items, the question is the whole hit area, and the
   chevron states which way it goes. Twelve of these, so the closed state has to
   be compact: a closed row is one line of text and nothing else. */
html[data-ui="next"] .faq-item{ border-top:1px solid var(--line-soft); }
html[data-ui="next"] .faq-item:first-child{ border-top:0; }
html[data-ui="next"] .faq-q{ margin:0; }
html[data-ui="next"] .faq-q button{
  display:flex; align-items:center; justify-content:space-between; gap:12px;
  width:100%; text-align:left; cursor:pointer;
  padding:12px var(--pad); border:0; background:none;
  font-family:var(--font-body); font-weight:500; font-size:13.5px; color:var(--ink);
  transition:background-color .12s ease;
}
html[data-ui="next"] .faq-q button:hover{ background:var(--hover); }
html[data-ui="next"] .faq-q button:focus-visible{ outline:2px solid var(--blue); outline-offset:-2px; }
html[data-ui="next"] .faq-chev{ width:14px; height:14px; flex-shrink:0; color:var(--ink-4); transition:transform .16s var(--ease); }
html[data-ui="next"] .faq-q button[aria-expanded="true"]{ color:var(--blue); }
html[data-ui="next"] .faq-q button[aria-expanded="true"] .faq-chev{ transform:rotate(180deg); color:var(--blue); }

/* The answers carry real markup - several are lists - so they are styled as
   prose rather than squeezed into a single line. */
html[data-ui="next"] .faq-a{
  padding:0 var(--pad) 14px;
  font-size:13px; line-height:1.55; color:var(--ink-2);
  max-width:78ch;
}
html[data-ui="next"] .faq-a[hidden]{ display:none; }
html[data-ui="next"] .faq-a ul{ margin:8px 0 0; padding-left:18px; }
html[data-ui="next"] .faq-a li{ margin-bottom:4px; }
html[data-ui="next"] .faq-a strong{ font-weight:500; color:var(--ink); }

/* ---------- Manual viewer ----------
   Contents beside the document, both scrolling independently. The manual runs to
   fifteen sections and a thousand lines; without a list of what is in it you are
   scrolling blind. */
html[data-ui="next"] .man{ display:grid; grid-template-columns:196px minmax(0,1fr); gap:16px; height:min(56vh, 440px); }
html[data-ui="next"] .man-toc{ overflow-y:auto; border-right:1px solid var(--line-soft); padding-right:12px; min-height:0; }
html[data-ui="next"] .man-toc-head{
  font-size:11px; font-weight:500; letter-spacing:.05em; text-transform:uppercase;
  color:var(--ink-4); padding:0 8px 7px;
}
html[data-ui="next"] .man-toc a{
  display:block; padding:5px 8px; border-radius:var(--r-item);
  font-size:12.5px; color:var(--ink-3);
  transition:background-color .12s ease, color .12s ease;
}
html[data-ui="next"] .man-toc a:hover{ background:var(--hover); color:var(--ink); }
html[data-ui="next"] .man-toc a.on{ background:var(--tint); color:var(--blue); font-weight:500; }
html[data-ui="next"] .man-doc{ overflow-y:auto; min-height:0; font-size:13px; line-height:1.6; color:var(--ink-2); max-width:72ch; }
html[data-ui="next"] .man-doc h4{ margin:0 0 6px; }
html[data-ui="next"] .man-doc h4 ~ h4{ margin-top:18px; }
html[data-ui="next"] .man-doc p{ margin-bottom:10px; }
html[data-ui="next"] .man-doc code{
  font-family:var(--font-mono); font-size:11.5px;
  background:rgba(10,25,50,0.05); padding:1px 5px; border-radius:4px; color:var(--ink-3);
}
/* Stands in for body text the concept deliberately does not duplicate - the
   live document is the source and copying it here would create a second one. */
html[data-ui="next"] .man-skel{
  height:52px; border-radius:var(--r-item);
  background:repeating-linear-gradient(180deg, rgba(10,25,50,.05) 0 9px, transparent 9px 17px);
}
html[data-ui="next"] .man-swap{ display:flex; gap:6px; margin-right:auto; }

@media (max-width:760px){
  html[data-ui="next"] .man{ grid-template-columns:minmax(0,1fr); height:auto; }
  html[data-ui="next"] .man-toc{ border-right:0; border-bottom:1px solid var(--line-soft); max-height:120px; padding-right:0; }
}

/* =========================================================
   Configuration - module cards and the device setup QR
   ========================================================= */

/* ---------- Module cards ----------
   A card rather than a row, because each module carries a description AND a
   conditional notice, and a settings row has nowhere to put the second one.
   The left border states which card needs attention - three cards side by side
   are scanned, not read. */
html[data-ui="next"] .panel.mod{ display:flex; flex-direction:column; border-left:3px solid transparent; }
html[data-ui="next"] .panel.mod > .panel-body{ display:flex; flex-direction:column; flex:1; padding-bottom:12px; }
html[data-ui="next"] .panel.mod h4{ margin-bottom:4px; }
html[data-ui="next"] .panel.mod .set-row{ border-top:0; padding-left:0; padding-right:0; flex:1; align-items:flex-start; }
html[data-ui="next"] .panel.mod.warn{ border-left-color:#a9670b; background:#fffdf5; }

html[data-ui="next"] .mod-note{
  display:flex; align-items:flex-start; gap:7px;
  margin-top:auto; padding:7px 9px; border-radius:var(--r-item);
  font-size:11.5px; line-height:1.4;
}
html[data-ui="next"] .mod-note svg{ width:12px; height:12px; flex-shrink:0; margin-top:1px; }
html[data-ui="next"] .mod-note.info{ background:var(--tint); color:var(--blue); }
html[data-ui="next"] .mod-note.warn{ background:rgba(169,103,11,.09); color:#a9670b; }

/* ---------- Device setup QR ----------
   Hidden until asked for: the payload identifies the host and the warehouse, so
   it is not something to leave on screen. */
html[data-ui="next"] .qr{ display:flex; align-items:center; gap:24px; flex-wrap:wrap; }
html[data-ui="next"] .qr-code{
  width:190px; height:190px; flex-shrink:0;
  border:1px solid var(--line); border-radius:var(--r-ctl); background:#fff;
  /* Stand-in for the generated code. Deliberately not a real QR: a scannable
     code in a mock is a code that points somewhere it should not. */
  background-image:
    repeating-linear-gradient(90deg, var(--ink) 0 6px, transparent 6px 12px),
    repeating-linear-gradient(180deg, var(--ink) 0 6px, transparent 6px 12px);
  background-size:12px 12px;
  background-blend-mode:difference;
  background-position:14px 14px;
  background-clip:content-box;
  padding:14px;
  opacity:.82;
}
html[data-ui="next"] .qr-facts{ border-collapse:collapse; font-size:13px; }
html[data-ui="next"] .qr-facts td{ padding:3px 0; vertical-align:top; }
html[data-ui="next"] .qr-facts td:first-child{ color:var(--ink-4); padding-right:16px; white-space:nowrap; }
html[data-ui="next"] .qr-facts b{ font-weight:500; color:var(--ink); }

/* =========================================================
   Company tree - /admin/companies
   A three-level hierarchy needs three visibly different levels. Indentation
   alone is not hierarchy: it is the same row moved sideways, and the eye has to
   measure it. So each level changes ground, weight and shape.

     Company    white, on its own hairline, the heaviest name
     Site       tinted ground, lighter name, connected by a rule
     Warehouse  a card in a grid - a leaf that does not look like a branch
   ========================================================= */
html[data-ui="next"] .tree{ position:relative; }
html[data-ui="next"] .tree.empty::after{
  content:"No company matches that."; display:block;
  padding:32px var(--pad); text-align:center; font-size:13px; color:var(--ink-4);
}

html[data-ui="next"] .tree-row{
  display:flex; align-items:center; gap:10px;
  padding:11px var(--pad);
  transition:background-color .12s ease;
}

/* ---------- Level 1 · company ---------- */
html[data-ui="next"] .tree-co{ border-top:1px solid var(--line-soft); }
html[data-ui="next"] .tree-co:first-child{ border-top:0; }
html[data-ui="next"] .co-row:hover{ background:var(--hover); }
html[data-ui="next"] .tree-ico{
  display:inline-flex; align-items:center; justify-content:center;
  width:30px; height:30px; flex-shrink:0;
  border-radius:var(--r-ctl); background:var(--tint); color:var(--blue);
}
html[data-ui="next"] .tree-ico svg{ width:15px; height:15px; }
html[data-ui="next"] .tree-head{ display:flex; flex-direction:column; gap:1px; min-width:0; flex:1; }
html[data-ui="next"] .co-row .tree-name{ font-weight:500; font-size:14px; color:var(--ink); }

/* Tenant ID, and the copy button that is the reason it is on the row at all. */
html[data-ui="next"] .tree-sub{ display:flex; align-items:center; gap:5px; min-width:0; }
html[data-ui="next"] .tree-sub code{
  font-family:var(--font-mono); font-size:11px;
  color:var(--ink-4); overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
html[data-ui="next"] .copy{
  display:inline-flex; align-items:center; justify-content:center;
  width:19px; height:19px; flex-shrink:0; padding:0;
  border:0; border-radius:4px; background:none; cursor:pointer;
  color:var(--ink-4); opacity:0; position:relative;
  transition:opacity .12s ease, color .12s ease, background-color .12s ease;
}
html[data-ui="next"] .copy svg{ width:11px; height:11px; }
html[data-ui="next"] .co-row:hover .copy,
html[data-ui="next"] .copy:focus-visible{ opacity:1; }
html[data-ui="next"] .copy:hover{ background:var(--hover); color:var(--ink-2); }
html[data-ui="next"] .copy.done{ opacity:1; color:#12806a; }

/* ---------- Counts ----------
   Not chips. A chip in this system says "switched on"; these are quantities, and
   they belong to the row rather than standing off it. */
html[data-ui="next"] .tree-metrics{ display:flex; gap:16px; flex-shrink:0; }
html[data-ui="next"] .tree-metric{ font-size:11.5px; color:var(--ink-4); white-space:nowrap; }
html[data-ui="next"] .tree-metric b{
  font-weight:500; color:var(--ink-2); margin-right:4px;
  font-variant-numeric:tabular-nums;
}

/* ---------- Chevrons ---------- */
html[data-ui="next"] .tree-chev{
  display:inline-flex; align-items:center; justify-content:center;
  width:22px; height:22px; flex-shrink:0; padding:0;
  border:0; border-radius:var(--r-item); background:none; cursor:pointer;
  color:var(--ink-4);
  transition:background-color .12s ease, color .12s ease;
}
html[data-ui="next"] .tree-chev svg{ width:13px; height:13px; transition:transform .16s var(--ease); }
html[data-ui="next"] .tree-chev:hover{ background:var(--hover); color:var(--ink-2); }
html[data-ui="next"] .tree-co.open > .tree-row > .tree-chev svg,
html[data-ui="next"] .tree-site.open > .tree-row > .tree-chev svg{ transform:rotate(90deg); }

html[data-ui="next"] .tree-kids{ display:none; }
html[data-ui="next"] .tree-co.open > .tree-kids,
html[data-ui="next"] .tree-site.open > .tree-kids{ display:block; }

/* ---------- Level 2 · site ----------
   Tinted ground and a rule down the left, so the nesting is stated twice - once
   by the indent and once by something the eye does not have to measure. */
html[data-ui="next"] .tree-co > .tree-kids{
  background:var(--field);
  border-top:1px solid var(--line-soft);
  padding:8px 0 8px 46px;
}
html[data-ui="next"] .tree-site{ position:relative; }
html[data-ui="next"] .tree-site + .tree-site{ margin-top:2px; }
html[data-ui="next"] .tree-site::before{
  content:""; position:absolute; left:-24px; top:0; bottom:0;
  border-left:1px solid var(--line);
}
html[data-ui="next"] .site-row{ padding:7px 12px 7px 0; gap:8px; }
html[data-ui="next"] .site-row .tree-name{ font-weight:500; font-size:13px; color:var(--ink-2); flex:1; }
html[data-ui="next"] .site-row:hover{ background:transparent; }

/* ---------- Level 3 · warehouse ----------
   Cards in a grid. A leaf that looks like the branches above it is the thing
   that made the first version unreadable; a different shape ends the ambiguity
   without another indent. */
html[data-ui="next"] .wh-grid{
  display:grid; grid-template-columns:repeat(auto-fill, minmax(260px, 1fr));
  gap:8px; padding:2px 12px 8px 0;
}
html[data-ui="next"] .wh-card{
  display:flex; align-items:center; gap:9px;
  padding:9px 10px; min-width:0;
  background:var(--surface); border:1px solid var(--line); border-radius:var(--r-ctl);
  transition:border-color .12s ease, box-shadow .12s ease;
}
html[data-ui="next"] .wh-card:hover{ border-color:rgba(10,25,50,.16); box-shadow:var(--shadow); }
html[data-ui="next"] .wh-ico{
  display:inline-flex; align-items:center; justify-content:center;
  width:24px; height:24px; flex-shrink:0;
  border-radius:var(--r-item); background:rgba(18,128,106,.09); color:#12806a;
}
html[data-ui="next"] .wh-ico svg{ width:13px; height:13px; }
html[data-ui="next"] .wh-text{ min-width:0; flex:1; line-height:1.35; }
html[data-ui="next"] .wh-text b{ display:block; font-weight:500; font-size:13px; color:var(--ink); }
html[data-ui="next"] .wh-text code{
  display:block; font-family:var(--font-mono);
  font-size:10.5px; color:var(--ink-4);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}

/* ---------- Actions ----------
   Square icon buttons, revealed on hover or focus. Six permanently-visible
   controls per row was what buried the names in the first version; the row is
   for reading, and the actions arrive when you go for them.

   `:focus-within` is not optional here - without it the buttons are reachable
   by Tab and invisible while focused, which is worse than not having them. */
html[data-ui="next"] .tree-act{ display:flex; gap:4px; flex-shrink:0; opacity:0; transition:opacity .12s ease; }
html[data-ui="next"] .tree-row:hover .tree-act,
html[data-ui="next"] .tree-row:focus-within .tree-act{ opacity:1; }
html[data-ui="next"] .wh-act{ display:flex; gap:4px; flex-shrink:0; opacity:0; transition:opacity .12s ease; }
html[data-ui="next"] .wh-card:hover .wh-act,
html[data-ui="next"] .wh-card:focus-within .wh-act{ opacity:1; }

/* Square variant of the shared action button, for icon-only use. */
html[data-ui="next"] .act.sq{ width:28px; height:28px; padding:0; justify-content:center; }
html[data-ui="next"] .act.sq svg{ width:13px; height:13px; }

/* Touch has no hover, so there is nothing to reveal - show them always rather
   than leaving the page unusable. */
@media (hover:none){
  html[data-ui="next"] .tree-act,
html[data-ui="next"] .wh-act,
html[data-ui="next"] .copy{ opacity:1; }
}

/* ---------- Empty states ---------- */
html[data-ui="next"] .tree-empty{
  display:flex; flex-direction:column; align-items:flex-start; gap:6px;
  padding:14px 12px 16px 0; font-size:12.5px; color:var(--ink-4);
}
html[data-ui="next"] .tree-empty b{ font-family:var(--font-head); font-size:13px; color:var(--ink); }
html[data-ui="next"] .tree-empty span{ max-width:48ch; }
html[data-ui="next"] .tree-empty .btn{ margin-top:4px; }
html[data-ui="next"] .link-btn{
  border:0; background:none; padding:0; cursor:pointer;
  font-family:var(--font-body); font-size:12.5px; color:var(--blue); font-weight:500;
}
html[data-ui="next"] .link-btn:hover{ text-decoration:underline; }

@media (max-width:900px){
  html[data-ui="next"] .tree-metrics{ display:none; }
  html[data-ui="next"] .tree-co > .tree-kids{ padding-left:24px; }
  html[data-ui="next"] .tree-site::before{ left:-14px; }
}

/* =========================================================
   Quick views - Identity operator / device, and the data capture
   Identity/OperatorQuickViewModal.razor, DeviceQuickViewModal.razor,
   Insight/InsightDetailModal.razor.

   All three open the same way: who or what this is, then the three facts you
   came for, then the detail. The strip exists because those three - last seen,
   last device, authenticated - answer most visits on their own, and burying
   them in a definition list with fourteen others means reading all fifteen.
   ========================================================= */

/* The name cell in a table that opens something. It is a <button>: it performs
   an action rather than going to a URL, and a bare <b> that turns out to be
   clickable is the kind of thing only the person who wrote it ever finds. */
html[data-ui="next"] .cell-link{
  border:0;background:none;padding:0;
  font:inherit;font-weight:700;color:var(--blue);
  cursor:pointer;text-align:left;
  border-bottom:1px solid transparent;
}
html[data-ui="next"] .cell-link:hover{ border-bottom-color:currentColor; }
html[data-ui="next"] .cell-link:focus-visible{ outline:2px solid var(--blue); outline-offset:2px; border-radius:2px; }

html[data-ui="next"] .qv-head{
  display:flex;align-items:center;gap:12px;
  padding-bottom:12px;margin-bottom:14px;
  border-bottom:1px solid var(--line-soft);
}
html[data-ui="next"] .qv-id{display:flex;flex-direction:column;gap:1px;min-width:0;flex:1;}
html[data-ui="next"] .qv-id > b{font-size:15px;letter-spacing:-0.012em;}
html[data-ui="next"] .qv-id > .sub{font-size:12.5px;color:var(--ink-4);}

html[data-ui="next"] .qv-strip{
  display:grid;grid-auto-flow:column;grid-auto-columns:1fr;gap:1px;
  background:var(--line-soft);
  border:1px solid var(--line-soft);border-radius:8px;overflow:hidden;
  margin-bottom:16px;
}
html[data-ui="next"] .qv-strip > div{background:var(--surface);padding:9px 12px;display:flex;flex-direction:column;gap:2px;}
html[data-ui="next"] .qv-strip small{font-size:11px;color:var(--ink-4);}
html[data-ui="next"] .qv-strip strong{font-size:13px;font-weight:600;color:var(--ink);}

/* The period filter and the search, on one line. They belong to the table under
   them, so they sit tight against it rather than in a card of their own - the
   app wraps each in its own Bootstrap card, which costs two boxes and 40px to
   say "these two controls filter the thing below". */
html[data-ui="next"] .qv-hist-ctl{display:flex;align-items:center;gap:10px;margin-bottom:12px;}

/* The tab strip inside a dialog needs the gap the page version gets from its
   surrounding padding. */
html[data-ui="next"] .dlg-body > .tabs{margin-bottom:16px;}

/* =========================================================
   Scope Filters, inside the KPI alert rule editor
   Account/NotificationSettings.razor

   The app puts this in a tinted card so it reads as a subsection of the form
   rather than more of the same form. Kept, because the distinction is real:
   everything above the card describes WHAT to measure, everything inside it
   describes WHERE to measure it, and those are answered by different people.
   ========================================================= */
html[data-ui="next"] .scope-card{
  margin-top:16px;padding:14px;
  background:var(--field);
  border:1px solid var(--line-soft);
  border-radius:10px;
}
html[data-ui="next"] .scope-card > h4{
  margin:0 0 12px;font-size:12px;color:var(--ink-3);
  display:flex;align-items:baseline;gap:6px;
}
html[data-ui="next"] .scope-card > h4 small{font-weight:400;color:var(--ink-4);}

/* Replaces the card outright for metrics that take no filters. Not an inline
   warning next to a disabled field: there is nothing to disable, the whole
   section is inapplicable, and greying out five controls says "you may not"
   where the truth is "there is nothing here". */
html[data-ui="next"] .notice{
  display:flex;align-items:flex-start;gap:9px;
  margin-top:16px;padding:12px 14px;
  background:rgba(0,100,205,.05);
  border:1px solid rgba(0,100,205,.14);
  border-radius:10px;
  font-size:12.5px;line-height:1.5;color:var(--ink-2);
}
html[data-ui="next"] .notice > svg{width:15px;height:15px;flex-shrink:0;margin-top:1px;color:var(--blue);}

/* Footer of an inline form that replaces a list, rather than a dialog footer.
   Same right-aligned Cancel / commit pair, so the two read the same way whether
   the editor opened in place or in a modal. */
html[data-ui="next"] .form-actions{
  display:flex;justify-content:flex-end;gap:8px;
  margin-top:18px;padding-top:14px;
  border-top:1px solid var(--line-soft);
}

/* One warehouse in the per-user access list. The level select is disabled until
   the box is ticked, because a level without access is not a state the app can
   store. */
html[data-ui="next"] .wa-row{
  display:grid;grid-template-columns:auto 1fr 150px;align-items:center;gap:10px;
  padding:9px 0;border-bottom:1px solid var(--line-soft);
}
html[data-ui="next"] .wa-row:last-of-type{border-bottom:0;}
html[data-ui="next"] .wa-row input[type=checkbox]{accent-color:var(--blue);width:15px;height:15px;}
html[data-ui="next"] .wa-name{font-size:13.5px;color:var(--ink);}
html[data-ui="next"] .wa-level{padding:5px 8px;font-size:12.5px;}

/* =========================================================
   Policy and terms pages
   PrivacyPolicy.razor, CookiePolicy.razor, Account/TermsAcceptance.razor

   Legal copy is the one place in the product where the reading measure matters
   more than the density. Everything else here is scannable at a glance; this is
   read once, carefully, and then never again.
   ========================================================= */
html[data-ui="next"] .legal-toc{position:sticky;top:calc(var(--topbar-h) + 20px);}
html[data-ui="next"] .toc-item{
  display:block;padding:6px 0;font-size:13px;color:var(--ink-3);
  border-left:2px solid transparent;padding-left:10px;margin-left:-10px;
}
html[data-ui="next"] .toc-item:hover{color:var(--ink);}
html[data-ui="next"] .toc-item.on{color:var(--blue);border-left-color:var(--blue);font-weight:600;}

html[data-ui="next"] .legal-body{max-width:68ch;}
html[data-ui="next"] .legal-body .lede{font-size:15px;color:var(--ink-2);margin-bottom:20px;line-height:1.6;}
html[data-ui="next"] .legal-body h4{
  margin:22px 0 7px;font-family:var(--font-head);font-size:14.5px;
  font-weight:700;color:var(--ink);letter-spacing:-0.012em;
}
html[data-ui="next"] .legal-body h4:first-of-type{margin-top:0;}
html[data-ui="next"] .legal-body p{font-size:13.5px;line-height:1.65;color:var(--ink-2);margin:0;}

/* The gate scrolls its own terms rather than the page, so Accept stays put. A
   button that walks off the bottom of a long document is how people end up
   accepting without reaching the end, or worse, not finding the button. */
html[data-ui="next"] .terms-scroll{max-height:52vh;overflow-y:auto;}

@media (max-width:900px){
  html[data-ui="next"] .legal-toc{display:none;}
  html[data-ui="next"] .page .grid[style*="240px"]{grid-template-columns:minmax(0,1fr) !important;}
}

/* ---------- auth-scene.css ---------- */
/* =========================================================
   CT Insight - sign-in backdrop
   Offline concept. Loaded only by login.html.

   Third attempt, and the first two are worth recording because each failed in a
   different direction.

     1. A warehouse aisle in perspective - racking, pallets, roof trusses,
        lights. A landing-page hero behind a login form. Too much.
     2. The same warehouse as a top-down plan in hairlines. Much quieter, but
        still a picture sitting NEXT TO the copy, competing for the same space
        rather than supporting it.

   What both missed is that the copy block is the subject. The backdrop's job is
   to give it somewhere to sit, not to be looked at. So this is two things and
   nothing else:

     - A soft wash anchored under the copy. Pure gradient, no edges, nothing that
       resolves into a shape.
     - The CaptureTech C, oversized and very faint, positioned so its implied
       circle ENCLOSES the copy. The mark wraps the text from the left and its
       tips curl back in above and below, so the text sits in its opening. The
       brand does the framing instead of a scene doing the decorating.

   The C is the landing page's own mark, not an approximation: the ring geometry
   and that six-point clip-path come from `landing.css`, where the comment notes
   the points are measured off the logo file rather than eyeballed.
   ========================================================= */

/* The copy block is the positioning context, so the mark tracks the text at any
   width. The earlier versions were fixed to the viewport and aimed at where the
   text happened to land on one screen size. */
html[data-ui="next"] .auth-pitch{ position:relative; }
html[data-ui="next"] .auth-pitch > *{ position:relative; z-index:1; }

/* ---------- The wash ----------
   Two overlapping radials across the WHOLE PAGE, not the copy column.

   It was on .auth-pitch, and a gradient on a box has a box: however soft the
   colour, the rectangle still ended - and it ended right where the sign-in card
   begins, so the fade appeared to stop against the card and the card looked like
   it was sitting on a panel. Moving it to .auth means the only edges are the
   ones the gradients make themselves, and a radial that ends in `transparent`
   has none. */
html[data-ui="next"] .auth{ position:relative; }
html[data-ui="next"] .auth::before{
  content:"";
  position:absolute; inset:0;
  z-index:0;
  pointer-events:none;
  background:
    radial-gradient(64% 58% at 20% 40%, rgba(0,155,208,.075), transparent 68%),
    radial-gradient(58% 64% at 42% 92%, rgba(0,100,205,.055), transparent 70%),
    radial-gradient(46% 50% at 88% 18%, rgba(0,155,208,.035), transparent 72%);
}

/* ---------- The mark ----------
   All of the geometry derives from one number.

   The C is a thick-bordered circle clipped to its left slice. That circle is
   359.9% of the mark box's width and starts 19.4% above it, which puts its
   centre at (1.7995w, 1.2965w) from the mark's top-left corner. Offsetting the
   mark by exactly that much lands the circle's centre on the middle of the copy
   - which is the whole point of this version, and why those two constants are
   written out rather than nudged into place by eye. */
html[data-ui="next"] .auth-mark{
  --mw:clamp(190px, 17vw, 268px);       /* mark box width; everything scales off it */

  /* Pointer offset, -1 to 1 on each axis, written to <html> by mark-pointer.js
     and inherited from there. It must NOT be re-declared here: a local --px:0
     would shadow the inherited value, and the mark would sit perfectly still
     while the stylesheet looked entirely correct.

     The DISTANCE is decided here, beside the mark's other geometry, rather than
     in the script - which is also what lets the rail's copy of this mark move a
     different amount from the same two numbers. 24px and 15px against an arc
     nine hundred pixels across is about a fortieth of its size: enough to notice
     when you move, never enough to read as something happening. */

  position:absolute;
  left:calc(50% - 1.7995 * var(--mw));
  top:calc(50% - 1.2965 * var(--mw));
  width:var(--mw);
  aspect-ratio:290 / 752;               /* the mark's own proportion */
  z-index:0;
  pointer-events:none;

  /* Fades along its length rather than stopping. A hard-ended arc reads as a
     shape someone drew; one that dissolves reads as ground. This is the gradient
     doing its work on the mark itself, so the two ideas are one thing rather
     than a gradient with a logo sitting on top of it. */
  -webkit-mask:linear-gradient(158deg, #000 12%, rgba(0,0,0,.55) 52%, transparent 88%);
  mask:linear-gradient(158deg, #000 12%, rgba(0,0,0,.55) 52%, transparent 88%);

  /* Two animations, on two DIFFERENT properties, which is the only reason they
     can coexist. The entrance uses `scale` and `opacity`; the drift uses
     `rotate`. Both are independent transform properties, so neither overwrites
     the other - put both on `transform` and the second declaration simply wins
     and one of them silently never runs.

     The drift is a few degrees over most of a minute. At this size three degrees
     moves the arc a long way in absolute terms: enough to feel alive, far too
     slow to track. */
  /* The entrance owns `scale`, the drift owns `rotate`, and the pointer owns
     `translate` - three behaviours, three independent properties, none able to
     overwrite another. On a single `transform` any two of them would be a fight
     the last declaration wins.

     The pointer wants to rotate as well, and it cannot do it here: `rotate` is
     already animated, and an animation beats a plain declaration in the cascade,
     so a `rotate:` on this element would be accepted, look correct in the
     stylesheet, and never once be applied. It goes on .auth-c instead - see
     there.

     The transition is what makes it feel attached rather than wired: the mark
     eases toward the cursor over half a second and trails it slightly, which
     reads as weight. Following exactly would read as a cursor accessory. */
  /* The drift rotates THIS box, and by default a box rotates about its own
     centre - which is 331px to the LEFT of the ring's centre, because the ring
     hangs off to one side of the mark. So a 3.5deg drift was not turning the C
     in place, it was swinging it around on a 331px arm, and lifting the whole
     circle 20px up and 15px down as it went.

     The ring is 918px tall in a 980px viewport: 62px of slack, total. Twenty of
     those went to the drift, fifteen more to the pointer, and the top of the arc
     - the opaque end of the mask, so the end you actually see - ran out of page.

     Anchoring the origin ON the ring's centre makes the drift a pure rotation of
     the C about itself: the tips still sweep, the circle no longer moves. The
     arm is exactly horizontal (the ring's centre and the mark's centre share a
     y), so 179.95% across and 50% down is the ring's centre precisely, not an
     approximation. */
  transform-origin:179.95% 50%;

  /* 8px rather than 15. With the drift no longer stealing vertical room this
     would fit at 15, but the mark is sized against the COPY and the viewport
     only has to be a little shorter for the slack to vanish again. The
     horizontal 24px is untouched: sideways it bleeds off the left edge, which is
     clipped on purpose and reads as intended rather than as a crop. */
  translate:calc(var(--px, 0) * 24px) calc(var(--py, 0) * 8px);
  transition:translate 520ms cubic-bezier(.22,.68,0,1);

  /* The drift alone here. The entrance is added under .auth-enter below, so a
     mark built after the page has already arrived does not fade in a second
     time. */
  animation:auth-drift 48s ease-in-out infinite alternate;
}
@keyframes auth-mark-in{
  from{ opacity:0; scale:1.06; }
  to{ opacity:1; scale:1; }
}

html[data-ui="next"] .auth-c{
  position:absolute;display:block;
  left:0;top:-19.4%;
  width:359.9%;
  aspect-ratio:1;
  box-sizing:border-box;
  /* 17.7% of the ring's diameter, per the mark. Deriving it keeps the stroke
     proportional at every size instead of going spindly on small screens. */
  border:calc(var(--mw) * 3.599 * 0.177) solid rgba(0,155,208,.13);
  border-radius:50%;
  /* The six points that form the C's angled tips, measured off the logo file. */
  clip-path:polygon(0% 0%, 15.3% 14.0%, 28.8% 26.1%, 28.8% 73.9%, 15.3% 86.0%, 0% 100%);

  /* The pointer's rotation, on the ring rather than on the mark box, for two
     reasons. It is the only free `rotate` in the stack - the mark's is spoken
     for by the drift. And it pivots better: this element IS the circle, so its
     centre is the ring's centre, which is also the middle of the copy. The mark
     box's centre is somewhere off to one side of that, and turning about it
     would swing the arc's radius rather than just its angle.

     Both axes contribute, at different weights. Horizontal is the dominant read
     of "lean toward the cursor"; the vertical term is half as strong and tips
     the C's opening up or down to follow. Under three degrees combined, which on
     an arc this size moves its edge about twenty pixels - the same order as the
     translate, so the two read as one movement rather than two. */
  rotate:calc(var(--px, 0) * 2.4deg + var(--py, 0) * 1.1deg);
  transition:rotate 520ms cubic-bezier(.22,.68,0,1);
}

@keyframes auth-drift{
  from{ rotate:-3.5deg; }
  to{ rotate:2.5deg; }
}

/* ---------- Entrance ----------
   The page arrives rather than appearing. Everything moves the same small
   distance in the same direction, staggered down the column, so it reads as one
   thing settling and not as six elements each doing their own animation.

   10px and 460ms: far enough to register as motion, over before anyone who came
   here to type a password has finished reaching for the keyboard.

   Every entrance hangs off .auth-enter, and only the first render of the page
   carries that class. The app prerenders the scene on the server and then, when
   its circuit connects about a second later, discards that DOM and builds the
   same thing again from the interactive render. New nodes start their
   animations from the beginning, so the page arrived, and then arrived again.
   The interactive pass leaves the class off and its nodes are born at rest.
   login.html renders once, so it simply sets the class. */
html[data-ui="next"] .auth-enter .auth-mark{
  animation:
    auth-mark-in 1100ms cubic-bezier(.22,.68,0,1) both,
    auth-drift 48s ease-in-out 1100ms infinite alternate;
}
html[data-ui="next"] .auth-enter .auth-pitch h1,
html[data-ui="next"] .auth-enter .auth-pitch .lede,
html[data-ui="next"] .auth-enter .auth-points li,
html[data-ui="next"] .auth-enter .auth-trust,
html[data-ui="next"] .auth-enter .auth-card{
  animation:auth-rise 460ms cubic-bezier(.22,.68,0,1) both;
}
html[data-ui="next"] .auth-enter .auth-pitch h1{ animation-delay:60ms; }
html[data-ui="next"] .auth-enter .auth-pitch .lede{ animation-delay:110ms; }
html[data-ui="next"] .auth-enter .auth-points li:nth-child(1){ animation-delay:160ms; }
html[data-ui="next"] .auth-enter .auth-points li:nth-child(2){ animation-delay:195ms; }
html[data-ui="next"] .auth-enter .auth-points li:nth-child(3){ animation-delay:230ms; }
html[data-ui="next"] .auth-enter .auth-trust{ animation-delay:275ms; }
/* The card is the destination, so it leads rather than trails - the copy is
   what the eye catches up with. */
html[data-ui="next"] .auth-enter .auth-card{ animation-delay:40ms; }

@keyframes auth-rise{
  from{ opacity:0; transform:translateY(10px); }
  to{ opacity:1; transform:none; }
}

/* The mark is composition - it frames the copy, so it stays. Only the drift
   goes, resting mid-range rather than at one end of it. */
/* Nothing moves, and nothing is left mid-animation either: `both` fill means an
   element whose animation never runs would sit at its `from` state - invisible
   and 10px low - so the rules have to put everything back explicitly. */
@media (prefers-reduced-motion:reduce){
  /* The script also stops writing, but the CSS says so too: a stylesheet that
     depends on a script honouring the setting is one script away from not
     honouring it. */
  html[data-ui="next"] .auth-mark,
html[data-ui="next"] .auth-enter .auth-mark{ animation:none; rotate:-0.5deg; opacity:1; scale:1;
              translate:none; transition:none; }
  html[data-ui="next"] .auth-c{ rotate:none; transition:none; }
  html[data-ui="next"] .auth-enter .auth-pitch h1,
html[data-ui="next"] .auth-enter .auth-pitch .lede,
html[data-ui="next"] .auth-enter .auth-points li,
html[data-ui="next"] .auth-enter .auth-trust,
html[data-ui="next"] .auth-enter .auth-card{
    animation:none; opacity:1; transform:none;
  }
}

/* ---------- Containment ----------
   The mark is deliberately wider than its column and bleeds off the left edge of
   the page. The clip therefore belongs on .auth, which spans the viewport - put
   on .auth-body it cropped at the 1080px content column instead, which is 210px
   in from the window edge, and swallowed all of the arc except its top tip. The
   bleed is the point: a mark that stops neatly inside the column is a picture
   again.

   `clip` rather than `hidden`: it crops without making anything scrollable,
   which `hidden` would do the moment something overflowed.

   Both axes, not just x. The ring is nearly as tall as the viewport and hangs
   off .auth-pitch, so with only the horizontal clip its bottom edge counted as
   vertical overflow: the moment the pointer nudged it down, or the drift or the
   pointer rotation swung a tip low enough, the page grew a scrollbar, and it
   went away again when the cursor moved off. Clipping y too costs nothing: the
   real content sizes .auth, so the page still scrolls when it genuinely has
   to, and the mark is the only thing that ever leaves the box. */
html[data-ui="next"] .auth{ overflow:clip; }

/* Every real region sits above the backdrop. Listing them beats putting z-index
   on .auth, which would trap the layer underneath it. */
html[data-ui="next"] .auth-head,
html[data-ui="next"] .auth-body,
html[data-ui="next"] .auth-foot{ position:relative; z-index:1; }
html[data-ui="next"] .auth-card{ position:relative; z-index:2; }

/* Below the split the copy column is gone, and the mark goes with the thing it
   was framing. */
/* Below the split the copy column is gone, so the mark goes with the thing it
   was framing. The wash stays: it is page-wide and has nothing to do with the
   column. */
@media (max-width:900px){ html[data-ui="next"] .auth-mark{ display:none; } }
