/* ==========================================================================
   Мониторинг майнеров.

   Правило цвета: цвет означает температуру или отклонение и больше ничего.
   Исправная ферма монохромна, проблема — единственное цветное пятно на
   экране. Зелёного «всё хорошо» нет: панель открывают ночью с телефона,
   чтобы ответить на один вопрос — «где горит». Если раскрашено всё, не
   выделяется ничто.

   Палитра — тепловизорная (docs/plans/2026-08-04-miners-dashboard-design.md).
   Тема тёмная всегда: это инструмент для помещения с приглушённым светом.
   ========================================================================== */

/* --- Шрифты ---------------------------------------------------------------
   Локальные файлы, как в ../my-site: без обращений к чужим доменам страница
   не зависит от чужой доступности. Подмножества latin и cyrillic отдаются по
   unicode-range — телефон качает только нужный.
   -------------------------------------------------------------------------- */

@font-face {
  font-family: 'Golos Text';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/static/fonts/GolosText-400-cyrillic.woff2) format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'Golos Text';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/static/fonts/GolosText-400-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Golos Text';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(/static/fonts/GolosText-600-cyrillic.woff2) format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'Golos Text';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(/static/fonts/GolosText-600-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/static/fonts/IBMPlexMono-400-cyrillic.woff2) format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/static/fonts/IBMPlexMono-400-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(/static/fonts/IBMPlexMono-500-cyrillic.woff2) format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(/static/fonts/IBMPlexMono-500-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Unbounded';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(/static/fonts/Unbounded-700-cyrillic.woff2) format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'Unbounded';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(/static/fonts/Unbounded-700-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* --- Токены --------------------------------------------------------------- */

:root {
  /* Палитра тепловизора: таблица из дизайн-документа, значения точные. */
  --ash:      #14100E;   /* фон, тёплая сажа, не сине-чёрный */
  --rack:     #1E1815;   /* поверхности карточек и строк */
  --line:     #2E2622;   /* разделители */
  --text:     #EDE6E0;   /* основной текст */
  --muted:    #9A8F87;   /* подписи */
  --frost:    #5B7FB0;   /* офлайн: майнер физически холодный */
  --flare:    #F2B23C;   /* повышенная температура */
  --ember:    #E2622F;   /* перегрев */
  --critical: #D6453F;   /* авария */

  /* Не из таблицы: клетка нормы. Она обязана быть видимой в полосе стойки,
     но не имеет права быть цветной — иначе исправная ферма перестанет быть
     монохромной и правило цвета сломается на самом заметном элементе. */
  --steady:   #6B605A;

  --f-display: 'Unbounded', system-ui, sans-serif;
  --f-body:    'Golos Text', system-ui, -apple-system, sans-serif;
  --f-mono:    'IBM Plex Mono', ui-monospace, 'Cascadia Mono', monospace;

  --gutter: clamp(0.875rem, 3vw, 2.25rem);
  --measure: 1440px;
  --radius: 3px;

  /* Единственная длительность движения в проекте: переток числа и смена
     цвета клетки. Всё остальное статично. */
  --beat: 400ms;
}

*, *::before, *::after { box-sizing: border-box; }

/* [hidden] в браузерном стиле — это display:none с нулевой специфичностью:
   любой наш .bulk { display:flex } его перекрывает, и «скрытый» блок
   остаётся на экране. */
[hidden] { display: none !important; }

html {
  -webkit-text-size-adjust: 100%;
  background: var(--ash);
}

body {
  margin: 0;
  min-height: 100vh;
  padding: 0 var(--gutter) calc(3rem + env(safe-area-inset-bottom));
  background: var(--ash);
  color: var(--text);
  font-family: var(--f-body);
  font-size: 0.9375rem;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

body > * { max-width: var(--measure); margin-inline: auto; }

h1, h2, h3 { margin: 0; font-weight: 600; line-height: 1.2; }
p { margin: 0; }
ul, ol, dl, dd, dt { margin: 0; padding: 0; }
li { list-style: none; }
a { color: inherit; }
button, input, textarea, select { font: inherit; color: inherit; }

/* Табличные цифры на всех числах: без них строка дёргается по ширине при
   каждом обновлении, и глаз ловит движение там, где ничего не произошло. */
.num {
  font-family: var(--f-mono);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
  letter-spacing: -0.01em;
}

.unit-mark {           /* «TH/s», «Вт», «°C» рядом с числом */
  color: var(--muted);
  font-size: 0.75em;
  font-weight: 400;
  letter-spacing: 0.02em;
}

.overline {
  font-family: var(--f-display);
  font-weight: 700;
  font-size: 0.5625rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted);
}

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

:focus-visible {
  outline: 2px solid var(--ember);
  outline-offset: 2px;
}

.skip-link {
  position: fixed;
  top: 0; left: 50%;
  z-index: 90;
  padding: 0.6rem 1.2rem;
  transform: translate(-50%, -140%);
  background: var(--text);
  color: var(--ash);
  font: 500 0.75rem/1 var(--f-mono);
  text-decoration: none;
}
.skip-link:focus { transform: translate(-50%, 0); }

/* --- Шапка ---------------------------------------------------------------- */

.masthead {
  padding: clamp(1rem, 2.5vw, 1.75rem) 0 0;
}

.masthead__bar {
  display: flex;
  align-items: baseline;
  gap: 0.75rem 1rem;
  flex-wrap: wrap;
}

.masthead__tools {
  display: flex;
  align-items: center;
  gap: 0.25rem;
}
.masthead__tools .logout { display: contents; }

.wordmark {
  font-family: var(--f-display);
  font-weight: 700;
  font-size: 0.8125rem;
  letter-spacing: 0.34em;
  text-transform: uppercase;
  margin-right: auto;
}

/* Возраст данных стоит рядом с логотипом, а не прячется в подвале: мёртвый
   поллер снаружи не виден ничем другим, и «данные от 14:32» — единственная
   компенсация. */
.freshness {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-family: var(--f-mono);
  font-size: 0.75rem;
  color: var(--muted);
}
.freshness::before {
  content: '';
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--steady);
  transition: background-color var(--beat) linear;
}
.freshness[data-state='stale']::before { background: var(--flare); }
.freshness[data-state='offline']::before { background: var(--frost); }
.freshness[data-state='stale'],
.freshness[data-state='offline'] { color: var(--text); }

