/* РемонтБот web panel — Stage 5.1. No external fonts/CDN: system font stack only. */

:root {
  --font-sans: Inter, "Segoe UI", Arial, sans-serif;

  --sidebar-bg: #1a2332;
  --sidebar-bg-hover: #232f42;
  --sidebar-text: #b7c2d0;
  --sidebar-text-active: #ffffff;
  --sidebar-border: #2a3648;

  --bg: #eef1f6;
  --card-bg: #ffffff;
  --border: #e2e6ee;
  --border-strong: #d3d9e3;
  --text: #1b2430;
  --text-muted: #6b7688;
  --text-faint: #97a1b0;

  --accent-blue: #2f6fed;
  --accent-blue-soft: #eaf1fe;
  --accent-red: #e0393e;
  --accent-red-soft: #fdeced;
  --accent-green: #22a35a;
  --accent-green-soft: #e9f8ef;
  --accent-orange: #f2994a;
  --accent-orange-soft: #fdf1e6;

  --radius: 10px;
  --radius-sm: 6px;
  --shadow-card: 0 1px 2px rgba(16, 24, 40, 0.04), 0 1px 3px rgba(16, 24, 40, 0.06);
  --shadow-popover: 0 8px 24px rgba(16, 24, 40, 0.16);
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: 14px;
  line-height: 1.45;
}

a { color: inherit; text-decoration: none; }

table { border-collapse: collapse; width: 100%; }

/* ---------------------------------------------------------------------- */
/* App shell                                                              */
/* ---------------------------------------------------------------------- */

.app-shell {
  display: flex;
  min-height: 100vh;
}

.sidebar {
  width: 232px;
  flex: 0 0 232px;
  background: var(--sidebar-bg);
  display: flex;
  flex-direction: column;
  padding: 20px 14px;
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
}

.sidebar-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 4px 8px 20px;
  margin-bottom: 12px;
  border-bottom: 1px solid var(--sidebar-border);
}

.sidebar-brand .brand-icon {
  width: 30px;
  height: 30px;
  flex: none;
  color: var(--accent-blue);
}
.sidebar-brand .brand-icon svg { width: 100%; height: 100%; }

