/*
 * OLT Admin Panel — component library.
 *
 * Every colour/radius/shadow/font/duration below is a var(--token) from
 * theme.css; nothing here hard-codes a hex. Where the pixel reference used a
 * shade theme.css has no token for, the nearest token is substituted and
 * flagged inline with "// nearest:" so it's easy to find later.
 *
 * Interactive state is `is-*` classes plus real :hover/:focus-visible/
 * [disabled] — the reference encodes hover as a `style-hover` attribute,
 * every one of those became a real :hover rule here.
 */

/* ============================== buttons ==============================
   There is no button system here any more.

   NowaCRM's components/buttons.css owns .btn and every variant this app
   uses -- primary, secondary, danger, success, ghost, outline-*, plus the
   -xs/-sm/-lg/-icon/-block sizes. This file used to define a SECOND set
   under a `.btn--primary` spelling with the same job and a different look,
   so <x-ui.button> ended up emitting both class names at once to keep
   whichever stylesheet happened to load last happy.

   One vocabulary now: Bootstrap's class names, NowaCRM's definitions.
   Only the two things NowaCRM has no answer for stay below.
   =================================================================== */

/* NowaCRM's disabled rule sets `pointer-events: none`, which also
   suppresses the native title tooltip. Several buttons in this app are
   disabled precisely to explain why ("You do not have permission to
   dispatch actions on this OLT"), and that explanation is carried in
   `title`. Hand the pointer back so the tooltip still appears. */
.btn:disabled,
.btn[disabled],
.btn.is-disabled {
  pointer-events: auto;
  cursor: not-allowed;
}

/* Icons are flex children of buttons and rows; without this they shrink to a
   few pixels the moment the container is tight. Their width/height attributes
   do not survive flex-shrink on their own. */
.ui-icon { flex: none; }

.btn-spinner {
  width: 13px;
  height: 13px;
  border-radius: 50%;
  border: 2px solid var(--accent-line-strong);
  border-top-color: var(--accent);
  animation: oltSpin2 .8s linear infinite;
  flex: none;
}
.btn.is-loading { cursor: wait; }

/* ============================== badges ================================ */

.badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  height: 22px;
  padding: 0 10px;
  border-radius: var(--r-pill);
  background: var(--tone-soft, var(--sunken-2));
  color: var(--tone, var(--ink-2));
  border: none;
  font: 600 11px/1 var(--font);
  letter-spacing: .01em;
  white-space: nowrap;
}
.badge-dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: none; }