/* --- Полоса стойки -------------------------------------------------------- */

/* Подпись экрана: взгляд вдоль стойки. Клетка на майнера, цвет по тепловой
   шкале. Холодный провал в ленте виден раньше, чем прочитано хоть одно
   число. */
.rack {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(16px, 1fr));
  gap: 4px;
  margin: 1.25rem 0 0;
  padding: 0;
  border: 0;
}

.rack__cell {
  height: 30px;
  padding: 0;
  border: 0;
  border-radius: 2px;
  background: var(--steady);
  cursor: pointer;
  transition: background-color var(--beat) linear, transform 120ms ease;
}
.rack__cell:hover { transform: translateY(-2px); }
.rack__cell[data-heat='cold']  { background: var(--frost); }
.rack__cell[data-heat='warm']  { background: var(--flare); }
.rack__cell[data-heat='hot']   { background: var(--ember); }
.rack__cell[data-heat='down']  { background: var(--critical); }
.rack__cell[data-heat='unknown'] { background: var(--line); }
.rack__cell[aria-current='true'] {
  box-shadow: inset 0 0 0 2px var(--text);
}

.rack--empty {
  min-height: 30px;
  border-radius: 2px;
  background: repeating-linear-gradient(
    90deg, var(--rack) 0 16px, transparent 16px 20px
  );
}

/* --- Сводка --------------------------------------------------------------- */

.totals {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1rem 1.5rem;
  margin: 1.25rem 0 0;
  padding: 0 0 1.25rem;
  border-bottom: 1px solid var(--line);
}