.brand-title { color: #fff; font-weight: 700; font-size: 16px; }
.brand-subtitle { color: var(--sidebar-text); font-size: 11.5px; }

.sidebar-nav { display: flex; flex-direction: column; gap: 2px; flex: 1; }

.nav-item {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  color: var(--sidebar-text);
  font-size: 13.5px;
  font-weight: 500;
  transition: background-color .12s ease, color .12s ease;
}
.nav-item:hover { background: var(--sidebar-bg-hover); color: var(--sidebar-text-active); }
.nav-item.active { background: var(--accent-blue); color: #fff; }
.nav-icon { width: 18px; height: 18px; flex: none; }
.nav-icon svg { width: 100%; height: 100%; }

.app-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }

.topbar {
  display: flex;
  align-items: center;
  height: 60px;
  padding: 0 24px;
  background: var(--card-bg);
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  z-index: 20;
}
.topbar-spacer { flex: 1; }

.mobile-nav-toggle {
  display: none;
  border: none;
  background: none;
  width: 32px; height: 32px;
  color: var(--text);
  cursor: pointer;
}
.mobile-nav-toggle svg { width: 100%; height: 100%; }

.topbar-user { display: flex; align-items: center; gap: 10px; }
.topbar-user-icon {
  width: 34px; height: 34px; border-radius: 50%;
  background: var(--accent-blue-soft); color: var(--accent-blue);
  display: flex; align-items: center; justify-content: center; padding: 7px;
}
.topbar-user-icon svg { width: 100%; height: 100%; }
.topbar-user-name { font-weight: 600; font-size: 13.5px; }
.topbar-user-role { font-size: 11.5px; color: var(--text-muted); }

.logout-form { margin: 0 0 0 6px; }
.logout-button {
  border: none; background: none; cursor: pointer; color: var(--text-muted);
  width: 30px; height: 30px; border-radius: var(--radius-sm);
  display: flex; align-items: center; justify-content: center;
}
.logout-button svg { width: 18px; height: 18px; }
.logout-button:hover { background: var(--accent-red-soft); color: var(--accent-red); }

.content { padding: 22px 26px 40px; max-width: 1440px; width: 100%; margin: 0 auto; }

/* ---------------------------------------------------------------------- */
/* Cards / generic components                                             */
/* ---------------------------------------------------------------------- */

.card {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  padding: 18px;
}

.card-title {
  font-size: 14.5px;
  font-weight: 600;
  margin: 0 0 14px;
  display: flex;
  align-items: center;
  gap: 8px;
}
.card-title-icon { width: 17px; height: 17px; color: var(--accent-orange); flex: none; }
.card-title-icon svg { width: 100%; height: 100%; }

.page-header { margin-bottom: 18px; }
.page-title { font-size: 22px; font-weight: 700; margin: 0 0 2px; }
.page-subtitle { color: var(--text-muted); margin: 0; font-size: 13px; }

.text-muted { color: var(--text-muted); }

.btn {
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--radius-sm); padding: 9px 16px; font-size: 13.5px;
  font-weight: 600; border: 1px solid var(--border-strong); cursor: pointer;
  background: var(--card-bg); color: var(--text);
  /* .btn is used on both <a> and <button>; without this a <button> falls
     back to the UA's own control font instead of the app's stack. */
  font-family: inherit;
}
.btn:hover { background: var(--bg); }
.btn-primary { background: var(--accent-blue); border-color: var(--accent-blue); color: #fff; }
.btn-primary:hover { background: #255fd1; }
/* Destructive actions (Stage 5.6 "Удалить") — the one place on the site a
   button is red by default rather than only on a hover/error state. */
.btn-danger { background: var(--accent-red); border-color: var(--accent-red); color: #fff; }
.btn-danger:hover { background: #c62828; }
.btn-small { padding: 6px 12px; font-size: 12.5px; }

.placeholder-card { text-align: center; padding: 44px 24px; }
.placeholder-icon { width: 40px; height: 40px; margin: 0 auto 14px; color: var(--text-faint); }
.placeholder-icon svg { width: 100%; height: 100%; }
.placeholder-text { color: var(--text-muted); margin: 0 0 6px; }
.placeholder-query { font-size: 12.5px; color: var(--text-faint); margin: 0 0 18px; }
.placeholder-query code { background: var(--bg); padding: 2px 6px; border-radius: 4px; }

.empty-state-card { text-align: center; padding: 22px; margin-bottom: 18px; }

/* ---------------------------------------------------------------------- */
/* Period selector                                                        */
/* ---------------------------------------------------------------------- */

.dashboard-header {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: flex-end;
  gap: 14px;
}

.period-bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

.period-range-display, .period-btn {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 8px 14px; border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong); background: var(--card-bg);
  font-size: 13px; font-weight: 500; color: var(--text);
  list-style: none; font-family: inherit;
}
.period-range-display svg { width: 15px; height: 15px; color: var(--text-muted); }

/* .period-range-display is now a plain, non-interactive label (just shows
   the active range) — the "Произвольный период" button below is the only
   thing that opens the date picker (see .period-custom-wrap). */
.period-btn { cursor: pointer; }
.period-btn:hover { background: var(--bg); }
.period-btn:focus-visible { outline: 2px solid var(--accent-blue); outline-offset: 1px; }
.period-btn.active { background: var(--accent-blue); border-color: var(--accent-blue); color: #fff; }

.period-custom-wrap { position: relative; }
.period-custom-form {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 30;
  background: var(--card-bg); border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow-popover); padding: 14px; display: flex; flex-direction: column; gap: 10px;
  width: 230px;
}
.period-custom-form[hidden] { display: none; }
.period-custom-form label { display: flex; flex-direction: column; gap: 4px; font-size: 12.5px; color: var(--text-muted); }
.period-custom-form input[type="date"] {
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm); padding: 7px 8px; font-family: inherit; font-size: 13px;
}
.period-custom-actions { display: flex; gap: 8px; }
.period-custom-actions .btn { flex: 1; }

/* ---------------------------------------------------------------------- */
/* KPI cards                                                              */
/* ---------------------------------------------------------------------- */

.kpi-grid {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 14px;
  margin-bottom: 18px;
}

.kpi-card {
  display: flex; align-items: flex-start; gap: 12px;
  cursor: pointer; transition: box-shadow .12s ease, border-color .12s ease;
}
a.kpi-card:hover, .kpi-popover-wrap:hover, .kpi-popover-wrap:focus-within {
  box-shadow: 0 4px 14px rgba(16, 24, 40, 0.08); border-color: var(--border-strong);
}
.kpi-card-static { cursor: default; }
.kpi-card-static:hover { box-shadow: var(--shadow-card); }

/* "Сейчас неисправно" is a <button> filling the whole card (not
   <details>/<summary>, whose default clickable box didn't match the
   card's full hover area) so the entire card is one logical, keyboard-
   accessible control: click/Enter/Space anywhere on it toggles the popover. */
.kpi-popover-wrap { position: relative; display: flex; align-items: stretch; }
.kpi-trigger {
  display: flex; align-items: flex-start; gap: 12px; flex: 1;
  background: none; border: none; margin: 0; padding: 0;
  font: inherit; text-align: left; color: inherit; cursor: pointer;
}
.kpi-trigger:focus-visible {
  outline: 2px solid var(--accent-blue); outline-offset: 2px; border-radius: var(--radius-sm);
}

.kpi-icon {
  width: 40px; height: 40px; border-radius: 10px; flex: none;
  display: flex; align-items: center; justify-content: center; padding: 9px;
}
.kpi-icon svg { width: 100%; height: 100%; }
.kpi-icon-blue { background: var(--accent-blue-soft); color: var(--accent-blue); }
.kpi-icon-red { background: var(--accent-red-soft); color: var(--accent-red); }

.kpi-body { min-width: 0; flex: 1; }
.kpi-value { font-size: 22px; font-weight: 700; line-height: 1.1; }
.kpi-label { font-size: 12.5px; color: var(--text-muted); margin-top: 2px; }
.kpi-secondary { font-size: 12px; color: var(--text-faint); margin-top: 6px; }

.kpi-comparison { font-size: 12.5px; font-weight: 600; margin-top: 8px; }
.kpi-sentiment-good { color: var(--accent-green); }
.kpi-sentiment-bad { color: var(--accent-red); }
.kpi-sentiment-neutral { color: var(--text-faint); font-weight: 500; }

.kpi-link {
  display: inline-flex; align-items: center; gap: 2px; margin-top: 8px;
  color: var(--accent-blue); font-size: 12.5px; font-weight: 600;
}
.kpi-chevron { width: 13px; height: 13px; }
.kpi-chevron svg { width: 100%; height: 100%; }

.kpi-progress { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
.kpi-progress-track { flex: 1; height: 6px; border-radius: 3px; background: var(--border); overflow: hidden; }
.kpi-progress-fill { height: 100%; background: var(--accent-blue); border-radius: 3px; }
.kpi-progress-pct { font-size: 12px; color: var(--text-muted); font-weight: 600; }

.kpi-popover {
  position: absolute; top: calc(100% + 8px); left: 0; z-index: 30;
  width: 330px; max-width: min(330px, 84vw);
  background: var(--card-bg); border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow-popover); padding: 8px; max-height: 360px; overflow-y: auto;
}
.kpi-popover[hidden] { display: none; }
.popover-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.popover-item-link { display: block; padding: 9px 10px; border-radius: var(--radius-sm); }
.popover-item-link:hover { background: var(--bg); }
.popover-item-title { font-weight: 600; font-size: 13px; display: flex; gap: 6px; align-items: baseline; }
.popover-badge { font-size: 11px; color: var(--text-faint); font-weight: 500; }
.popover-item-meta { font-size: 12px; color: var(--text-muted); margin-top: 2px; }
.popover-item-downtime { font-size: 12px; color: var(--accent-red); margin-top: 2px; font-weight: 500; }
.popover-footer-link {
  display: block; text-align: center; padding: 10px; margin-top: 4px;
  border-top: 1px solid var(--border); color: var(--accent-blue); font-weight: 600; font-size: 12.5px;
}
.popover-empty { padding: 18px 10px; text-align: center; color: var(--text-muted); }

/* ---------------------------------------------------------------------- */
/* Charts                                                                  */
/* ---------------------------------------------------------------------- */

/* Three EQUAL columns, deliberately the same track definition as
   .rankings-grid below (post-5.5 UI/UX polish): the Dashboard's chart row
   and the ranking row underneath it used to use different ratios
   (1.3/1.3/1 vs. 1/1/1), so their card edges never lined up. They now
   share one grid, and follow the same breakpoints (see Responsive). */
.chart-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
  margin-bottom: 18px;
  align-items: stretch;
}
.chart-card { display: flex; flex-direction: column; }
.chart-svg { width: 100%; height: auto; overflow: visible; }
.chart-empty { color: var(--text-faint); font-size: 13px; padding: 40px 0; text-align: center; }

.chart-gridline { stroke: var(--border); stroke-width: 1; }

/* Chart text sizes are in viewBox user units, not screen pixels — see
   app/web/charts.py for why each page asks for a viewBox roughly as wide
   as the card it renders into (render scale ~1:1, so these numbers are
   very close to the on-screen px they produce).

   Hierarchy is intentional (post-5.5 UI/UX polish): the DATA VALUE label
   is the loudest thing on the chart (14px/700 + a card-coloured halo so
   it stays legible where it crosses a gridline or the trend line), while
   the axis ticks stay quiet at 10px/regular. */
.chart-axis-label { fill: var(--text-faint); font-size: 10px; font-family: var(--font-sans); }
.chart-x-label { fill: var(--text-faint); font-size: 10px; font-family: var(--font-sans); text-anchor: middle; }
.chart-value-label {
  fill: var(--text); font-size: 14px; font-weight: 700; font-family: var(--font-sans);
  paint-order: stroke; stroke: var(--card-bg); stroke-width: 3px; stroke-linejoin: round;
}

.chart-line { fill: none; stroke: var(--accent-blue); stroke-width: 2.5; stroke-linejoin: round; stroke-linecap: round; }
.chart-point { fill: var(--card-bg); stroke: var(--accent-blue); stroke-width: 2.5; }
.chart-bar { fill: var(--accent-blue); opacity: 0.85; }
.chart-bar:hover { opacity: 1; }

.donut-wrap { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; justify-content: center; }
.donut-svg { width: 150px; height: 150px; flex: none; }
.donut-track { fill: none; stroke: var(--border); stroke-width: 18; }
.donut-segment { fill: none; stroke-width: 18; transform: rotate(-90deg); transform-origin: 50% 50%; }
.donut-center-value { font-size: 22px; font-weight: 700; fill: var(--text); font-family: var(--font-sans); }
.donut-center-label { font-size: 10px; fill: var(--text-muted); font-family: var(--font-sans); }

.donut-segment.accent-blue { stroke: var(--accent-blue); }
.donut-segment.accent-red { stroke: var(--accent-red); }
.donut-segment.accent-green { stroke: var(--accent-green); }
.donut-segment.accent-orange { stroke: var(--accent-orange); }

.donut-legend { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; min-width: 140px; }
.donut-legend-item { display: flex; align-items: center; gap: 8px; padding: 3px 4px; border-radius: var(--radius-sm); }
.donut-legend-item:hover { background: var(--bg); }
.donut-legend-dot { width: 9px; height: 9px; border-radius: 50%; flex: none; }
.donut-legend-dot.accent-blue { background: var(--accent-blue); }
.donut-legend-dot.accent-red { background: var(--accent-red); }
.donut-legend-dot.accent-green { background: var(--accent-green); }
.donut-legend-dot.accent-orange { background: var(--accent-orange); }
.donut-legend-label { flex: 1; font-size: 12.5px; }
.donut-legend-pct { font-size: 12.5px; font-weight: 700; }

/* ---------------------------------------------------------------------- */
/* Rankings                                                                */
/* ---------------------------------------------------------------------- */

.rankings-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; }
.ranking-card { overflow-x: auto; }
.ranking-table { font-size: 13px; }
.ranking-table th {
  text-align: left; color: var(--text-faint); font-weight: 600; font-size: 11.5px;
  text-transform: uppercase; letter-spacing: .03em; padding: 0 8px 8px; border-bottom: 1px solid var(--border);
}
.ranking-table td { padding: 9px 8px; border-bottom: 1px solid var(--border); }
.ranking-table tr:last-child td { border-bottom: none; }