.badge--ok { --tone: #16a34a; --tone-soft: #f0fdf4; }
.badge--warn { --tone: #ea580c; --tone-soft: #fff7ed; }
.badge--crit { --tone: #dc2626; --tone-soft: #fef2f2; }
.badge--info { --tone: #475569; --tone-soft: #f1f5f9; }
.badge--accent { --tone: #2563eb; --tone-soft: #eff6ff; }

/* ============================== alerts ================================= */

.alert {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 11px 12px;
  border-radius: var(--r-lg);
  background: var(--tone-wash, var(--sunken));
  border: 1px solid var(--tone-line, var(--line));
}
.alert-icon {
  width: 22px;
  height: 22px;
  border-radius: var(--r-sm);
  background: var(--tone, var(--ink-3));
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  color: var(--paper);
}
.alert-body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.alert-body > strong,
.alert-body > .alert-title { font: 800 11.5px/1.2 var(--font); color: var(--ink); }
.alert-body > span,
.alert-body > .alert-msg { font: 500 11px/1.45 var(--font); color: var(--ink-3); }

.alert--ok { --tone: var(--ok); --tone-wash: var(--ok-wash); --tone-line: var(--ok-line); }
.alert--warn { --tone: var(--warn); --tone-wash: var(--warn-wash); --tone-line: var(--warn-line); }
.alert--crit { --tone: var(--crit); --tone-wash: var(--crit-wash); --tone-line: var(--crit-line); }
.alert--info { --tone: var(--info); --tone-wash: var(--info-wash); --tone-line: var(--info-line); }

/* ============================== cards =================================== */

.card {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-2xl);
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  box-shadow: var(--sh-2);
  transition: border-color var(--t-slow), box-shadow var(--t-slow), transform var(--t-slow);
}
.card-head { display: flex; align-items: center; gap: 9px; }
.card-head-rule { width: 4px; height: 14px; border-radius: var(--r); background: var(--tone, var(--accent)); flex: none; }
.card-title { font: 800 14px/1 var(--font); letter-spacing: -.3px; color: var(--ink); white-space: nowrap; }
.card-sub { font: 500 11px/1 var(--font); color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Head trailing slot: <x-ui.card>'s `head` slot and its `href` chevron both
   sit at the far right of the head row, whatever the title width. */
.card-head-actions { display: flex; align-items: center; gap: 8px; margin-left: auto; }
.card-head-actions + .card-head-link { margin-left: 0; }
.card-head-link {
  display: inline-flex;
  align-items: center;
  margin-left: auto;
  color: var(--muted);
  transition: color var(--t);
}
.card-head-link:hover { color: var(--accent-dark); }
.card-body { display: flex; flex-direction: column; gap: 12px; flex: 1; min-width: 0; }
.card-foot {
  display: flex;
  align-items: center;
  gap: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--line-2);
}
/* sticky form action bar (spec 1015-1022) reuses .card + .card-foot */
.card--sticky {
  position: sticky;
  bottom: 0;
  z-index: 20;
  padding: 12px 16px;
  flex-direction: row;
  align-items: center;
  gap: 10px;
  box-shadow: 0 -2px 12px color-mix(in srgb, var(--ink) 6%, transparent); /* nearest: no upward/downward shadow token exists */
}
/* Context on the left, actions on the right. Without this the buttons crowd
   up against the label and the bar reads as a sentence, not a footer. */
.card--sticky .card-sub { margin-right: auto; }

@media (max-width: 620px) {
  .card--sticky { flex-wrap: wrap; }
  .card--sticky .card-sub { width: 100%; margin-right: 0; }
  .card--sticky .btn { flex: 1; justify-content: center; }
}

.card--ok { --tone: var(--ok); }
.card--warn { --tone: var(--warn); }
.card--crit { --tone: var(--crit); }
.card--info { --tone: var(--info); }
.card--accent { --tone: var(--accent); }

/* grid wrappers used by the dashboard layout; not named in the class-name
   contract, added because req. #6 (responsive stat/metric/card grids)
   has no other home for them */
.stat-grid { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 12px; }
.metric-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; }
.card-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }

/* A column of full-width cards -- the form screens stack rather than grid,
   and without an owned gap the cards butt together with only their borders
   between them. */
.form-stack {
  display: flex;
  flex-direction: column;
  gap: 12px;
  /* Room for .card--sticky to float over. Without it the action bar pins itself
     across the last card's controls with no way to scroll them clear. */
  padding-bottom: 72px;
}

@media (max-width: 1280px) {
  .stat-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .metric-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 1024px) {
  .stat-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .card-grid { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 720px) {
  .stat-grid { grid-template-columns: minmax(0, 1fr); }
  .metric-grid { grid-template-columns: minmax(0, 1fr); }
}

/* ============================== stat cards ============================== */

.stat-card {
  min-height: 168px;
  padding: 16px 16px 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
  overflow: hidden;
}
/* Three rows, each laid out explicitly. The component emits stat-head /
   stat-figure / stat-foot; earlier versions emitted bare <div>s against these
   rules and the whole tile collapsed into an unstyled vertical stack. */
.stat-head { display: flex; align-items: center; gap: 10px; }
.stat-head > span:not(.stat-icon) {
  font: 800 13px/1 var(--font);
  letter-spacing: -.2px;
  color: var(--ink);
  white-space: nowrap;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Baseline-aligned so the 34px numeral and the delta pill sit on one line,
   with the pill pushed to the trailing edge. */
.stat-figure { display: flex; align-items: baseline; gap: 5px; }
.stat-figure .stat-delta { margin-left: auto; align-self: center; }
.stat-icon {
  width: 36px;
  height: 36px;
  border-radius: var(--r-md);
  background: var(--tone-soft, var(--accent-soft));
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  color: var(--tone, var(--accent));
}
.stat-value { font: 800 30px/1 var(--font); letter-spacing: -1.2px; color: var(--ink); }
.stat-delta {
  font: 700 9px/1 var(--mono);
  color: var(--tone, var(--accent));
  background: var(--tone-soft, var(--accent-soft));
  padding: 5px 6px;
  border-radius: var(--r);
  white-space: nowrap;
  flex: none;
}
.stat-foot { display: flex; align-items: flex-end; gap: 8px; margin-top: auto; }
.stat-foot > span { font: 500 11px/1.3 var(--font); color: var(--muted); padding-bottom: 12px; white-space: nowrap; }
/* The sparkline paints with currentColor, so the tile's tone has to reach it —
   otherwise every spark renders in body ink regardless of the card's state. */
.stat-spark { flex: 1; min-width: 0; height: 54px; display: block; color: var(--tone, var(--accent)); }

.stat-delta--accent { --tone: var(--accent); --tone-soft: var(--accent-soft); }
.stat-delta--ok { --tone: var(--ok); --tone-soft: var(--ok-soft); }
.stat-delta--info { --tone: var(--info); --tone-soft: var(--info-soft); }
.stat-delta--warn { --tone: var(--warn); --tone-soft: var(--warn-soft); }
.stat-delta--crit { --tone: var(--crit); --tone-soft: var(--crit-soft); }

/* Staggered entrance, 60ms apart, matching the reference design's cascade. */
.stat-card--step-0 { animation: oltRise .35s ease-out 0s both; }
.stat-card--step-1 { animation: oltRise .35s ease-out .06s both; }
.stat-card--step-2 { animation: oltRise .35s ease-out .12s both; }
.stat-card--step-3 { animation: oltRise .35s ease-out .18s both; }
.stat-card--step-4 { animation: oltRise .35s ease-out .24s both; }
.stat-card--step-5 { animation: oltRise .35s ease-out .30s both; }

.stat-card--accent { --tone: var(--accent); --tone-lift: var(--accent-lift); --tone-soft: var(--accent-soft); }
.stat-card--ok { --tone: var(--ok); --tone-lift: var(--ok-bright); --tone-soft: var(--ok-soft); }
.stat-card--info { --tone: var(--info); --tone-lift: var(--info-bright); --tone-soft: var(--info-soft); }
.stat-card--warn { --tone: var(--warn); --tone-lift: var(--warn-bright); --tone-soft: var(--warn-soft); }
.stat-card--crit { --tone: var(--crit); --tone-lift: var(--crit-bright); --tone-soft: var(--crit-soft); }

/* NowaCRM wave tiles: a tone-tinted radial glow pooled in the top-right
   corner, over a near-white 145deg surface gradient. Drawn as a ::before
   overlay so the stat-card's own stacking (head / figure / foot) stays
   untouched. */
.stat-card {
  position: relative;
  background: linear-gradient(145deg, rgba(255, 255, 255, .96), rgba(248, 250, 252, .9));
}
.stat-card::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(circle at 100% 0%, color-mix(in srgb, var(--tone, var(--accent)) 25%, transparent) 0%, transparent 55%);
}
.stat-card > * { position: relative; }

/* ============================== metric lists ============================ */

.metric-list { display: flex; flex-direction: column; }
.metric-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 9px 7px;
  margin: 0 -7px;
  border-radius: var(--r-md);
  transition: background var(--t-fast), padding-left var(--t-fast);
}
.metric-row + .metric-row { border-top: 1px solid var(--line-2); }
.metric-row:hover { background: var(--tone-soft, var(--accent-soft)); padding-left: 12px; }
.metric-icon {
  width: 20px;
  height: 20px;
  border-radius: var(--r-sm); /* nearest: design uses 7px */
  background: var(--tone-soft, var(--accent-soft));
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  color: var(--tone, var(--accent));
}
.metric-label { font: 600 11.5px/1.2 var(--font); color: var(--ink-2); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.metric-value { flex: none; font: 700 11px/1 var(--mono); color: var(--ink); white-space: nowrap; margin-left: auto; }
.metric-value--ok { color: var(--ok); }
.metric-value--warn { color: var(--warn); }
.metric-value--crit { color: var(--crit); }
.metric-value--info { color: var(--info); }

.metric-list--accent { --tone: var(--accent); }
.metric-list--ok { --tone: var(--ok); }
.metric-list--info { --tone: var(--info); }
.metric-list--warn { --tone: var(--warn); }
.metric-list--crit { --tone: var(--crit); }

/* ============================== progress ================================ */

.progress { height: 6px; border-radius: var(--r); background: var(--line-4); overflow: hidden; }
.progress-fill {
  height: 100%;
  border-radius: var(--r);
  background: linear-gradient(90deg, var(--tone-lift, var(--accent-lift)), var(--tone, var(--accent)));
  transition: width var(--t-slow);
}
.progress--ok .progress-fill { --tone: var(--ok); --tone-lift: var(--ok-bright); }
.progress--warn .progress-fill { --tone: var(--warn); --tone-lift: var(--warn-bright); }
.progress--crit .progress-fill { --tone: var(--crit); --tone-lift: var(--crit-bright); }
.progress--info .progress-fill { --tone: var(--info); --tone-lift: var(--info-bright); }

/* ============================== dividers ================================= */

.section-divider {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 22px 0 12px;
  font: 800 13px/1 var(--font);
  color: var(--ink);
  letter-spacing: -.2px;
}
.section-divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--line);
}
.section-divider .section-divider-tag {
  font: 600 10px/1 var(--mono);
  color: var(--muted-2);
  letter-spacing: .08em;
}

/* ============================== form fields ============================== */

.field { display: flex; flex-direction: column; gap: 7px; min-width: 0; }
.field-label { font: 700 11px/1 var(--font); color: var(--ink-3); letter-spacing: -.1px; }
.field-label .field-required { color: var(--crit); }
.field-help { font: 500 10.5px/1 var(--font); color: var(--muted-2); }
.field-error { font: 500 10.5px/1.3 var(--font); color: var(--crit); display: none; }
.field-error:not(:empty) { display: block; }

/* ============================== inputs ==================================== */

.input {
  width: 100%;
  height: 40px;
  border-radius: var(--r-lg);
  border: 1px solid var(--line);
  background: var(--sunken);
  padding: 0 12px;
  font: 600 12.5px/1 var(--font);
  color: var(--ink);
  outline: none;
  transition: border-color var(--t), background var(--t), box-shadow var(--t);
}
.input::placeholder { color: var(--muted-2); }
.input:hover { border-color: var(--accent-line-strong); background: var(--paper); }
.input:focus-visible,
.input:focus {
  border-color: var(--accent-line-strong);
  background: var(--paper);
  box-shadow: 0 0 0 3px var(--accent-ring);
}
.input.is-invalid { border-color: var(--crit-line); background: var(--crit-wash); }
.input.is-invalid:focus { box-shadow: 0 0 0 3px var(--crit-soft); }
.input[disabled] { background: var(--sunken-2); color: var(--muted-2); cursor: not-allowed; }

.input--mono { font-family: var(--mono); font-size: 12px; }

/* Leading icon INSIDE the field (as opposed to .input-addon, which is its own
   bordered box outside it). The wrapper is the positioning context; the input
   keeps its own border, so focus and invalid states need no new rules. */
.input-iconwrap { position: relative; display: block; min-width: 0; }
.input-icon {
  position: absolute;
  left: 12px;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  color: var(--muted);
  pointer-events: none;
  transition: color var(--t);
}
.input--iconed { padding-left: 34px; }
.input-iconwrap:focus-within .input-icon { color: var(--accent); }
.input-iconwrap:has(.is-invalid) .input-icon { color: var(--crit); }

.textarea {
  width: 100%;
  height: 118px;
  border-radius: var(--r-lg);
  border: 1px solid var(--line);
  background: var(--sunken);
  padding: 11px 12px;
  font: 500 12.5px/1.55 var(--font);
  color: var(--ink);
  outline: none;
  resize: none;
  transition: border-color var(--t), background var(--t), box-shadow var(--t);
}
.textarea:focus-visible,
.textarea:focus { border-color: var(--accent-line-strong); background: var(--paper); box-shadow: 0 0 0 3px var(--accent-ring); }
.textarea-count { font: 500 10.5px/1 var(--mono); color: var(--muted-2); }

.input-group { display: flex; align-items: center; gap: 0; }
.input-group .input { border-radius: 0 var(--r-lg) var(--r-lg) 0; }
.input-addon {
  height: 40px;
  display: flex;
  align-items: center;
  padding: 0 11px;
  border: 1px solid var(--line);
  border-right: 0;
  border-radius: var(--r-lg) 0 0 var(--r-lg);
  background: var(--accent-soft);
  font: 700 11px/1 var(--mono);
  color: var(--accent-dark);
  white-space: nowrap;
  flex: none;
}

/* ============================== stepper =================================== */

.stepper { display: flex; align-items: center; gap: 8px; }
.stepper-btn {
  width: 40px;
  height: 40px;
  border-radius: var(--r-lg);
  border: 1px solid var(--line);
  background: var(--paper);
  font: 800 15px/1 var(--font);
  color: var(--ink-2);
  cursor: pointer;
  flex: none;
  transition: border-color var(--t), color var(--t), background var(--t);
}
.stepper-btn:hover { border-color: var(--accent); color: var(--accent-dark); background: var(--accent-tint); }
.stepper .input { text-align: center; font-family: var(--mono); font-weight: 700; }

/* ============================== select + dropdown ========================= */

.select { position: relative; min-width: 0; }
.select-btn {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  height: 40px;
  border-radius: var(--r-lg);
  border: 1px solid var(--line);
  background: var(--sunken);
  padding: 0 12px;
  cursor: pointer;
  text-align: left;
  transition: border-color var(--t), background var(--t), box-shadow var(--t);
}
.select-btn:hover { border-color: var(--accent-line-strong); }
.select-btn.is-open,
.select.is-open .select-btn {
  border-color: var(--accent-line-strong);
  background: var(--paper);
  box-shadow: 0 0 0 3px var(--accent-ring);
}
.select-value { font: 600 12.5px/1 var(--font); color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 1; }

/* The icon sits above both faces of the control -- the native <select> that
   renders before JS runs, and the .select-btn that replaces it after. */
.select-icon {
  position: absolute;
  left: 12px;
  top: 50%;
  transform: translateY(-50%);
  z-index: 1;
  display: flex;
  color: var(--muted);
  pointer-events: none;
  transition: color var(--t);
}
.select--iconed .select-native,
.select--iconed .select-btn { padding-left: 34px; }
.select--iconed:focus-within .select-icon,
.select--iconed.is-open .select-icon { color: var(--accent); }
.select-caret { flex: none; transition: transform var(--t-slow); }
.select.is-open .select-caret,
.is-open > .select-caret { transform: rotate(180deg); }

.dropdown { position: relative; }
.dropdown-panel {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-xl);
  box-shadow: var(--sh-pop);
  padding: 6px;
  z-index: 40;
  animation: oltDrop .16s ease-out;
  max-height: 250px;
  overflow: auto;
}
.dropdown--right { left: auto; right: 0; }
/* Visibility is the `hidden` attribute, set by ui/dropdown.js -- .is-open only
   styles the trigger. Stated explicitly because .dropdown-panel sets position
   but no display, so without this the panel's closed state rests entirely on
   the UA sheet. */