.totals__item { min-width: 0; }
.totals__item dt { margin-bottom: 0.35rem; }
.totals__item dd {
  font-family: var(--f-mono);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  font-size: clamp(1.25rem, 4vw, 1.75rem);
  line-height: 1;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* --- Баннеры -------------------------------------------------------------- */

.banners { margin-top: 0.75rem; }
.banners:empty { display: none; }

.banner {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-top: 0.5rem;
  padding: 0.7rem 0.9rem;
  border: 1px solid var(--line);
  border-left-width: 3px;
  border-radius: var(--radius);
  background: var(--rack);
  font-size: 0.875rem;
}
.banner__text { flex: 1 1 12rem; }
.banner__actions { display: flex; gap: 0.5rem; margin-left: auto; }
.banner[data-tone='cold'] { border-left-color: var(--frost); }
.banner[data-tone='warn'] { border-left-color: var(--flare); }
.banner[data-tone='calm'] { border-left-color: var(--line); }

/* --- Тревоги -------------------------------------------------------------- */

.alerts { margin-top: 1.25rem; }
.alerts:empty { display: none; }

.alert {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.65rem 0.9rem;
  border-left: 3px solid var(--flare);
  background: var(--rack);
  border-radius: 0 var(--radius) var(--radius) 0;
}
.alert + .alert { margin-top: 2px; }
.alert[data-severity='critical'] { border-left-color: var(--critical); }

.alert__body { flex: 1 1 auto; min-width: 0; }
.alert__name {
  font-family: var(--f-mono);
  font-weight: 500;
  font-size: 0.8125rem;
}
.alert__text {
  font-size: 0.8125rem;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Строка «ещё N»: тревог может быть двадцать, а место на экране одно. */
.alert--more {
  display: block;
  width: 100%;
  margin-top: 2px;
  padding: 0.55rem 0.9rem;
  border: 0;
  border-left: 3px solid var(--line);
  background: var(--rack);
  color: var(--muted);
  text-align: left;
  cursor: pointer;
}
.alert--more:hover { color: var(--text); }

/* --- Панель управления ---------------------------------------------------- */

.toolbar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin-top: 1.25rem;
  padding: 0.75rem 0;
  background: var(--ash);
  border-bottom: 1px solid var(--line);
}

.search {
  flex: 1 1 12rem;
  min-width: 0;
  padding: 0.5rem 0.7rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--rack);
  font-size: 0.875rem;
}
.search::placeholder { color: var(--muted); }

.segmented {
  display: inline-flex;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
}
.segmented button {
  padding: 0.5rem 0.75rem;
  border: 0;
  background: transparent;
  color: var(--muted);
  font-size: 0.8125rem;
  cursor: pointer;
}
.segmented button + button { border-left: 1px solid var(--line); }
.segmented button[aria-pressed='true'] {
  background: var(--line);
  color: var(--text);
}

.icon-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  min-width: 2.25rem;
  height: 2.25rem;
  padding: 0 0.6rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: transparent;
  color: var(--muted);
  font-size: 0.8125rem;
  cursor: pointer;
}
.icon-button:hover { color: var(--text); border-color: var(--muted); }
.icon-button[aria-pressed='true'] { background: var(--line); color: var(--text); }
.icon-button svg { width: 16px; height: 16px; fill: currentColor; }

.button {
  padding: 0.5rem 0.9rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: transparent;
  color: var(--text);
  font-size: 0.875rem;
  cursor: pointer;
}
.button:hover:not(:disabled) { border-color: var(--muted); }
.button--accent {
  border-color: var(--ember);
  background: var(--ember);
  color: var(--ash);
  font-weight: 600;
}
.button--accent:hover:not(:disabled) { filter: brightness(1.08); }
/* Опасное действие не раскрашено в состоянии покоя: красный на этом экране
   означает аварию, и кнопка «Удалить» с ним соперничать не должна. Цвет
   появляется под курсором и в фокусе — там, где он уже про действие. */