/* Every ranking table's columns are addressed by CLASS, never by position
   (UI/UX follow-up after Stage 5.5). This used to be
   `.ranking-table td:first-child, .ranking-table th:first-child`, which
   assumed every such table starts with a "#" rank column — true on the
   Dashboard, but NOT for Statistics' "Среднее восстановление по участкам"
   / "по типам", whose first column is the label ("Участок"/"Тип"). Those
   labels were being squeezed into 26px and painted faint+bold like a rank
   number, which is what made their headers look shifted away from the
   values underneath.

   The three column roles below (rank / label / numeric value) are the one
   grid every ranking table now shares, so a header and its data can't
   land on different alignments.

   Both are scoped to .ranking-table ON PURPOSE: an unscoped
   `.ranking-value-col` (one class) LOSES to `.ranking-table th` (one class
   + one element) on specificity, so `text-align: right` silently applied
   to the <td>s only and every numeric HEADER stayed left-aligned above
   right-aligned digits — that is exactly what looked like the headers
   having "slid" off their columns. */
.ranking-table .ranking-rank-col { width: 26px; color: var(--text-faint); font-weight: 600; }
/* Numeric columns (count, %, duration, average) — right-aligned on BOTH
   th and td, applied together in every template, so the header sits
   exactly above its digits. */