.dropdown-panel[hidden] { display: none; }
.dropdown-head {
  display: block;
  padding: 7px 10px 8px;
  font: 700 9.5px/1 var(--mono);
  color: var(--muted-2);
  letter-spacing: .1em;
}
.dropdown-divider { height: 1px; background: var(--line-4); margin: 4px 4px; }
.dropdown-search {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 34px;
  padding: 0 10px;
  margin-bottom: 4px;
  border-radius: var(--r-md);
  background: var(--sunken);
  border: 1px solid var(--line);
}
.dropdown-search input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: transparent;
  outline: none;
  font: 600 12px/1 var(--font);
  color: var(--ink);
}
.dropdown-opt {
  display: flex;
  align-items: center;
  gap: 9px;
  height: 34px;
  padding: 0 10px;
  border-radius: var(--r-md);
  cursor: pointer;
  transition: background var(--t-fast);
  font: 600 12px/1 var(--font);
  color: var(--ink-2);
}
.dropdown-opt:hover { background: var(--accent-tint); }
.dropdown-opt.is-selected { background: var(--accent-tint); }
.dropdown-empty { padding: 12px 10px; font: 500 11.5px/1 var(--font); color: var(--muted-2); text-align: center; }

/* ============================== tags / multiselect ========================= */

.tags {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  min-height: 40px;
  padding: 6px 10px;
  border-radius: var(--r-lg);
  border: 1px solid var(--line);
  background: var(--sunken);
  cursor: text;
  transition: border-color var(--t), background var(--t), box-shadow var(--t);
}
.tags:hover { border-color: var(--accent-line-strong); }
.tags.is-open {
  border-color: var(--accent-line-strong);
  background: var(--paper);
  box-shadow: 0 0 0 3px var(--accent-ring);
}
.tags input {
  flex: 1;
  min-width: 100px;
  height: 26px;
  border: 0;
  background: transparent;
  outline: none;
  font: 600 12px/1 var(--font);
  color: var(--ink);
}
.tag {
  display: flex;
  align-items: center;
  gap: 6px;
  height: 26px;
  padding: 0 6px 0 9px;
  border-radius: var(--r);
  background: var(--accent-soft);
  border: 1px solid var(--accent-line);
  flex: none;
  font: 700 11px/1 var(--font);
  color: var(--accent-dark);
  white-space: nowrap;
}
.tag-remove {
  width: 15px;
  height: 15px;
  border-radius: var(--r);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  color: var(--accent-dark);
  transition: background var(--t-fast);
}
.tag-remove:hover { background: var(--accent-line); }

/* ============================== switch / checkbox / radio ================== */

/* The control is the row: track + optional label. Sizing lives on
   .switch-track so the label is never squeezed into the pill. */
.switch {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  cursor: pointer;
  min-width: 0;
}
.switch-track {
  width: 42px;
  height: 24px;
  border-radius: var(--r-pill);
  border: 0;
  padding: 3px;
  display: flex;
  flex: none;
  justify-content: flex-start;
  background: var(--line-3);
  transition: background var(--t-slow);
}
.switch.is-on .switch-track { justify-content: flex-end; background: var(--accent); }
.switch-label { font: 600 12px/1.35 var(--font); color: var(--ink-2); }
.switch:hover .switch-track { background: var(--muted-3); }
.switch.is-on:hover .switch-track { background: var(--accent-dark); }
.switch:focus-visible { outline: 2px solid rgba(99, 102, 241, .4); outline-offset: 3px; border-radius: var(--r-sm); }
.switch-knob {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--paper);
  box-shadow: var(--sh-1);
  transition: transform var(--t-slow);
}

.checkbox {
  width: 17px;
  height: 17px;
  border-radius: var(--r-xs);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  border: 1.5px solid var(--muted-3); /* nearest: design uses a shade between muted-3 and line-3 */
  background: var(--paper);
  color: var(--paper);
  transition: all var(--t-fast);
  flex: none;
}
.checkbox.is-checked { border-color: var(--accent); background: var(--accent); }
.checkbox--lg { width: 20px; height: 20px; border-radius: var(--r-sm); }