.button--danger { color: var(--muted); }
.button--danger:hover:not(:disabled),
.button--danger:focus-visible { border-color: var(--critical); color: var(--critical); }
/* В диалоге подтверждения — наоборот: там всё внимание уже на одном действии. */
dialog .button--danger { border-color: var(--critical); color: var(--critical); }
dialog .button--danger:hover:not(:disabled) { background: var(--critical); color: var(--text); }
.button--quiet { border-color: transparent; color: var(--muted); }
.button--quiet:hover:not(:disabled) { color: var(--text); }
button:disabled, .button:disabled { opacity: 0.4; cursor: not-allowed; }

/* Сеть недоступна — всё, что меняет состояние фермы, выключено. Показывать
   кнопку, которая заведомо не сработает, значит врать.
   Выключает её `disabled` из app.js (syncNetworkControls), а не этот стиль:
   `pointer-events: none` убрал бы только мышь, оставив Tab и Enter рабочими.
   Здесь — как это выглядит. */
body[data-connection='offline'] [data-needs-network] {
  opacity: 0.4;
  cursor: not-allowed;
}

.bulk {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-top: 0.5rem;
  padding: 0.6rem 0.9rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--rack);
  font-size: 0.875rem;
}
.bulk__count { margin-right: auto; font-family: var(--f-mono); }

/* --- Список --------------------------------------------------------------- */

.list { margin-top: 0.25rem; }

.columns {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.75rem 0.75rem 0.4rem;
}
.columns > [data-sort]:first-of-type { flex: 1 1 auto; text-align: left; }
.columns__metrics {
  display: grid;
  grid-template-columns: var(--metric-columns);
  gap: 0.75rem;
  align-items: center;
}
.columns__metrics > * { text-align: right; }
.columns__pick { display: none; width: 1rem; flex: 0 0 auto; }
body[data-select='on'] .columns__pick { display: block; }

/* В режиме карточек заголовок остаётся, но перестаёт быть заголовком таблицы:
   колонок под ним нет, выравнивать нечего. Спрятать его целиком нельзя — это
   единственный орган управления сортировкой во всём интерфейсе, и вместе с
   ним пропадала сама сортировка. Поэтому здесь он превращается в полосу
   кнопок «сортировать по». */
body[data-view='cards'] .columns__metrics {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
}
body[data-view='cards'] .columns > [data-sort]:first-of-type { flex: 0 0 auto; }
body[data-view='cards'] .columns { flex-wrap: wrap; }

/* Заголовок таблицы — это и переключатель сортировки. Активный столбец
   помечается стрелкой, а не цветом: цвет занят температурой. */
.columns [data-sort] {
  padding: 0;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  cursor: pointer;
}
.columns [data-sort]:hover { color: var(--text); }
.columns [data-sort][data-active='true'] { color: var(--text); }
.columns [data-sort][aria-sort='ascending']::after { content: ' ↑'; }
.columns [data-sort][aria-sort='descending']::after { content: ' ↓'; }

:root { --metric-columns: 6.5rem 4.5rem 5rem 4.5rem 2.25rem; }

.unit {
  border-bottom: 1px solid var(--line);
  background: transparent;
}
.unit[data-state='offline'] { color: var(--muted); }

.unit__row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding-left: 0.75rem;
}
.list--cards .unit__row { padding-left: 0.9rem; }