.ranking-table .ranking-value-col { text-align: right; font-weight: 600; white-space: nowrap; }

/* Whole-row click target (touch-friendly, no separate link per cell): the
   real, only <a> stretches to cover the row via ::after. */
.ranking-row { position: relative; cursor: pointer; }
.ranking-row:hover, .ranking-row:focus-within { background: var(--bg); }
/* No position:relative here on purpose — the ::after below must find its
   containing block at the .ranking-row (<tr>) level, not on the link
   itself, or its inset:0 only covers the link's own small text box
   instead of the whole row. */
.ranking-row-link:hover { color: var(--accent-blue); text-decoration: underline; }
.ranking-row-link::after { content: ""; position: absolute; inset: 0; }
.ranking-row-link:focus-visible { outline: none; }
.ranking-row:focus-within { outline: 2px solid var(--accent-blue); outline-offset: -2px; }

/* ---------------------------------------------------------------------- */
/* Stage 5.2 — Equipment list + detail                                    */
/* ---------------------------------------------------------------------- */

/* white-space: nowrap — a pill badge must never wrap: "В ремонте" was
   breaking onto two lines inside History's squeezed status column, which
   made the pill two rows tall and the row ragged. Keeping the label on one
   line is the badge's own business, so it belongs here rather than on any
   one table's status column. */
.status-badge {
  display: inline-flex; align-items: center; padding: 3px 10px; border-radius: 999px;
  font-size: 12px; font-weight: 600; white-space: nowrap;
}
.status-badge-ok { background: var(--accent-green-soft); color: var(--accent-green); }
.status-badge-broken { background: var(--accent-red-soft); color: var(--accent-red); }
.status-badge-inactive { background: var(--border); color: var(--text-muted); }
.status-badge-lg { font-size: 13.5px; padding: 5px 14px; }
.status-dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; margin-right: 5px; flex: none; display: inline-block; }