.radio-chips { display: flex; gap: 8px; flex-wrap: wrap; }
.radio-chip {
  display: flex;
  align-items: center;
  gap: 7px;
  height: 40px;
  padding: 0 13px;
  border-radius: var(--r-lg);
  border: 1px solid var(--line);
  background: var(--paper);
  font: 700 12px/1 var(--font);
  color: var(--ink-3);
  cursor: pointer;
  flex: none;
  transition: border-color var(--t), background var(--t), color var(--t);
}
.radio-chip.is-active { border-color: var(--accent); background: var(--accent-tint); color: var(--accent-dark); }
.radio-dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  flex: none;
  border: 2px solid var(--muted-3); /* nearest: design uses a shade between muted-3 and line-3 */
  background: transparent;
  transition: all var(--t);
}
.radio-chip.is-active .radio-dot { border-color: var(--accent); background: var(--accent); box-shadow: inset 0 0 0 2px var(--paper); }

/* ============================== choice cards =============================== */

/* Tile-sized radios for the one choice that steers the rest of a form. The
   native radio stays the source of truth and only :has() reads it, so there
   is no JS and no is-selected class to keep in sync. */
.choice-cards {
  display: grid;
  grid-template-columns: repeat(var(--choice-columns, 2), minmax(0, 1fr));
  gap: 12px;
}
.choice-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  padding: 18px 14px;
  border-radius: var(--r-2xl);
  border: 1.5px solid var(--line);
  background: var(--paper);
  cursor: pointer;
  text-align: center;
  transition: border-color var(--t), background var(--t), box-shadow var(--t);
}
.choice-card:hover { border-color: var(--accent-line-hover); background: var(--accent-wash); }
.choice-card-icon { color: var(--muted); transition: color var(--t); }
.choice-card-title { font: 800 13px/1.2 var(--font); color: var(--ink); letter-spacing: -.2px; }
.choice-card-sub { font: 500 11px/1.3 var(--font); color: var(--muted); }

.choice-card:has(input:checked) {
  border-color: var(--accent);
  background: linear-gradient(180deg, var(--accent-tint), var(--accent-soft));
  box-shadow: 0 0 0 3px var(--accent-ring);
}
.choice-card:has(input:checked) .choice-card-icon { color: var(--accent); }
.choice-card:has(input:checked) .choice-card-sub { color: var(--accent-ink); }

/* The visually-hidden radio still takes focus, so the ring has to be drawn on
   the tile the sighted keyboard user is actually looking at. */
.choice-card:has(input:focus-visible) { outline: 2px solid rgba(99, 102, 241, .4); outline-offset: 2px; }
.choice-cards.is-invalid .choice-card { border-color: var(--crit-line); }

@media (max-width: 620px) {
  .choice-cards { grid-template-columns: minmax(0, 1fr); }
}

/* ============================== calendar =================================== */