.unit__head {
  display: grid;
  grid-template-columns: minmax(7rem, 1fr) auto;
  gap: 0.75rem;
  align-items: center;
  flex: 1 1 auto;
  min-width: 0;
  padding: 0.7rem 0.75rem 0.7rem 0;
  border: 0;
  background: transparent;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.unit__head:hover { background: var(--rack); }

.unit__id {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  min-width: 0;
}
.unit__name {
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.unit__octet {
  font-family: var(--f-mono);
  font-size: 0.8125rem;
  color: var(--muted);
}

.unit__metrics {
  display: grid;
  grid-template-columns: var(--metric-columns);
  gap: 0.75rem;
  align-items: center;
}
.metric { text-align: right; min-width: 0; }
.metric__label { display: none; }
.metric__value {
  font-family: var(--f-mono);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  font-size: 0.875rem;
  white-space: nowrap;
}
.metric--temp .metric__value[data-heat='warm'] { color: var(--flare); }
.metric--temp .metric__value[data-heat='hot']  { color: var(--ember); }
.metric--temp .metric__value[data-heat='down'] { color: var(--critical); }

.unit__more {
  justify-self: end;
  color: var(--muted);
  font-family: var(--f-mono);
}

.unit__pick {
  display: none;
  width: 1rem; height: 1rem;
  flex: 0 0 auto;
  margin: 0;
  accent-color: var(--ember);
}
body[data-select='on'] .unit__pick { display: block; }

/* Карточки: тот же узел, другая раскладка. Переключение вида не пересобирает
   список — меняется один класс на контейнере. */
.list--cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
  gap: 0.75rem;
  margin-top: 0.75rem;
}
.list--cards .unit {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--rack);
}
.list--cards .unit__head {
  grid-template-columns: 1fr;
  gap: 0.9rem;
  padding: 0.9rem 0.9rem 0.9rem 0;
}
.list--cards .unit__head:hover { background: transparent; border-radius: var(--radius); }
.list--cards .unit__metrics {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.75rem 0.5rem;
}
.list--cards .metric { text-align: left; }
.list--cards .metric__label { display: block; }
.list--cards .metric__value { font-size: 1.0625rem; }
.list--cards .unit__more { display: none; }
.list--cards .detail { grid-column: 1 / -1; }

.empty {
  padding: 3rem 0.75rem;
  text-align: center;
  color: var(--muted);
}
.empty strong { display: block; margin-bottom: 0.35rem; color: var(--text); }

/* --- Раскрытая карточка --------------------------------------------------- */

.detail {
  padding: 0.25rem 0.75rem 1.1rem;
  border-top: 1px solid var(--line);
  background: var(--rack);
}
.list--cards .detail { border-radius: 0 0 var(--radius) var(--radius); }

.detail__bar {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  padding: 0.75rem 0;
}
.detail__ip {
  margin-right: auto;
  font-family: var(--f-mono);
  font-size: 0.8125rem;
  color: var(--muted);
}

.charts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: 0.75rem;
}
.chart {
  padding: 0.6rem 0.7rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.chart__head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.5rem;
  margin-bottom: 0.4rem;
}
.chart__last {
  font-family: var(--f-mono);
  font-variant-numeric: tabular-nums;
  font-size: 0.8125rem;
}
.chart svg { display: block; width: 100%; height: 48px; overflow: visible; }
.chart__line { fill: none; stroke: var(--text); stroke-width: 1.25; stroke-linejoin: round; }
.chart__area { fill: rgba(237, 230, 224, 0.06); stroke: none; }
.chart__empty { color: var(--muted); font-size: 0.8125rem; }

.facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
  gap: 0.6rem;
  margin-top: 0.75rem;
}
.fact dt { margin-bottom: 0.2rem; }
.fact dd {
  font-family: var(--f-mono);
  font-variant-numeric: tabular-nums;
  font-size: 0.875rem;
}

/* --- Диалоги -------------------------------------------------------------- */

dialog {
  width: min(30rem, calc(100vw - 2rem));
  /* Панель «Сеть» на телефоне выше экрана: без потолка и прокрутки её нижние
     кнопки оказываются за краем. */
  max-height: calc(100vh - 2rem);
  overflow-y: auto;
  margin: auto;
  padding: 1.25rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--rack);
  color: var(--text);
}
dialog::backdrop { background: rgba(10, 8, 7, 0.72); }
dialog h2 {
  font-family: var(--f-display);
  font-size: 0.8125rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
}
dialog p { margin-top: 0.5rem; color: var(--muted); font-size: 0.875rem; }