.filter-card { margin-bottom: 18px; }
.equipment-filter-form { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.filter-input {
  flex: 2 1 280px; min-width: 200px; border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  padding: 9px 12px; font-family: inherit; font-size: 13.5px;
}
.filter-input:focus { outline: 2px solid var(--accent-blue); outline-offset: 1px; }
.filter-select {
  flex: 0 0 auto; border: 1px solid var(--border-strong); border-radius: var(--radius-sm); padding: 9px 10px;
  font-family: inherit; font-size: 13.5px; background: var(--card-bg); color: var(--text);
}

.table-scroll { overflow-x: auto; }
.equipment-table { font-size: 13px; min-width: 720px; }
.equipment-table th {
  text-align: left; color: var(--text-faint); font-weight: 600; font-size: 11.5px;
  text-transform: uppercase; letter-spacing: .03em; padding: 12px 14px; border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
.equipment-table td { padding: 12px 14px; border-bottom: 1px solid var(--border); vertical-align: middle; }
.equipment-table tr:last-child td { border-bottom: none; }

/* Equipment list, grouped by section (Stage 5.2 UX pass) — each group is a
   native <details>/<summary> (open by default): zero JS, keyboard-
   accessible for free (Enter/Space on <summary> toggles it natively), and
   deliberately not the popover mechanism app.js already has (that solves a
   different problem — an overlay panel that must close on outside click —
   which a simple always-in-flow collapsible list doesn't need). */
.equipment-groups { display: flex; flex-direction: column; gap: 14px; }
.equipment-group { padding: 0; overflow: hidden; }
.equipment-group-header {
  display: flex; align-items: center; gap: 8px; padding: 14px 18px; cursor: pointer;
  font-weight: 700; font-size: 14px; list-style: none; -webkit-user-select: none; user-select: none;
}
.equipment-group-header::-webkit-details-marker { display: none; }
.equipment-group-header:focus-visible { outline: 2px solid var(--accent-blue); outline-offset: -2px; border-radius: var(--radius-sm); }
.equipment-group-chevron { width: 14px; height: 14px; color: var(--text-faint); transition: transform .15s ease; flex: none; }
.equipment-group-chevron svg { width: 100%; height: 100%; }
.equipment-group[open] .equipment-group-chevron { transform: rotate(90deg); }
.equipment-group-count { color: var(--text-faint); font-weight: 500; }
.equipment-group-rows { border-top: 1px solid var(--border); }

.equipment-row-item {
  display: flex; align-items: center; gap: 12px; padding: 11px 18px;
  border-bottom: 1px solid var(--border);
}
.equipment-row-item:last-child { border-bottom: none; }
.equipment-row-item:hover { background: var(--bg); }
.equipment-row-item:focus-visible { outline: 2px solid var(--accent-blue); outline-offset: -2px; }
.equipment-row-icon {
  width: 30px; height: 30px; border-radius: 8px; flex: none; padding: 6px;
  background: var(--accent-blue-soft); color: var(--accent-blue);
  display: flex; align-items: center; justify-content: center;
}
.equipment-row-icon svg { width: 100%; height: 100%; }
.equipment-row-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.equipment-row-name { font-weight: 600; font-size: 13.5px; }
.equipment-row-status-line { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 12px; }
.equipment-row-issue { color: var(--text-muted); }
.equipment-row-count { font-size: 12.5px; color: var(--text-muted); white-space: nowrap; flex: none; }

/* Infinite scroll ("История поломок", templates/incidents.html) — the
   sentinel is a zero-content marker the IntersectionObserver watches;
   the footer holds the fallback "Загрузить ещё" button plus a small,
   non-intrusive status line ("Загрузка…" / "Показаны все записи" / an
   error). No redesign of the table/card itself. */
.infinite-scroll-sentinel { height: 1px; }
.infinite-scroll-footer {
  display: flex; align-items: center; justify-content: center; gap: 12px; flex-wrap: wrap;
  margin-top: 14px; font-size: 13px;
}
.infinite-scroll-status { color: var(--text-muted); }
/* .btn's own `display: inline-flex` has the same specificity as the
   attribute selector below and would otherwise win over it in the
   cascade, keeping a [hidden] button visible — same fix already applied
   to .period-custom-form/.kpi-popover/.modal above. */
[data-incidents-load-more][hidden] { display: none; }

/* Period bar as used on Equipment list/detail — visually secondary to the
   page's main content (list/status card), unlike the Dashboard's own use
   of .period-bar which stays full-size (not touched here). */
.period-bar-compact .period-range-display, .period-bar-compact .period-btn { padding: 6px 10px; font-size: 12px; }
.period-bar-compact svg { width: 13px; height: 13px; }

.back-link { display: inline-block; color: var(--accent-blue); font-weight: 600; font-size: 13px; margin-bottom: 10px; }
.equipment-detail-header { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: 14px; }
.equipment-detail-badges { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.active-flag-badge { font-size: 12px; font-weight: 600; padding: 3px 10px; border-radius: 999px; }
.active-flag-yes { background: var(--accent-blue-soft); color: var(--accent-blue); }
.active-flag-no { background: var(--border); color: var(--text-muted); }
.equipment-inactive-note { margin: 8px 0 0; font-size: 12.5px; }

/* Main status card — the dominant element right under the header (Stage
   5.2 UX pass): identification -> current failure (if any) -> everything
   else. Three variants share the same card shell. */
.status-card { margin-bottom: 18px; }
.status-card-broken { border-color: var(--accent-red); background: var(--accent-red-soft); }
.status-card-headline { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.status-card-title { margin: 0; font-size: 16px; font-weight: 700; }
.status-card-broken .status-card-title { color: var(--accent-red); }
.status-card-downtime { font-size: 20px; font-weight: 700; color: var(--accent-red); white-space: nowrap; }
.status-card-subtitle { font-size: 13.5px; color: var(--text); margin-top: 4px; font-weight: 600; }
.status-card-meta-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; margin-top: 14px; }
.status-card-meta-value { font-weight: 600; font-size: 13.5px; margin-top: 2px; }
.status-card-footnote { font-size: 12px; color: var(--text-muted); margin-top: 10px; }

.status-card-positive, .status-card-neutral { display: flex; align-items: flex-start; gap: 14px; }
.status-card-icon { font-size: 26px; line-height: 1; flex: none; }
.status-card-positive .status-card-title { color: var(--accent-green); }
.status-card-neutral .status-card-title { color: var(--text-muted); }
.status-card-subtitle-muted { font-size: 13px; color: var(--text-muted); margin-top: 2px; }

.equipment-kpi-header { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: 14px; margin-top: 6px; }
.section-title { font-size: 16px; font-weight: 700; margin: 0; }
.kpi-grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }

.equipment-type-card { margin-bottom: 18px; }
.equipment-type-card .donut-wrap { justify-content: flex-start; max-width: 520px; }

/* Incident-table column sizing — ONE set of column-role classes shared by
   Equipment Detail's "Последние поломки" (.recent-incidents-table) and the
   History page's table (.incidents-table), which have the same columns in
   the same roles (UI/UX follow-up after Stage 5.5 extended this from the
   detail table to History too).

   Both reporter columns share ONE class, so they can never drift to
   different widths, and both are kept narrow — the old "Сообщил о
   восстановлении" header alone was wider than any name under it and ate
   the comment column's space. Metadata columns shrink to their content
   (width:1% is the usual auto-table-layout "as narrow as possible" hint)
   while .comment-cell claims the entire remainder.

   Scoped to .equipment-table so these win over its own td/th padding
   (equal class count, one more element) and so nothing else on the site
   can accidentally pick up a `.col-*` name. */
.equipment-table .col-num,
.equipment-table .col-date,
.equipment-table .col-datetime,
.equipment-table .col-section,
.equipment-table .col-type,
.equipment-table .col-status,
.equipment-table .col-downtime { width: 1%; white-space: nowrap; }
.equipment-table .col-reporter {
  width: 96px; min-width: 96px; max-width: 96px;
  padding-left: 10px; padding-right: 10px; overflow-wrap: anywhere;
}
/* Equipment name is the one metadata column allowed to wrap and grow — it
   holds free text, unlike the dates/labels above. */
.equipment-table .col-equipment { min-width: 150px; }

.recent-incidents-table { min-width: 780px; }

/* ---------------------------------------------------------------------- */
/* Stage 5.3 — История поломок + web repair comments                      */
/* ---------------------------------------------------------------------- */

.incidents-table { min-width: 1180px; }
.filter-input-number { flex: 0 1 120px; min-width: 90px; }

/* Comment column takes whatever the fixed-width metadata columns above
   leave over, on BOTH incident tables. Row height is no longer kept in
   check by clipping the text at a max-width — it is kept in check by
   showing a short preview and hiding the rest behind .comment-full's
   disclosure (see below), which is both tidier and actually readable. */
/* 240px, not more: History's eleven columns plus a wider floor pushed the
   table ~2px past the widest desktop card, so it scrolled horizontally on
   every screen. At 240 the whole table fits at max content width and the
   comment column still takes the real remainder via width:100% (~260px on
   History, ~670px on Equipment Detail's shorter table). */
.comment-cell { width: 100%; min-width: 240px; }
.comment-preview { font-size: 12.5px; white-space: pre-wrap; word-break: break-word; }
.comment-empty { font-size: 12.5px; }

/* Read-only "Показать полностью" disclosure for a long comment (UI/UX
   follow-up after Stage 5.5). Reading a full comment used to require
   opening the EDIT form, which conflated reading with editing; now the
   <summary> IS the preview, so clicking the preview text expands the full
   comment in place. Native <details>, no JS, same idiom as .comment-edit
   below — and .comment-edit stays a separate control doing only editing.

   Both toggle labels live in the markup (not in CSS `content`) and are
   swapped by [open], so the visible text is real, translatable content. */
.comment-full { margin: 0; }
/* Open state is laid out bottom-up so the toggle stays UNDER the text in
   both states — collapsed reads [preview][Показать полностью], expanded
   reads [full text][Свернуть], instead of the toggle jumping above the
   comment just because <summary> must come first in the markup. */
.comment-full[open] { display: flex; flex-direction: column-reverse; }
.comment-full-summary {
  cursor: pointer; list-style: none; -webkit-user-select: none; user-select: none;
}
.comment-full-summary::-webkit-details-marker { display: none; }
.comment-full-summary:focus-visible { outline: 2px solid var(--accent-blue); outline-offset: 2px; border-radius: var(--radius-sm); }
.comment-full-toggle {
  display: inline-block; margin-top: 2px;
  font-size: 12.5px; font-weight: 600; color: var(--accent-blue);
}
.comment-full-summary:hover .comment-full-toggle { text-decoration: underline; }
.comment-full[open] .comment-collapsed-text { display: none; }
.comment-full[open] .comment-toggle-more { display: none; }
.comment-full:not([open]) .comment-toggle-less { display: none; }
.comment-error {
  background: var(--accent-red-soft); color: var(--accent-red); font-size: 12px;
  padding: 6px 8px; border-radius: var(--radius-sm); margin: 0 0 6px;
}

/* "Добавить/Редактировать комментарий" — a link-looking <button> that opens
   the shared edit dialog. It used to be the <summary> of an inline
   <details> edit form; the form moved into partials/comment_modal.html, so
   the .comment-edit / .comment-edit-form / .comment-edit-actions rules that
   styled that inline form are gone with it — there is one edit UI now. */
.comment-edit-toggle {
  display: inline-block; margin-top: 6px; padding: 0; border: none; background: none;
  font: inherit; font-size: 12.5px; font-weight: 600; color: var(--accent-blue);
  cursor: pointer; text-align: left;
}
.comment-edit-toggle:hover { text-decoration: underline; }
.comment-edit-toggle:focus-visible { outline: 2px solid var(--accent-blue); outline-offset: 2px; border-radius: var(--radius-sm); }
.comment-textarea {
  width: 100%; min-height: 64px; resize: vertical; border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm); padding: 8px 10px; font-family: inherit; font-size: 13px;
}
.comment-textarea:focus { outline: 2px solid var(--accent-blue); outline-offset: 1px; }

/* Counter sits under the dialog's textarea, kept in sync while typing by
   app.js (see .comment-modal-counter for its placement). */
.comment-counter { font-size: 12px; color: var(--text-faint); font-variant-numeric: tabular-nums; }

/* ---------------------------------------------------------------------- */
/* Stage 5.4 — Статистика                                                  */
/* ---------------------------------------------------------------------- */

.stats-section-title { margin: 22px 0 14px; }
.stats-section-title:first-of-type { margin-top: 0; }

.kpi-grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
/* Two EQUAL columns, matching .rankings-grid-2 below it (post-5.5 UI/UX
   polish): the trend/type chart row used a 1.4/1 ratio while every ranking
   row underneath was 1/1, so the card edges down the page never lined up.
   Same tracks, same gap, same breakpoints -> one visible grid. The wider
   type card is also what lets the donut grow (see .stats-type-card). */
.chart-grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.rankings-grid-2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; margin-bottom: 18px; }
.stats-full-ranking { margin-bottom: 18px; }