.calendar { width: 262px; padding: 12px; }
.calendar--range { width: 290px; }
.calendar-head { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
.calendar-head .btn-icon { width: 26px; height: 26px; border-radius: var(--r); }
.calendar-head-title { flex: 1; text-align: center; font: 800 12.5px/1 var(--font); color: var(--ink); }
.calendar-weekdays,
.calendar-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.calendar-weekdays { margin-bottom: 4px; }
.calendar-weekdays span {
  height: 22px;
  display: flex;
  align-items: center;
  justify-content: center;
  font: 700 9.5px/1 var(--mono);
  color: var(--muted-2);
}
.calendar-day {
  height: 30px;
  border: 0;
  border-radius: var(--r);
  background: transparent;
  font: 600 11.5px/1 var(--font);
  color: var(--ink-2);
  cursor: pointer;
  transition: background .15s ease, color .15s ease;
}
.calendar-day:hover { background: var(--accent-soft); }
.calendar-day.is-outside { color: var(--muted-3); } /* nearest: design uses a shade between muted-3 and line-3 */
.calendar-day.is-today { color: var(--accent); font-weight: 800; box-shadow: inset 0 0 0 1px var(--accent-line); }
.calendar-day.is-selected { background: var(--accent); color: var(--paper); font-weight: 800; }
.calendar-day.is-range-start,
.calendar-day.is-range-end { background: var(--info); color: var(--paper); font-weight: 800; }
.calendar-day.is-in-range { background: var(--info-soft); color: var(--info-dark); }

.calendar-presets { display: flex; gap: 6px; margin-bottom: 11px; flex-wrap: wrap; }
.calendar-presets button {
  height: 26px;
  padding: 0 10px;
  border-radius: var(--r);
  border: 1px solid var(--line);
  background: var(--paper);
  font: 700 10.5px/1 var(--font);
  color: var(--ink-3);
  cursor: pointer;
  flex: none;
  transition: border-color var(--t), color var(--t);
}
.calendar-presets button:hover { border-color: var(--info); color: var(--info); }
.calendar-foot {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 11px;
  padding-top: 10px;
  border-top: 1px solid var(--line-2);
}
.calendar-foot > span { font: 500 10.5px/1 var(--mono); color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* ============================== table toolbar =============================== */

.table-card {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-2xl);
  padding: 16px 16px 12px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.table-toolbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; flex: none; }
.table-toolbar-label { font: 600 11.5px/1 var(--font); color: var(--ink-3); white-space: nowrap; }
.table-toolbar-divider { width: 1px; height: 22px; background: var(--line); flex: none; }

.table-sizes { display: flex; gap: 3px; padding: 3px; border-radius: var(--r-lg); background: var(--sunken-2); }
.table-sizes button {
  padding: 5px 10px;
  border: 0;
  border-radius: var(--r-sm);
  cursor: pointer;
  font: 600 11px/1 var(--font);
  color: var(--muted);
  background: transparent;
  transition: background var(--t), color var(--t);
}
.table-sizes button.is-active { font-weight: 700; color: var(--ink); background: var(--paper); box-shadow: var(--sh-1); }

.table-export { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.table-export button {
  height: 30px;
  padding: 0 11px;
  border-radius: var(--r);
  border: 1px solid var(--line);
  background: var(--paper);
  font: 700 11px/1 var(--font);
  color: var(--ink-3);
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 6px;
  flex: none;
  transition: border-color var(--t), color var(--t), background var(--t);
}
.table-export button:hover { border-color: var(--accent-line-strong); color: var(--accent-dark); background: var(--accent-tint); }

.table-search {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 36px;
  padding: 0 12px;
  border-radius: var(--r-lg);
  border: 1px solid var(--line);
  background: var(--sunken);
  flex: none;
  min-width: 210px;
}
.table-search input { flex: 1; min-width: 0; border: 0; background: transparent; outline: none; font: 600 12px/1 var(--font); color: var(--ink); }
.table-search-clear {
  width: 17px;
  height: 17px;
  border-radius: var(--r);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  flex: none;
  color: var(--ink-3);
  transition: background var(--t-fast);
}
.table-search-clear:hover { background: var(--line); }

/* ============================== data table ==================================== */

.data-table-scroll { margin: 0 -4px; overflow-x: auto; }
.data-table { width: 100%; border-collapse: separate; border-spacing: 0; min-width: 640px; table-layout: auto; }
.data-table th {
  text-align: left;
  padding: 0 8px 9px 10px;
  font: 700 9.5px/1 var(--mono);
  color: var(--muted);
  letter-spacing: .09em;
  text-transform: uppercase;
  white-space: nowrap;
  position: sticky;
  top: 0;
  background: var(--paper);
  z-index: 2;
  box-shadow: 0 1px 0 var(--line-2);
}
.data-table td { padding: 11px 8px; border-top: 1px solid var(--line-2); }
.data-table tbody tr { transition: background var(--t-fast); }
.data-table tbody tr:hover { background: var(--sunken); }
.data-table tbody tr.is-selected { background: var(--accent-wash); }

.sort-head { display: inline-flex; align-items: center; gap: 5px; cursor: pointer; color: var(--muted); transition: color var(--t); }
.sort-head:hover { color: var(--ink); }
.sort-head.is-active { color: var(--ink); }
.sort-arrows { flex: none; }
.sort-arrows .sort-up,
.sort-arrows .sort-down { fill: var(--line-3); }
.sort-head.is-asc .sort-up { fill: var(--accent); }
.sort-head.is-desc .sort-down { fill: var(--accent); }

.row-check {
  width: 17px;
  height: 17px;
  border-radius: var(--r-xs);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  border: 1.5px solid var(--muted-3); /* nearest: design uses a shade between muted-3 and line-3 */
  background: var(--paper);
  color: var(--paper);
  transition: all var(--t-fast);
}
.row-check.is-checked { border-color: var(--accent); background: var(--accent); }

.pon-bar { flex: 1; height: 5px; border-radius: var(--r); background: var(--line-4); overflow: hidden; min-width: 40px; }
/* display:block is load-bearing: this is a <span>, and an inline box ignores
   width and height outright -- without it the fill never paints and every bar
   reads as 0% no matter what the real figure is. */
.pon-bar-fill { display: block; height: 100%; border-radius: var(--r); background: linear-gradient(90deg, var(--accent-lift), var(--accent)); }

.row-actions {
  display: flex;
  align-items: center;
  gap: 4px;
  justify-content: flex-end;
}
/* The sticky column is the CELL. `position: sticky` on the inner div does
   nothing -- the td scrolls away and takes the div with it. */
.data-table td.col-actions,
.data-table th.col-actions {
  position: sticky;
  right: 0;
  background: var(--paper);
  box-shadow: -8px 0 10px -8px color-mix(in srgb, var(--ink) 14%, transparent); /* nearest: no token for this directional fade */
}
.data-table tbody tr:hover td.col-actions { background: var(--sunken); }
.row-actions .btn-icon { width: 28px; height: 28px; border-radius: var(--r); }
.row-actions .btn-danger:hover { border-color: var(--crit-line); background: var(--crit-wash); }

.data-table-empty { padding: 34px 12px; text-align: center; font: 600 12px/1.6 var(--font); color: var(--muted-2); }

/* ============================== pager ============================================ */

.table-foot { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding-top: 2px; flex: none; }
.table-info { font: 500 11.5px/1 var(--font); color: var(--muted); white-space: nowrap; }
.table-info-selected {
  font: 700 10px/1 var(--mono);
  color: var(--accent-dark);
  background: var(--accent-soft);
  padding: 5px 8px;
  border-radius: var(--r);
  white-space: nowrap;
}

.pager { display: flex; align-items: center; gap: 4px; }
.pager-btn {
  min-width: 30px;
  height: 30px;
  padding: 0 9px;
  border-radius: var(--r);
  border: 1px solid var(--line);
  background: var(--paper);
  font: 700 11.5px/1 var(--font);
  color: var(--ink-2);
  cursor: pointer;
  flex: none;
  transition: border-color var(--t), color var(--t), background var(--t);
}
.pager-btn:hover { border-color: var(--accent-line-strong); color: var(--accent-dark); background: var(--accent-tint); }
.pager-btn.is-current {
  border-color: var(--accent-dark);
  background: linear-gradient(145deg, var(--accent-grad-a), var(--accent));
  color: var(--paper);
}
.pager-btn.is-disabled,
.pager-btn[disabled] { color: var(--muted-3); cursor: default; } /* nearest: design uses a shade between muted-3 and line-3 */
.pager-btn.is-disabled:hover,
.pager-btn[disabled]:hover { border-color: var(--line); background: var(--paper); color: var(--muted-3); }

/* ============================== empty state ======================================= */

.empty-state {
  border: 1.5px dashed var(--line-3);
  border-radius: var(--r-xl);
  padding: 20px 16px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 9px;
  transition: border-color var(--t-slow), background var(--t-slow);
}
.empty-state:hover { border-color: var(--ok); background: var(--ok-wash); }
.empty-state-icon {
  width: 42px;
  height: 42px;
  border-radius: var(--r-xl);
  background: var(--ok-soft);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--ok);
}
.empty-state-title { font: 700 13px/1 var(--font); color: var(--ink); }
.empty-state-body { font: 500 11.5px/1.5 var(--font); color: var(--muted); text-align: center; max-width: 280px; }
.empty-state-actions { display: flex; gap: 8px; margin-top: 4px; }

/* ============================== skeleton =========================================== */

.skeleton {
  border-radius: var(--r);
  background: linear-gradient(90deg, var(--line-2) 25%, var(--line) 37%, var(--line-2) 63%);
  background-size: 320px 100%;
  animation: oltShimmer 1.3s linear infinite;
  display: block;
}
.skeleton-line { height: 10px; width: 100%; }
.skeleton-avatar { width: 38px; height: 38px; border-radius: var(--r-lg); flex: none; }

/* ============================== modal =============================================== */

.modal {
  position: fixed;
  inset: 0;
  z-index: 230;
  background: color-mix(in srgb, var(--ink) 42%, transparent); /* nearest: no scrim token exists */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  animation: oltFade .18s ease-out;
}
.modal-card {
  position: relative;
  width: 100%;
  max-width: 428px;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-2xl);
  box-shadow: var(--sh-modal);
  padding: 20px;
  display: flex;
  flex-direction: column;
  gap: 15px;
  animation: oltModalIn .2s ease-out;
}
.modal-head { display: flex; align-items: flex-start; gap: 12px; }
.modal-icon {
  width: 38px;
  height: 38px;
  border-radius: var(--r-lg); /* nearest: design uses 12px */
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--tone, var(--accent));
  color: var(--paper);
}
.modal-icon--delete { --tone-a: var(--crit-bright); background: linear-gradient(145deg, var(--crit-bright), var(--crit)); }
.modal-icon--provision { background: linear-gradient(145deg, var(--accent-grad-a), var(--accent)); }
.modal-body { display: flex; flex-direction: column; gap: 6px; min-width: 0; flex: 1; }
.modal-title { font: 800 15px/1.25 var(--font); color: var(--ink); letter-spacing: -.3px; }
.modal-text { font: 500 12.5px/1.55 var(--font); color: var(--ink-3); }
.modal-close {
  width: 26px;
  height: 26px;
  border-radius: var(--r);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  color: var(--ink-3);
  flex: none;
  transition: background var(--t-fast);
}
.modal-close:hover { background: var(--line-2); }
.modal-meta {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 11px 12px;
  border-radius: var(--r-lg);
  background: var(--sunken);
  border: 1px solid var(--line-4);
}
.modal-meta-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--crit); flex: none; }
.modal-meta-text { font: 700 11.5px/1 var(--mono); color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.modal-actions { display: flex; gap: 8px; justify-content: flex-end; }

/* ============================== toast ================================================= */
/* markup mapped onto toastr's DOM by a separate agent; class names below match
   the reference's toast card (spec 1205-1221) */

.toast {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 10px;
  width: 308px;
  padding: 12px 12px 13px;
  border-radius: var(--r-xl);
  background: var(--paper);
  border: 1px solid var(--line);
  box-shadow: var(--sh-toast);
  overflow: hidden;
  animation: oltToastIn .22s ease-out;
}
.toast-icon {
  width: 24px;
  height: 24px;
  border-radius: var(--r-sm);
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--tone, var(--info));
  color: var(--paper);
}
.toast-body { display: flex; flex-direction: column; gap: 4px; min-width: 0; flex: 1; }
.toast-title { font: 800 12.5px/1.2 var(--font); color: var(--ink); }
.toast-msg { font: 500 11.5px/1.45 var(--font); color: var(--ink-3); }
.toast-close {
  width: 20px;
  height: 20px;
  border-radius: var(--r);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  color: var(--muted);
  flex: none;
  transition: background var(--t-fast);
}
.toast-close:hover { background: var(--line-2); }
.toast-bar {
  position: absolute;
  left: 0;
  bottom: 0;
  height: 3px;
  width: 100%;
  transform-origin: left;
  background: var(--tone, var(--info));
  opacity: .5;
  animation: oltToastBar 4.2s linear forwards;
}