.field { margin-top: 0.9rem; }
.field label { display: block; margin-bottom: 0.3rem; }
.field input, .field textarea {
  width: 100%;
  padding: 0.55rem 0.7rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--ash);
  font-size: 0.9375rem;
}
.field textarea { resize: vertical; font-family: var(--f-mono); font-size: 0.8125rem; }
.field--inline { display: flex; align-items: center; gap: 0.5rem; }
.field--inline input { width: auto; accent-color: var(--ember); }
.field--inline label { margin: 0; }
.field__hint { margin-top: 0.3rem; font-size: 0.75rem; color: var(--muted); }

.row-actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin-top: 1.25rem;
}

.form-error {
  margin-top: 0.75rem;
  padding: 0.5rem 0.7rem;
  border-left: 3px solid var(--critical);
  background: var(--ash);
  font-size: 0.875rem;
  color: var(--text);
}
.form-error:empty { display: none; }

.panel__section + .panel__section {
  margin-top: 1.25rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--line);
}

.progress {
  height: 4px;
  margin-top: 0.75rem;
  border-radius: 2px;
  background: var(--line);
  overflow: hidden;
}
.progress__bar {
  height: 100%;
  width: 0;
  background: var(--ember);
  transition: width var(--beat) linear;
}

.found { margin-top: 0.75rem; max-height: 14rem; overflow-y: auto; }
.found__item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.4rem 0;
  border-bottom: 1px solid var(--line);
  font-size: 0.875rem;
}
.found__item input { accent-color: var(--ember); }
.found__ip { font-family: var(--f-mono); font-size: 0.8125rem; color: var(--muted); }

/* Короткое сообщение об исходе действия. Причина от майнера приводится
   целиком: «ошибка» без причины не помогает никому. */
.toast {
  position: fixed;
  left: 50%;
  bottom: calc(1.25rem + env(safe-area-inset-bottom));
  z-index: 80;
  max-width: min(28rem, calc(100vw - 2rem));
  padding: 0.7rem 1rem;
  transform: translateX(-50%);
  border: 1px solid var(--line);
  border-left: 3px solid var(--steady);
  border-radius: var(--radius);
  background: var(--rack);
  font-size: 0.875rem;
}
.toast[data-tone='bad'] { border-left-color: var(--critical); }
.toast[hidden] { display: none; }

/* --- Кошельки и пул ------------------------------------------------------- */

/* Раздел внешних данных. Цвет здесь появляется ровно в одном месте — у
   процента отказов воркера, и только выше порога. Курс не раскрашивается
   никогда: рост и падение — не авария, а цветом здесь говорят о проблемах.
   Отметка времени, наоборот, есть у всего: чужой источник молчит чаще
   собственной фермы, и цифра без возраста в этот момент опаснее пустоты. */

.market {
  margin-top: 1.75rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--line);
}

.market__head {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
}
.market__head .overline { flex: 0 0 auto; }
.market__head .button { margin-left: auto; }

.market__age,
.market__note {
  color: var(--muted);
  font-size: 0.8125rem;
}

/* Второй dd в сводке: возраст курса под самим курсом. Появляется только тогда,
   когда курс перестал обновляться, поэтому пустым он места не занимает. */
.totals__note {
  margin-top: 0.3rem;
  color: var(--muted);
  font-family: var(--f-body);
  font-size: 0.75rem;
  font-weight: 400;
  line-height: 1.2;
}
.totals__note:empty { display: none; }

.market__block { margin-top: 1rem; }

.market__sum {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
}
.market__sum .num {
  font-size: clamp(1.125rem, 3.5vw, 1.5rem);
  font-variant-numeric: tabular-nums;
}

.market__rows { margin-top: 0.5rem; }

.wallet,
.worker {
  display: grid;
  gap: 0.5rem 0.75rem;
  align-items: baseline;
  padding: 0.55rem 0.75rem;
  border-bottom: 1px solid var(--line);
}
.wallet { grid-template-columns: minmax(6rem, 1fr) 8.5rem 6rem 7rem; }
.worker { grid-template-columns: minmax(6rem, 1fr) 7rem 4.5rem 5.5rem; }
.wallet > *:not(:first-child),
.worker > *:not(:first-child) { text-align: right; }