/* Statistics' "Поломки по типам неисправностей": the card is half the page
   wide, but a 150px donut + a narrow legend left most of it empty. Fill it
   — bigger donut, roomier legend, and a .donut-wrap that stretches to the
   card's full height so the visualization is vertically centred against
   the equally tall trend chart beside it. */
.stats-type-card .donut-wrap { flex: 1; gap: 28px; align-items: center; justify-content: center; }
.stats-type-card .donut-svg { width: 236px; height: 236px; }
.stats-type-card .donut-legend { gap: 12px; min-width: 200px; flex: 0 1 240px; }
.stats-type-card .donut-legend-label, .stats-type-card .donut-legend-pct { font-size: 13.5px; }
.stats-type-card .donut-legend-dot { width: 10px; height: 10px; }

/* Scoped like the other ranking column roles above, for the same
   specificity reason (`.ranking-table th` would otherwise win). */
.ranking-table .ranking-action-col { text-align: right; width: 70px; }
.ranking-row-history-link {
  position: relative; z-index: 1; color: var(--accent-blue); font-weight: 600; font-size: 12px;
}
.ranking-row-history-link:hover { text-decoration: underline; }

.stats-longest-table { min-width: 900px; }

/* ---------------------------------------------------------------------- */
/* Stage 5.5 — QR-коды                                                     */
/* ---------------------------------------------------------------------- */

.qr-bulk-actions { display: flex; gap: 8px; flex-wrap: wrap; }

.qr-detail-link { display: inline-flex; align-items: center; gap: 6px; }
.qr-detail-link svg { width: 14px; height: 14px; }

.qr-group-bulk-link {
  margin-left: auto; font-size: 12px; font-weight: 600; color: var(--accent-blue);
  padding: 4px 8px; border-radius: var(--radius-sm);
}
.qr-group-bulk-link:hover { background: var(--bg); text-decoration: underline; }

/* Per-equipment QR row — a compact, always-the-same-height <button> whose
   whole surface opens the shared QR modal (post-5.5 UI/UX polish). It
   replaced a <details> that expanded a 150px QR preview inline: the row
   stayed compact only while closed, and an opened one shoved the rest of
   the list down the page. Preview/Download/Print now live in the modal, so
   this page and Equipment Detail share one QR UX pattern. */
.qr-row {
  display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  padding: 11px 18px; background: none; border: none; border-bottom: 1px solid var(--border);
  font: inherit; color: inherit; cursor: pointer;
}
.qr-row:last-child { border-bottom: none; }
.qr-row:hover { background: var(--bg); }
.qr-row:focus-visible { outline: 2px solid var(--accent-blue); outline-offset: -2px; }
.qr-row-toggle-label { font-size: 12px; color: var(--accent-blue); font-weight: 600; flex: none; }

/* ---------------------------------------------------------------------- */
/* Stage 5.6 — Управление                                                  */
/* ---------------------------------------------------------------------- */