.toast--ok { --tone: var(--ok); }
.toast--warn { --tone: var(--warn); }
.toast--crit { --tone: var(--crit); }
.toast--info { --tone: var(--info); }

/* ============================== dropzone =============================================== */

.dropzone {
  border: 1.5px dashed var(--line-3);
  border-radius: var(--r-lg);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 7px;
  padding: 14px;
  cursor: pointer;
  transition: border-color var(--t-slow), background var(--t-slow);
}
.dropzone:hover,
.dropzone.is-active { border-color: var(--ok); background: var(--ok-wash); }
.dropzone-icon {
  width: 34px;
  height: 34px;
  border-radius: var(--r-lg);
  background: var(--ok-soft);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--ok);
}
.dropzone-title { font: 700 12px/1 var(--font); color: var(--ink-2); }
.dropzone-hint { font: 500 10.5px/1 var(--mono); color: var(--muted-2); }

@media (prefers-reduced-motion: reduce) {
  .btn-spinner,
  .skeleton,
  .toast-bar,
  .switch,
  .switch-knob,
  .progress-fill,
  .calendar-day,
  .metric-row,
  .stat-spark path { animation-duration: .01ms !important; }
}


/* ---------------------------------------------------------------------------
 * EMS state / observation components.
 *
 * These classes belong to the component library -- they are emitted by
 * x-monitoring.observation, x-ems.data-state and x-ems.unavailable, which
 * converted pages use. They previously existed ONLY as `.legacy .x` rules in
 * legacy.css, and no view declares @section('legacy'), so <main> never carried
 * the .legacy class and every one of these rendered completely unstyled.
 *
 * Moved here unscoped, declarations unchanged. The legacy.css copies stay for
 * now and are harmless (identical declarations, higher specificity).
 * DEBT: several declarations below still hard-code hex values, which the design
 * spec forbids. Map them to :root tokens in a separate pass -- not while views
 * are being converted.
 * ------------------------------------------------------------------------- */