.wallet__id {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  min-width: 0;
}
.wallet__name {
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.wallet__address,
.wallet__usd,
.wallet__note,
.worker__seen {
  color: var(--muted);
  font-size: 0.8125rem;
}
.wallet__btc,
.worker__hashrate,
.worker__reject {
  font-family: var(--f-mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
}

.worker__name {
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Воркер, которого пул не считает активным, тускнеет — как строка молчащего
   майнера. Это не тревога: пул сам решает, кого показывать активным. */
.worker[data-state='offline'] { color: var(--muted); }

/* Единственный цвет в разделе. Порог приходит с сервера (app.py,
   POOL_REJECT_*), поэтому раскраска и решение о ней не расходятся. */
.worker__reject[data-heat='warm'] { color: var(--flare); }
.worker__reject[data-heat='hot'] { color: var(--ember); }

/* --- Вход ----------------------------------------------------------------- */

.login {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  padding: 1.5rem 0;
}
.login__box { width: min(22rem, 100%); }
.login__box h1 {
  font-family: var(--f-display);
  font-weight: 700;
  font-size: 1rem;
  letter-spacing: 0.3em;
  text-transform: uppercase;
}
.login__box .field:first-of-type { margin-top: 1.75rem; }
/* Поле на странице входа стоит прямо на фоне: с заливкой в цвет фона оно
   читается как дырка. В диалогах фон другой, там заливка --ash уместна. */
.login .field input { background: var(--rack); }
.login__note { margin-top: 0.35rem; color: var(--muted); font-size: 0.875rem; }
.login__box button { width: 100%; margin-top: 0.9rem; }

/* --- Телефон -------------------------------------------------------------- */

@media (max-width: 40rem) {
  :root { --metric-columns: 5.5rem 3.5rem 2rem; }

  /* На телефоне доллары и метка кошелька уезжают на вторую строку: три числа
     в ряд на 360 точках превращаются в столбец из переносов. */
  .wallet { grid-template-columns: minmax(0, 1fr) auto; }
  .worker { grid-template-columns: minmax(0, 1fr) auto auto; }
  .wallet__note, .worker__seen { grid-column: 1 / -1; text-align: left; }

  /* Отметка свежести уезжает на свою строку: рядом с логотипом и двумя
     кнопками она не помещается, а прятать её нельзя — это единственное, что
     говорит о живости поллера. */
  .freshness { order: 3; flex-basis: 100%; }

  .rack__cell { height: 24px; }
  .totals { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .toolbar { gap: 0.4rem; }
  .search { flex-basis: 100%; order: -1; }

  /* На 390 точках подпись у кнопки добавления стоит третьей строки панели. */
  .button__label { display: none; }

  /* Мощность и давность уходят в раскрытую карточку: на 360 точках ширины
     четвёртая колонка чисел превращается в кашу. */
  .metric--power, .metric--age, .columns__metrics .col-power, .columns__metrics .col-age {
    display: none;
  }
  .list--cards .metric--power, .list--cards .metric--age { display: block; }
  /* В карточках эти числа остаются на экране — значит, остаётся и сортировка
     по ним: спрятанная кнопка убрала бы её вместе с собой. */
  body[data-view='cards'] .columns__metrics .col-power,
  body[data-view='cards'] .columns__metrics .col-age { display: block; }
  .list--cards { grid-template-columns: 1fr; }
}

/* --- Движение ------------------------------------------------------------- */

/* Единственное движение: при новом снимке изменившееся число коротко
   подсвечивается, а клетка в полосе меняет цвет. Спиннера при обновлении нет —
   данные и так приходят из фонового поллера, и вращающийся круг сообщал бы
   только о существовании таймера. */
@keyframes settle {
  from { color: var(--flare); }
  to   { color: inherit; }
}
.is-fresh { animation: settle var(--beat) ease-out; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }
  .rack__cell:hover { transform: none; }
}