/* Page-level validation error banner (empty name, has-history delete,
   stale not_found target, …) — same red palette as .login-error/
   .comment-error, just sized for a full-width page banner. */
.manage-alert {
  background: var(--accent-red-soft); color: var(--accent-red); font-size: 13px;
  padding: 12px 16px; margin-bottom: 18px;
}

.manage-table { min-width: 860px; }
.manage-equipment-name { font-weight: 600; color: var(--accent-blue); }
.manage-equipment-name:hover { text-decoration: underline; }
.manage-issue-note { font-size: 12px; margin-top: 3px; }
.manage-col-actions { width: 1%; white-space: nowrap; }
.manage-actions { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.manage-inline-form { display: inline-flex; margin: 0; }
.manage-action-disabled { font-size: 12.5px; cursor: help; }

.manage-form-input { width: 100%; }
.manage-form-input + .manage-form-input { margin-top: 0; }
.manage-form-label-spaced { margin-top: 14px; }
.manage-confirm-message { margin: 0; font-size: 13.5px; }

/* Small ADMIN-only shortcut into Management from the Equipment list header
   (item 23 of the Stage 5.6 task) — deliberately just a link, not a second
   management UI on this page. */
.manage-shortcut-link { color: var(--accent-blue); font-weight: 600; }
.manage-shortcut-link:hover { text-decoration: underline; }

/* ---------------------------------------------------------------------- */
/* Modal shell — generic, shared by every dialog on the site               */
/* (partials/qr_modal.html, partials/comment_modal.html).                  */
/*                                                                         */
/* These were the .qr-modal-* rules until a second dialog appeared (Stage  */
/* 5 comment edit modal UX); the shell was generalised instead of copied,  */
/* so backdrop, header, close button, scroll behaviour and focus ring stay */
/* defined once. Per-dialog rules live under .qr-modal / .comment-modal    */
/* below and only set what actually differs (width, body padding).         */
/* ---------------------------------------------------------------------- */

.modal {
  position: fixed; inset: 0; z-index: 60; padding: 20px;
  display: flex; align-items: center; justify-content: center; overflow-y: auto;
}
.modal[hidden] { display: none; }
.modal-backdrop { position: absolute; inset: 0; background: rgba(15, 20, 30, .5); }
.modal-card {
  position: relative; z-index: 1; width: 100%; max-width: 380px;
  background: var(--card-bg); border-radius: var(--radius); box-shadow: var(--shadow-popover);
  /* A short viewport (or a laptop at 125% scaling) scrolls the dialog
     itself rather than clipping its content or the action buttons. */
  max-height: calc(100vh - 40px); overflow-y: auto;
}
.modal-header {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 13px 16px; border-bottom: 1px solid var(--border);
}
.modal-title { margin: 0; font-size: 14.5px; font-weight: 600; }
.modal-close {
  flex: none; width: 28px; height: 28px; border: none; background: none; cursor: pointer;
  border-radius: var(--radius-sm); color: var(--text-muted); font: inherit; font-size: 20px;
  line-height: 1; display: flex; align-items: center; justify-content: center;
}
.modal-close:hover { background: var(--bg); color: var(--text); }
.modal-close:focus-visible { outline: 2px solid var(--accent-blue); outline-offset: 1px; }
.modal-body { padding: 18px 20px 0; }
.modal-actions { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; padding: 16px 20px 20px; }

/* Page behind an open dialog must not scroll under it. */
body.modal-open { overflow: hidden; }

/* --- QR dialog specifics ---------------------------------------------- */

.qr-modal-body { text-align: center; }
.qr-modal-equipment-name { font-size: 15px; font-weight: 700; }
.qr-modal-equipment-section { font-size: 13px; margin-top: 2px; }
.qr-modal-image {
  display: block; margin: 16px auto 0; width: 220px; height: 220px; max-width: 100%;
  border: 1px solid var(--border); border-radius: var(--radius-sm); background: #fff; padding: 8px;
}
.qr-modal-caption { margin: 14px 0 0; font-size: 12.5px; color: var(--text-muted); }

/* --- Comment edit dialog specifics ------------------------------------ */

/* Wider than the QR dialog (which is sized around a 220px image) because
   this one is mostly a textarea — but still nowhere near full-screen on
   desktop. */
.comment-modal-card { max-width: 560px; }
.comment-modal-form { display: block; }
.comment-modal-context { margin-bottom: 14px; }
.comment-modal-incident { font-size: 12.5px; color: var(--text-muted); }
.comment-modal-equipment { font-size: 15px; font-weight: 700; margin-top: 2px; }
.comment-modal-section { font-size: 12.5px; margin-top: 2px; }
.comment-modal-label {
  display: block; font-size: 12.5px; color: var(--text-muted); margin-bottom: 6px;
}
/* ~8 rows on desktop — the inline form this replaced was 3 rows, which
   made a long comment unreadable in the very place you had to edit it. */
.comment-modal-textarea { min-height: 168px; }
.comment-modal-counter { display: flex; justify-content: flex-end; margin-top: 6px; }
/* Cancel/Save sit right-aligned like a normal dialog footer, not centred
   like the QR dialog's two equal-weight download/print links. */
.comment-modal .modal-actions { justify-content: flex-end; }

/* --- Stage 5.6 management dialogs specifics ---------------------------- */

/* Small two-field form (name + section) — narrower than the comment
   dialog, no reason for it to stretch wide. */
.manage-form-modal-card { max-width: 400px; }
.manage-form-modal .modal-actions { justify-content: flex-end; }

/* Confirmation dialog — same narrow width as the QR dialog (a short
   message + two buttons, nothing to stretch for). */
.manage-confirm-card { max-width: 380px; }

/* ---------------------------------------------------------------------- */
/* Stage 5.5 — single-QR print page (standalone, not admin-shell)          */
/* ---------------------------------------------------------------------- */

.qr-print-body {
  min-height: 100vh; display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 20px; background: var(--bg); padding: 24px;
}
.qr-print-toolbar { display: flex; gap: 10px; }
.qr-print-card {
  background: #fff; border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow-card); padding: 32px 40px; text-align: center;
  display: flex; flex-direction: column; align-items: center; gap: 10px;
}
.qr-print-image { width: 260px; height: 260px; }
.qr-print-name { font-size: 18px; font-weight: 700; margin-top: 6px; }
.qr-print-section { font-size: 14px; color: var(--text-muted); }
.qr-print-caption { font-size: 13px; color: var(--text-muted); margin-top: 4px; }

@media print {
  .qr-print-toolbar { display: none; }
  .qr-print-body { background: #fff; padding: 0; min-height: 0; }
  .qr-print-card { box-shadow: none; border: none; }
}

/* ---------------------------------------------------------------------- */
/* Login page                                                              */
/* ---------------------------------------------------------------------- */

.login-body {
  min-height: 100vh; display: flex; align-items: center; justify-content: center;
  background: var(--sidebar-bg); padding: 20px;
}
.login-card {
  width: 100%; max-width: 380px; background: var(--card-bg); border-radius: var(--radius);
  box-shadow: var(--shadow-popover); padding: 32px 30px;
}
.login-brand { display: flex; align-items: center; gap: 12px; margin-bottom: 22px; }
.login-brand .brand-icon { width: 34px; height: 34px; color: var(--accent-blue); }
.login-brand .brand-icon svg { width: 100%; height: 100%; }
.login-title { font-size: 18px; font-weight: 700; }
.login-subtitle { font-size: 12.5px; color: var(--text-muted); }
.login-error {
  background: var(--accent-red-soft); color: var(--accent-red); padding: 10px 12px;
  border-radius: var(--radius-sm); font-size: 13px; margin-bottom: 16px;
}
.login-form { display: flex; flex-direction: column; gap: 5px; }
.login-label { font-size: 12.5px; color: var(--text-muted); margin-top: 10px; }
.login-input {
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm); padding: 10px 12px;
  font-family: inherit; font-size: 14px;
}
.login-input:focus { outline: 2px solid var(--accent-blue); outline-offset: 1px; }
.login-submit {
  margin-top: 18px; background: var(--accent-blue); color: #fff; border: none;
  border-radius: var(--radius-sm); padding: 11px; font-size: 14px; font-weight: 600; cursor: pointer;
}
.login-submit:hover { background: #255fd1; }

/* ---------------------------------------------------------------------- */
/* Responsive                                                              */
/* ---------------------------------------------------------------------- */

@media (max-width: 1440px) {
  .kpi-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .kpi-grid-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* .chart-grid / .chart-grid-2 deliberately keep their desktop column
     count here: they used to reflow one breakpoint EARLIER than the
     ranking rows below them (3->2 and 2->1 at 1440 while rankings stayed
     3 and 2 until 1024), which is exactly what made the rows look
     misaligned on a normal desktop. They now stack together at 1024. */
}

@media (max-width: 1024px) {
  .rankings-grid { grid-template-columns: 1fr; }
  .chart-grid { grid-template-columns: 1fr; }
  .rankings-grid-2 { grid-template-columns: 1fr; }
  .chart-grid-2 { grid-template-columns: 1fr; }
  .kpi-grid-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 768px) {
  .sidebar {
    position: fixed; left: 0; top: 0; z-index: 50; transform: translateX(-100%);
    transition: transform .18s ease; box-shadow: var(--shadow-popover);
  }
  .sidebar.open { transform: translateX(0); }
  .mobile-nav-toggle { display: flex; align-items: center; justify-content: center; }
  .mobile-nav-backdrop {
    display: none; position: fixed; inset: 0; background: rgba(15, 20, 30, .45); z-index: 40;
  }
  .mobile-nav-backdrop.open { display: block; }
  .content { padding: 16px 14px 32px; }
  .kpi-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .dashboard-header { flex-direction: column; align-items: stretch; }
  .period-bar { justify-content: flex-start; }
  .kpi-grid-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .equipment-detail-header { flex-direction: column; align-items: stretch; }
  .equipment-kpi-header { flex-direction: column; align-items: stretch; }
  .equipment-filter-form { flex-direction: column; align-items: stretch; }
  .filter-input { flex: none; }
  /* Equipment row: let it wrap to a second line instead of forcing
     .equipment-row-count (white-space: nowrap by design, so its own text
     never breaks mid-word) to squeeze the row past the viewport width. */
  .equipment-row-item { flex-wrap: wrap; }
  .equipment-row-count { flex: 1 1 100%; padding-left: 42px; }
  /* Narrower floor on small screens — the table scrolls horizontally
     inside .table-scroll there anyway, so a wide comment column would only
     make that scroll longer. */
  .comment-cell { min-width: 200px; }
  .kpi-grid-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .qr-group-bulk-link { margin-left: 0; flex: 1 1 100%; }
  .equipment-group-header { flex-wrap: wrap; }
  /* Donut stays large but must not push the legend off a tablet-width
     card — let the two wrap onto separate lines instead. */
  .stats-type-card .donut-svg { width: 200px; height: 200px; }
  .stats-type-card .donut-legend { flex: 1 1 200px; }
}

@media (max-width: 480px) {
  .kpi-grid { grid-template-columns: 1fr; }
  .kpi-popover, .period-custom-form { width: min(320px, 88vw); left: auto; right: 0; }
  .topbar-user-text { display: none; }
  .content { padding: 14px 10px 28px; }
  .card { padding: 14px; }
  .period-bar { gap: 6px; }
  .period-range-display, .period-btn { padding: 7px 10px; font-size: 12px; }
  .kpi-grid-4 { grid-template-columns: 1fr; }
  .kpi-grid-3 { grid-template-columns: 1fr; }
  .status-card-meta-grid { grid-template-columns: 1fr; }
  .qr-modal { padding: 12px; }
  .qr-modal-card { max-height: calc(100vh - 24px); }
  .qr-modal-image { width: 190px; height: 190px; }
  .qr-row { padding: 11px 14px; }
  .stats-type-card .donut-svg { width: 180px; height: 180px; }
}