.collection-banner {
    margin-bottom: 18px;
    padding: 19px 21px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 18px;
    border: 1px solid var(--line);
    border-left-width: 4px;
    border-radius: 12px;
    background: #fff;
}
.collection-banner h2 { margin-top: 8px; font-size: 16px;
}
.collection-banner p { margin: 5px 0 0; color: var(--muted); font-size: 11px;
}
.collection-fresh { border-left-color: var(--success);
}
.collection-stale, .collection-partial { border-left-color: #e2a62a;
}
.collection-error { border-left-color: var(--danger);
}
.collection-never { border-left-color: #a3a8b7;
}
.critical-age { color: var(--danger); font-size: 11px;
}
.observation-grid {
    padding: 21px;
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 1px;
    background: #eff0f5;
}
.observation-grid-compact { grid-template-columns: repeat(2, minmax(0, 1fr));
}
.observation {
    min-height: 92px;
    padding: 15px;
    display: grid;
    align-content: start;
    gap: 7px;
    background: #fff;
}
.observation > span { color: var(--muted); font-size: 10px; font-weight: 700;
}
.observation > strong { overflow-wrap: anywhere; font-size: 13px;
}
.observation > strong small { color: var(--muted); font-size: 9px;
}
.observation-missing { color: #8d93a5; font-weight: 600;
}
.observation-meta { color: #9aa0b1; font-size: 9px;
}
.danger-text { color: var(--danger);
}
.observation-grid { grid-template-columns: repeat(2, minmax(0, 1fr));
}
.collection-banner { align-items: flex-start; flex-direction: column;
}
.observation-grid, .observation-grid-compact { grid-template-columns: 1fr;
}
.ems-breadcrumb a { color: inherit; text-decoration: none;
}
.ems-breadcrumb a:hover { color: var(--accent, #2563eb); text-decoration: underline;
}
.ems-breadcrumb strong { color: var(--text, #0f172a); font-weight: 600;
}
.ems-breadcrumb span { opacity: 0.5;
}
.ems-state i {
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 50%;
    background: currentColor;
    flex: none;
}
.ems-state-live { color: #047857; background: #ecfdf5; border-color: #a7f3d0;
}
.ems-state-stale { color: #b45309; background: #fffbeb; border-color: #fde68a;
}
.ems-state-offline { color: #b91c1c; background: #fef2f2; border-color: #fecaca;
}
.ems-state-unknown { color: #475569; background: #f1f5f9; border-color: #e2e8f0;
}
.ems-unavailable.is-compact { padding: 0.6rem 0.75rem;
}
.ems-unavailable-mark {
    font-size: 1.35rem;
    line-height: 1;
    color: #94a3b8;
    font-weight: 700;
}
.ems-unavailable strong { display: block; font-size: 0.92rem; color: var(--text, #0f172a);
}
.ems-unavailable p { margin: 0.3rem 0 0; font-size: 0.84rem; color: var(--muted, #64748b);
}
.ems-metric {
    padding: 0.9rem 1rem;
    border: 1px solid var(--border, #e2e8f0);
    border-radius: 0.7rem;
    background: #fff;
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
}
.ems-metric > span { font-size: 0.78rem; color: var(--muted, #64748b); font-weight: 600;
}
.ems-metric > strong { font-size: 1.6rem; line-height: 1.15; font-variant-numeric: tabular-nums;
}
.ems-metric > small { font-size: 0.76rem; color: var(--muted, #64748b);
}
.ems-metric-ok { border-left: 3px solid #10b981;
}
.ems-metric-warn { border-left: 3px solid #f59e0b;
}
.ems-metric-alert { border-left: 3px solid #ef4444;
}
.ems-metric.is-unwired { background: #f8fafc;
}
.ems-metric.is-unwired > strong { color: #94a3b8;
}
.ems-note {
    margin: -0.6rem 0 1.2rem;
    font-size: 0.85rem;
    color: var(--muted, #64748b);
}
.ems-detail-identity h1 { margin: 0 0 0.25rem;
}
.ems-detail-state { display: flex; flex-direction: column; align-items: flex-end; gap: 0.3rem;
}
.ems-detail-state small { color: var(--muted, #64748b); font-size: 0.8rem;
}
.ems-pon-card {
    display: flex;
    flex-direction: column;
    gap: 0.55rem;
    padding: 0.9rem 1rem;
    border: 1px solid var(--border, #e2e8f0);
    border-radius: 0.7rem;
    background: #fff;
    text-decoration: none;
    color: inherit;
    transition: border-color 120ms ease, box-shadow 120ms ease;
}
.ems-pon-card:hover { border-color: var(--accent, #2563eb); box-shadow: 0 1px 8px rgb(15 23 42 / 0.08);
}
.ems-pon-card.has-alarm { border-left: 3px solid #ef4444;
}
.ems-pon-card-head { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
}
.ems-pon-card-head strong { font-size: 1rem;
}
.ems-pon-card-counts { display: flex; flex-wrap: wrap; gap: 0.75rem; font-size: 0.8rem; color: var(--muted, #64748b);
}
.ems-pon-card-counts strong { font-variant-numeric: tabular-nums; color: var(--text, #0f172a);
}
.ems-pon-card-counts .is-online strong { color: #047857;
}
.ems-pon-card-counts .is-offline strong { color: #b91c1c;
}
.ems-pon-card-foot { font-size: 0.74rem; color: #94a3b8;
}
.ems-event-list li {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.6rem 0;
    border-bottom: 1px solid var(--border, #e2e8f0);
}
.ems-event-list li:last-child { border-bottom: 0;
}
.ems-event-list li > div { flex: 1; min-width: 0;
}
.ems-event-list strong { display: block; font-size: 0.88rem;
}
.ems-event-list small { display: block; font-size: 0.78rem; color: var(--muted, #64748b);
}
.ems-event-state { font-size: 0.78rem; color: var(--muted, #64748b); white-space: nowrap;
}
.ems-actions .button[disabled] { opacity: 0.55; cursor: not-allowed;
}
.ems-actions-note { margin: 0.7rem 0 0; font-size: 0.8rem; color: var(--muted, #64748b);
}
.ems-paused { display: block; font-size: 0.72rem; color: #b45309;
}
/* The `.ems-table` rules that used to sit here styled a different table --
   a wide EMS event log with a sticky .ems-col-identity column. Nothing
   renders that markup any more (no view references ems-col-identity,
   ems-event-state, ems-metric-row or ems-paused), but the rules still
   matched the OLT detail tables, which share the class name and are styled
   in ems.css. The leftovers they contributed were `min-width: 48rem` and
   `white-space: nowrap`, which forced a three-column ARP table to 768px
   inside a 566px card and made it scroll sideways forever. One owner per
   class: .ems-table is ems.css's. */
.ems-metric-row { grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
}
.ems-metric > strong { font-size: 1.3rem;
}
.ems-detail-state { align-items: flex-start;
}
.card-note { margin: 10px 0 0; font-size: 11px; color: var(--muted-2);
}
.ems-state i { width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: 0 0 auto;
}
.ems-state-live { color: var(--ok); background: var(--ok-soft); border-color: #cbe7d9;
}
.ems-state-stale { color: var(--warn); background: var(--warn-soft); border-color: #f0dcb4;
}
.ems-state-offline { color: var(--crit); background: var(--crit-soft); border-color: #f4cfcf;
}
.ems-state-unknown { color: var(--unknown); background: var(--unknown-soft); border-color: #dfe3ea;
}
.ems-state-unknown i { background: transparent; box-shadow: inset 0 0 0 1.5px currentColor;
}
.ems-unavailable::before { content: "—"; color: var(--muted-2); font-weight: 700; line-height: 1.3;
}
.ems-unavailable strong { display: block; color: var(--ink-soft); font-weight: 620; font-size: 12.5px; margin-bottom: 1px;
}
.ems-unavailable p { margin: 0;
}

/* Nav submenus size to their own content and are positioned by ui/dropdown.js.
 * The base rule's `right: 0` makes a panel span its relative parent, which is
 * correct for a select-style panel that must match its trigger's width — but a
 * nav item's wrap is only as wide as its label, so keeping `right: 0` there
 * fought the JS-computed `left` and collapsed the panel to a sliver. */
.app-nav .dropdown-panel {
  /* fixed, not absolute: .app-nav-list is an overflow:auto scroller and would
     otherwise clip this panel to the 49px nav strip. ui/dropdown.js writes the
     viewport coordinates and re-measures on scroll/resize. */
  position: fixed;
  top: auto;
  right: auto;
  min-width: 210px;
}

/* Header menus hang off the RIGHT edge of their trigger. With the base rule's
 * `left: 0` they grow rightwards instead and the widest of them (the profile
 * card, which carries an email address) runs past the viewport edge. */
.app-header .dropdown-panel {
  left: auto;
  right: 0;
}

/* Segmented filter strip (OLT health tabs). Same shape as .table-sizes — the
 * design's "Show 10 / 25 / 50" control — but built from anchors, because each
 * tab is a real URL that must survive a refresh and be shareable. */
.filter-tabs {
  display: inline-flex;
  gap: 4px;
  padding: 4px;
  border-radius: var(--r-xl);
  background: var(--line-2);
  border: 1px solid var(--line);
  align-self: flex-start;
  margin-bottom: 16px;
}
.filter-tabs a {
  padding: 6px 14px;
  border-radius: var(--r-md);
  font: 600 12px/1 var(--font);
  color: var(--muted);
  text-decoration: none;
  white-space: nowrap;
  transition: all var(--t);
}
.filter-tabs a:hover { color: var(--ink); background: rgba(255, 255, 255, 0.6); }
.filter-tabs a.is-active {
  font-weight: 700;
  color: var(--accent-dark);
  background: var(--paper);
  box-shadow: var(--sh-1);
}

/* Device cell: icon tile + two-line stack. The tile is what stops the first
   column reading as a bare link. */
.device-cell { display: flex; align-items: center; gap: 10px; min-width: 0; }
.device-icon {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--accent-soft);
  color: var(--accent-dark);
  display: flex;
  align-items: center;
  justify-content: center;
  font: 700 12px/1 var(--font);
  flex: none;
}
.device-copy { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.device-copy a { text-decoration: none; }
.device-copy strong { font: 600 12.5px/1.3 var(--font); color: var(--ink); white-space: nowrap; }
.device-copy a:hover strong { color: var(--accent-dark); }
.device-meta { font: 500 11px/1.2 var(--font); color: var(--muted); white-space: nowrap; }

/* Usage cell: the bar sizes against this row, the count stays put beside it. */
.usage-cell { display: flex; align-items: center; gap: 7px; min-width: 78px; }
.usage-count { font: 700 10.5px/1 var(--mono); color: var(--ink-3); white-space: nowrap; flex: none; }

/* Mono is the house style for machine-read values in a row: zones, serials,
   timestamps, counts. */
.cell-mono { font: 500 11px/1.2 var(--mono); color: var(--ink-3); white-space: nowrap; }
.cell-figure { font: 700 11.5px/1 var(--mono); color: var(--ink); }

/* ===========================================================================
 * NOC drill-down surfaces (migrated unscoped from legacy.css).
 *
 * The OLT / PON / ONU detail screens (olts.show, pons.show, onus.show) are
 * network-operations surfaces: tighter density than the CRUD screens, one
 * badge system for state, faceplate-style PON ports, KPI strips. These
 * classes previously lived ONLY under `.legacy` in legacy.css, and the views
 * that use them do not declare the legacy section -- so every one rendered
 * unstyled. Moved here so the drill-down pages read the same theme as the
 * rest of the product. onus.show still loads legacy.css; its higher-specificity
 * `.legacy .x` copies keep winning there.
 * ========================================================================= */

/* Cards on drill-down pages: flush header/body so the head rule and border
   read as one NowaCRM card, and stacked cards keep a rhythm. Raw <section
   class="card"> blocks (olts.show) carry an <h2> in their head; <x-ui.card>
   instances never do, so the two layouts never collide. */
.app-main > .card + .card { margin-top: 14px; }

.app-main .card:has(> .card-head h2) {
  padding: 0;
  gap: 0;
  overflow: hidden;
}
.app-main .card:has(> .card-head h2) > .card-head {
  padding: 12px 16px;
  border-bottom: 1px solid var(--line-2);
  justify-content: space-between;
  gap: 12px;
}
.app-main .card:has(> .card-head h2) > .card-head h2 {
  font: 800 13.5px/1 var(--font);
  color: var(--ink);
  letter-spacing: -.2px;
}
.app-main .card:has(> .card-head h2) > .card-head .sub { font: 500 11.5px/1 var(--font); color: var(--muted); }
.app-main .card:has(> .card-head h2) > .card-body { padding: 14px 16px; }

/* Breadcrumb: the OLT -> PON -> ONU path stays visible on every drill-down. */
.noc-crumb {
  display: flex;
  align-items: center;
  gap: 7px;
  flex-wrap: wrap;
  margin-bottom: 12px;
  font: 500 12px/1 var(--font);
  color: var(--muted);
}
.noc-crumb a { color: var(--muted); }
.noc-crumb a:hover { color: var(--accent); }
.noc-crumb strong { color: var(--ink); font-weight: 700; }
.noc-crumb .sep { color: var(--line); }

/* State badges: one shape for telemetry freshness, ONU session state and
   alarm severity -- an operator learns to read state once. */
.st {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 7px 2px 6px;
  border-radius: var(--r-xs);
  font: 700 10.5px/1.5 var(--font);
  letter-spacing: .045em;
  text-transform: uppercase;
  white-space: nowrap;
  border: 1px solid transparent;
}
.st::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  flex: 0 0 auto;
}
.st-unknown::before,
.st-down::before { background: transparent; box-shadow: inset 0 0 0 1.5px currentColor; }

.st-live,
.st-online { color: var(--ok); background: var(--ok-soft); border-color: var(--ok-line); }
.st-stale,
.st-minor,
.st-warning,
.st-registering { color: var(--warn); background: var(--warn-soft); border-color: var(--warn-line); }
.st-offline,
.st-critical,
.st-down { color: var(--crit); background: var(--crit-soft); border-color: var(--crit-line); }
.st-major { color: var(--warn-bright); background: var(--warn-soft); border-color: var(--warn-line); }
.st-unknown { color: var(--muted-3); background: var(--sunken-2); border-color: var(--line); }
.st-info { color: var(--accent); background: var(--accent-soft); border-color: var(--accent-line); }

/* Bare dot for tables where the word would cost a column. */
.st-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; flex: 0 0 auto; }
.st-dot-live,
.st-dot-online { background: var(--ok); }
.st-dot-stale,
.st-dot-registering { background: var(--warn); }
.st-dot-offline,
.st-dot-down { background: var(--crit); }
.st-dot-unknown { background: transparent; box-shadow: inset 0 0 0 1.5px var(--muted-3); }

/* Identity strip: everything an operator needs to confirm they are on the
   right device, in one line. */
.ident {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  padding: 12px 16px;
  margin-bottom: 14px;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-2xl);
  box-shadow: var(--sh-2);
}
.ident-main { display: flex; align-items: center; gap: 10px; min-width: 0; }
.ident-main h1 { margin: 0; font: 800 17px/1 var(--font); color: var(--ink); letter-spacing: -.015em; }
.ident-facts { display: flex; gap: 18px; flex-wrap: wrap; margin-left: auto; }
.ident-fact { display: flex; flex-direction: column; gap: 2px; }
.label {
  font: 700 9.5px/1 var(--mono);
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--muted-2);
}
.ident-fact .val { font: 600 12.5px/1.2 var(--font); color: var(--ink); }
.mono { font-family: var(--mono); }
.ident-fact .val.mono { font-size: 11.5px; font-weight: 500; }
.ident-actions { display: flex; gap: 8px; align-items: center; }

/* KPI strip: density is the point -- many figures visible at once. */
.kpi-row {
  display: grid;
  gap: 10px;
  margin-bottom: 14px;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
.kpi {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-2xl);
  padding: 11px 13px 12px;
  display: flex;
  flex-direction: column;
  gap: 5px;
  min-width: 0;
  box-shadow: var(--sh-2);
}
.kpi-label { font: 700 10.5px/1 var(--mono); letter-spacing: .07em; text-transform: uppercase; color: var(--muted-2); }
.kpi-value {
  font: 640 25px/1.05 var(--font);
  letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.kpi-value.is-zero { color: var(--muted-2); }
.kpi-foot { font: 500 11px/1 var(--font); color: var(--muted); }
.kpi-unit { font-size: 13px; font-weight: 500; color: var(--muted-2); }
/* A left rule tints the tile by severity without flooding it with colour. */
.kpi-crit { border-left: 3px solid var(--crit); }
.kpi-crit .kpi-value { color: var(--crit); }
.kpi-warn { border-left: 3px solid var(--warn); }
.kpi-warn .kpi-value { color: var(--warn); }
.kpi-ok { border-left: 3px solid var(--ok); }
.kpi-quiet { border-left: 3px solid var(--line); }

/* PON port map: ports drawn as faceplate sockets -- bezel with the port
   number, status LED, and an occupancy bar showing how much is lit. */
.port-map {
  display: grid;
  gap: 10px;
  grid-template-columns: repeat(auto-fill, minmax(212px, 1fr));
}
.port {
  display: block;
  position: relative;
  padding: 11px 12px 12px;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-2xl);
  box-shadow: var(--sh-1);
  transition: border-color var(--t-fast), box-shadow var(--t-fast);
}
.port:hover { border-color: var(--accent); box-shadow: var(--sh-3); }
.port:focus-visible { outline: 2px solid rgba(99, 102, 241, .4); outline-offset: 2px; }
.port-top { display: flex; align-items: center; gap: 9px; }
.port-bezel {
  width: 38px;
  height: 30px;
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  border-radius: var(--r-xs);
  background: #1b2436;
  color: #dfe6f2;
  font: 700 13px/1 var(--font);
  font-variant-numeric: tabular-nums;
  box-shadow: inset 0 0 0 1px #2c3548, inset 0 1px 0 rgba(255, 255, 255, .10);
}
.port-id { font: 700 11px/1 var(--mono); letter-spacing: .06em; text-transform: uppercase; color: var(--muted-2); }
.port-name { font: 650 13px/1 var(--font); color: var(--ink); }
.port-counts {
  display: flex;
  gap: 14px;
  margin-top: 10px;
  font: 500 11.5px/1 var(--font);
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.port-counts b { color: var(--ink); font-weight: 650; }
.port-bar {
  margin-top: 9px;
  height: 4px;
  border-radius: 2px;
  background: var(--line-2);
  overflow: hidden;
  display: flex;
}
.port-bar i { display: block; height: 100%; }
.port-bar .on { background: var(--ok); }
.port-bar .off { background: var(--crit); opacity: .55; }
.port-down .port-bezel { background: #2a2f38; color: #7c8698; }
.port-down { background: var(--sunken); }

/* Filters row on drill-down tables. */
.filters { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }
.filter-search {
  display: flex;
  align-items: center;
  gap: 7px;
  flex: 1 1 200px;
  max-width: 320px;
  padding: 0 10px;
  height: 32px;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  color: var(--muted-2);
}
.filter-search input {
  border: 0;
  outline: 0;
  background: transparent;
  font: 500 12.5px/1 var(--font);
  width: 100%;
  color: var(--ink);
}

/* Small native select used beside filters (styled like a themed control). */
.select-sm {
  height: 32px;
  padding: 0 26px 0 9px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--paper);
  font: 600 12.5px/1 var(--font);
  color: var(--ink);
  appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, var(--muted-2) 50%),
                    linear-gradient(135deg, var(--muted-2) 50%, transparent 50%);
  background-position: calc(100% - 14px) 14px, calc(100% - 9px) 14px;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}
.select-sm:focus { border-color: var(--accent); outline: none; box-shadow: 0 0 0 3px var(--accent-ring); }

/* The `.button / .button-ghost / .button-danger / .button-small` set that
   used to live here was a FOURTH button spelling, alongside Bootstrap's
   .btn-*, the old .btn--*, and <x-ui.button>. Its own comment called it
   legacy "until those call sites convert to x-ui.button" -- they have, so
   it is gone. Use <x-ui.button variant size> for every button. */

.text-link { color: var(--accent-dark); font: 700 12.5px/1 var(--font); white-space: nowrap; }
.text-link:hover { text-decoration: underline; color: var(--accent-deep); }

.empty { padding: 22px 14px; text-align: center; color: var(--muted); font-size: 12.5px; }

.danger-zone {
  margin-top: 24px;
  padding: 19px 21px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  border: 1px solid var(--crit-line);
  border-radius: var(--r-2xl);
  background: var(--crit-soft);
}
.danger-zone strong { color: var(--crit-dark); font: 800 13px/1 var(--font); }
.danger-zone p { margin: 4px 0 0; color: var(--crit); font: 500 11px/1.4 var(--font); }

@media (max-width: 620px) {
  .danger-zone { align-items: flex-start; flex-direction: column; }
  .danger-zone form,
  .danger-zone .button { width: 100%; }
  .ident-facts { margin-left: 0; }
}
