/* Цвета не подбирались на глаз: обе палитры прогнаны валидатором и
     прошли проверки на различимость при дальтонизме, контраст и полосу
     светлоты (тёмная — худшая пара ΔE 8.4, светлая — 9.1 при пороге 8).
     Менять хексы по одному нельзя: только всем набором и с повторной
     проверкой, потому что проверяются ПАРЫ, а не отдельные цвета. */
  /* СВЕТЛАЯ тема — базовая. Это не «инверсия» тёмной: у каждой свои шаги
     тех же оттенков, подобранные под свою поверхность и проверенные
     отдельно. Автоматический флип дал бы нечитаемые цвета. */
  :root{
    color-scheme:light;
    --surface:#fcfcfb;
    --panel:#f4f4f1;
    --panel2:#eceae5;
    --line:#dedcd5;
    --fg:#0b0b0b;
    --dim:#52514e;
    --off:#8a8880;
    --accent:#2a78d6;

    /* Серии — фиксированный порядок, не по кругу. */
    --s1:#2a78d6;  /* хешрейт */
    --s2:#eb6834;  /* шар/час */
    --s3:#1baf7a;  /* температура */
    --s4:#eda100;  /* потребление */

    /* Статусы зарезервированы и НЕ меняются между темами: они не серии.
       На светлом фоне warning и serious ниже 3:1 намеренно — поэтому
       статус всегда идёт со значком и словом, а не одним цветом. */
    --ok:#0ca30c; --warn:#fab219; --serious:#ec835a; --bad:#d03b3b;
    --log-bg:#f7f7f4;
  }

  /* Тёмная тема двумя путями: системная настройка и явный выбор
     пользователя. Явный выбор обязан побеждать в обе стороны, поэтому
     медиа-запрос ограничен :not([data-theme="light"]). */
  @media (prefers-color-scheme: dark){
    :root:not([data-theme="light"]){
      color-scheme:dark;
      --surface:#1a1a19; --panel:#222220; --panel2:#2b2b28; --line:#383835;
      --fg:#ffffff; --dim:#c3c2b7; --off:#7a796f; --accent:#3987e5;
      --s1:#3987e5; --s2:#d95926; --s3:#199e70; --s4:#c98500;
      --log-bg:#111110;
    }
  }
  :root[data-theme="dark"]{
    color-scheme:dark;
    --surface:#1a1a19; --panel:#222220; --panel2:#2b2b28; --line:#383835;
    --fg:#ffffff; --dim:#c3c2b7; --off:#7a796f; --accent:#3987e5;
    --s1:#3987e5; --s2:#d95926; --s3:#199e70; --s4:#c98500;
    --log-bg:#111110;
  }
  /* Высота шапки. Значение здесь -- ТОЛЬКО запас на случай, если скрипт не
     отработал: настоящее ставит stickyhdr.js замером живого элемента.
     Зашитое число врало и накрывало названия столбцов: на 1280px шапка
     54px против записанных 45, а когда она переносится на вторую строку
     (появился значок аварии хаба) -- 92.5px, то есть половина шапки поверх
     заголовков. На телефоне это неважно: там th{position:static}. */
  :root{--hdr-h:45px;--content-max:1600px}

  *{box-sizing:border-box}
  body{margin:0;background:var(--surface);color:var(--fg);
       font:16px/1.5 ui-sans-serif,system-ui,"Segoe UI",Roboto,sans-serif}
  header{
         border-bottom:1px solid var(--line);background:var(--panel);
         position:sticky;top:0;z-index:20}
  .header-inner{display:flex;align-items:center;gap:16px;flex-wrap:wrap;
    width:100%;max-width:var(--content-max);margin:0 auto;padding:12px 20px}
  header h1{display:flex;align-items:center;gap:8px;flex-shrink:0;white-space:nowrap}
  .brand-logo{display:block;width:31px;height:40px;object-fit:contain;flex:none;background:transparent}
  .login-card{max-width:320px;margin:32px auto 60px}
  .login-brand{display:block;background:transparent;box-shadow:none;width:220px;max-width:75%;margin:0 auto 20px}
  .login-brand img{display:block;width:100%;height:auto;object-fit:contain;background:transparent;box-shadow:none}
  @media(max-height:650px){.login-card{margin-top:16px}.login-brand{width:150px;margin-bottom:14px}}
  #summary{min-width:0;overflow-wrap:anywhere}
  h1{font-size:17px;margin:0;letter-spacing:.4px;font-weight:600}
  .sub{color:var(--dim);font-size:14px}
  /* 🔴 max-width без margin:auto не центрирует -- блок просто упирается в
     потолок ширины и висит у ЛЕВОГО края: на широком экране (или на
     уменьшенном масштабе браузера, который расширяет видимую область точно
     так же) вся страница уезжает влево, пока справа пустота. */
  main{padding:18px 20px 60px;max-width:var(--content-max);margin:0 auto}

  /* Сводка парка: числа крупные, подписи мелкие — читается на расстоянии. */
  .kpis{display:grid;grid-template-columns:repeat(auto-fit,minmax(165px,1fr));
        gap:10px;margin-bottom:18px}
  .kpi{min-width:0;padding:14px 16px;background:var(--panel);
       border:1px solid var(--line);border-radius:10px}
  .kpi .v{font-size:24px;line-height:1.1;font-weight:600}
  .kpi .l{font-size:13px;color:var(--dim);text-transform:uppercase;
          letter-spacing:.5px;margin-top:7px}
  /* Плитка аварий ведёт к их списку -- и должна выглядеть нажимаемой.
     Подчёркивание пунктиром, а не цвет: цвет числа тут уже занят смыслом
     (красный -- есть аварии), и вторая роль у того же признака делает
     неразличимыми «тут беда» и «сюда можно нажать». */
  .kpi-click{cursor:pointer}
  .kpi-click .l{text-decoration:underline dotted;text-underline-offset:3px}
  .kpi-click:focus-visible{outline:2px solid var(--accent);outline-offset:3px;border-radius:4px}

  .grid{display:grid;gap:12px;grid-template-columns:repeat(auto-fill,minmax(300px,1fr))}
  .fleet-tabs{display:flex;gap:4px;overflow-x:auto;overscroll-behavior-inline:contain}
  .fleet-tabs .tab{flex:none;white-space:nowrap}
  .fleet-sort{display:inline-flex;align-items:center;gap:6px;color:var(--dim);font-size:14px}
  .fleet-sort select{min-height:36px;background:var(--panel);color:var(--fg);
    border:1px solid var(--line);border-radius:6px;padding:4px 8px}
  .fleet-row{display:grid;grid-template-columns:minmax(155px,1.4fr) 76px minmax(100px,1fr)
    minmax(105px,1.1fr) minmax(100px,1.1fr) 70px 76px minmax(100px,1fr);
    grid-template-areas:"name age algo hash gpu temp power fans";align-items:center;gap:10px;
    min-height:48px;padding:5px 10px;border-bottom:1px solid var(--line);background:var(--surface)}
  .fleet-row:hover{background:var(--panel)}
  .fleet-row-name{grid-area:name;display:flex;align-items:center;gap:7px;min-width:0}
  .fleet-rig-link{font-weight:600;color:var(--fg);text-decoration:none;overflow-wrap:anywhere}
  .fleet-rig-link:hover{text-decoration:underline;color:var(--accent)}
  .fleet-row-age{grid-area:age;color:var(--dim);font-size:14px;white-space:nowrap}
  .fleet-row-algo{grid-area:algo;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .fleet-row-hash{grid-area:hash;white-space:nowrap}
  .fleet-row-gpu{grid-area:gpu;white-space:nowrap}
  .fleet-row-temp{grid-area:temp;white-space:nowrap}
  .fleet-row-power{grid-area:power;white-space:nowrap}
  .fleet-row-fans{grid-area:fans;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .fleet-row-pick{flex:none}
  .card{background:var(--panel);border:1px solid var(--line);border-radius:10px;
        padding:13px 14px;cursor:pointer;transition:border-color .12s,transform .12s}
  .card:hover{border-color:var(--accent)}
  .card:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
  /* Проблемный риг подсвечивается полосой слева — форма, а не только цвет. */
  .card.alarm{border-left:3px solid var(--bad)}
  .card.attn{border-left:3px solid var(--warn)}
  .card h2{margin:0 0 6px;font-size:17px;display:flex;align-items:center;
           gap:7px;font-weight:600}

  /* Статус: точка + СЛОВО. Цвет никогда не несёт смысл в одиночку -- слово
     остаётся при любой раскраске, это только усиление, не замена. */
  .st{display:inline-flex;align-items:center;gap:5px;font-size:13px;
      padding:1px 7px;border-radius:10px;border:1px solid var(--line);
      color:var(--dim);white-space:nowrap}
  .st .dot{width:7px;height:7px;border-radius:50%;flex:none}
  /* 🔴 Раньше цвет нёс только точка -- бейдж вокруг оставался серым даже у
     здорового рига, и вся полоса читалась одним тоном. Теперь у бейджа
     свой цвет (заливка + рамка + текст), а точка внутри так и остаётся
     сплошной -- это единственное место, где заливка была бы неотличима
     от фона. */
  .st.ok{background:color-mix(in srgb, var(--ok) 16%, transparent);
         border-color:var(--ok);color:var(--ok)}
  .st.warn{background:color-mix(in srgb, var(--warn) 18%, transparent);
           border-color:var(--warn);color:var(--warn)}
  .st.bad{background:color-mix(in srgb, var(--bad) 16%, transparent);
          border-color:var(--bad);color:var(--bad)}
  .st.na{background:color-mix(in srgb, var(--off) 16%, transparent);
         border-color:var(--off);color:var(--off)}

  .row{display:flex;justify-content:space-between;gap:10px;padding:2px 0}
  .row span:first-child{color:var(--dim)}
  .mono{font-variant-numeric:tabular-nums;
        font-family:ui-monospace,"Cascadia Mono",Consolas,monospace}
  /* Крупные одиночные числа — пропорциональными цифрами: у tabular-nums
     на таком кегле появляются дыры между знаками. */
  .kpi .v{font-variant-numeric:proportional-nums}

  table{width:100%;border-collapse:collapse;margin-top:10px;font-size:15px}
  th,td{padding:7px 9px;text-align:right;border-bottom:1px solid var(--line);
        font-variant-numeric:tabular-nums}
  th{color:var(--dim);font-weight:500;font-size:13px;text-align:right;
     text-transform:uppercase;letter-spacing:.4px;position:static;
     background:var(--surface);overflow-anchor:none}
  th:first-child,td:first-child{text-align:left}
  tbody tr:hover{background:var(--panel)}
  .na{color:var(--off)}

  /* Липкая полоса рига. Липнет к ИЗМЕРЕННОЙ высоте шапки (--hdr-h), а не к
     своему числу: второе зашитое число разъехалось бы ровно так же, как
     первое. z-index ниже шапки (20), чтобы уходить под неё, а не поверх. */
  /* overflow-anchor:none -- липкий элемент не должен становиться якорем
     прокрутки. Когда высота шапки всё-таки меняется (свежесть данных
     сменилась на «устарели N с назад»), его top уезжает, и браузер тянет
     за ним всю страницу. Читающему список это видно как рывок. */
  .rigbar{position:sticky;top:var(--hdr-h);z-index:15;overflow-anchor:none;
          display:flex;align-items:center;gap:10px;flex-wrap:wrap;
          background:var(--panel);border:1px solid var(--line);
          border-radius:8px;padding:8px 12px;margin:0 0 10px}
  .rigbar .nm{font-size:19px;margin:0}
  .rigbar .acts{margin-left:auto;display:flex;gap:6px;flex-wrap:wrap}
  /* Navigation and rig controls share one sticky boundary and header offset. */
  .rig-sticky{position:sticky;top:var(--hdr-h);z-index:15;overflow-anchor:none;
              background:var(--surface);padding-top:6px;margin-bottom:10px}
  .rig-sticky .back{margin:0}
  .rig-sticky .rigbar{position:relative;top:auto;z-index:auto;margin-bottom:0}
  @media (max-width:640px){
    /* На телефоне полоса несёт ТОЛЬКО опознание: имя, состояние, свежесть.
       С кнопками она занимала 106px, и вместе с шапкой это пятая часть
       экрана -- ровно та беда, которую чинил M3, только на новом месте.
       Экран тут дефицит, и тратить его надо на то, что иначе не получить:
       «какой это риг и жив ли он» при прокрутке. Кнопки остаются в разделе
       «Управление» -- до них один жест, и они никуда не делись. */
    .rigbar .acts{display:none}
  }

  /* Чипы консоли на карточке. Выглядят нажимаемыми, но не кричат: попасть в
     консоль надо изредка, а карточка про состояние рига. */
  .chip.lnk{cursor:pointer;color:var(--accent);border-color:var(--line);
            text-decoration:none;font-weight:500}
  .chip.lnk:hover{border-color:var(--accent)}
  .chip.lnk:focus-visible{outline:2px solid var(--accent);outline-offset:2px}

  /* Меню способов войти в консоль -- «окошечко» у кнопки Shell.
     🔴 [hidden] ОБЯЗАН побеждать: display:flex ниже задан по классу, а это
     сильнее браузерного правила для [hidden], и меню висело открытым
     всегда. Правило-исключение пишем ПЕРВЫМ и явно. */
  .shellmenu[hidden]{display:none}
  .shellmenu{position:absolute;right:0;top:calc(100% + 6px);z-index:30;
             min-width:250px;display:flex;flex-direction:column;gap:6px;
             background:var(--panel);border:1px solid var(--line);
             border-radius:8px;padding:12px;box-shadow:0 6px 20px rgba(0,0,0,.28)}
  .shellmenu .btn{width:100%;text-align:left;text-decoration:none}
  .shellmenu .sub{margin:-2px 0 4px}
  /* Полоса рига -- система координат для меню, иначе оно уедет к краю окна. */
  .rigbar{position:sticky}

  .back{background:none;border:1px solid var(--line);color:var(--fg);
        padding:5px 12px;border-radius:6px;cursor:pointer;margin-bottom:14px}
  .back:hover{border-color:var(--accent)}
  .sect{margin-top:18px;padding:16px;background:var(--panel);
        border:1px solid var(--line);border-radius:10px;min-width:0}
  .sect h3{font-size:13px;color:var(--dim);text-transform:uppercase;
           letter-spacing:.7px;margin:0 0 8px;font-weight:600}
  /* Заголовок раздела локации. Отбивка сверху крупнее, чем у обычной
     секции: разделы идут подряд, и без неё группы сливаются в одну простыню,
     то есть группировка перестаёт что-либо давать. */
  .locgroup{margin:22px 0 8px;border:0;border-top:1px solid var(--line);
            border-radius:0;background:transparent;padding:12px 0 0}
  .locgroup:first-child{margin-top:4px;border-top:none;padding-top:0}
  .locgroup h3{font-size:15px;color:var(--fg);text-transform:none;
               letter-spacing:0;margin:0;font-weight:600;
               display:flex;align-items:baseline;gap:10px}
  .locsum{margin-left:auto;font-size:14px;color:var(--dim);font-weight:400}
  .kv{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:4px 22px}
  .empty{color:var(--dim);padding:40px 0;text-align:center}
  .pill{font-size:13px;padding:1px 7px;border-radius:10px;
        border:1px solid var(--line);color:var(--dim)}
  .bad{color:var(--bad)} .warn{color:var(--warn)} .ok{color:var(--ok)} .cool{color:var(--accent)}
  .btn{background:var(--panel2);border:1px solid var(--line);color:var(--fg);
       padding:6px 12px;border-radius:6px;cursor:pointer;font-size:15px}
  .btn:hover{border-color:var(--accent)}
  .btn:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
  .btn.on{border-color:var(--accent);color:var(--fg)}

  /* Вкладки отличаются от команд: выбранный раздел отмечен нижней кромкой. */
  .tabbar{border-bottom:1px solid var(--line);padding-bottom:6px}
  .tabbar .tab{background:transparent;border-color:transparent;
               border-radius:6px 6px 0 0;padding:9px 12px;color:var(--dim)}
  .tabbar .tab:hover{color:var(--fg);background:var(--panel)}
  .tabbar .tab.on{color:var(--accent);border-bottom:2px solid var(--accent);
                  background:var(--panel)}
  .tabpanel .sect:first-child{margin-top:14px}
  .sect table th{background:var(--panel)}
  .sect table tbody tr:hover{background:var(--panel2)}
  .sect td{padding-top:10px;padding-bottom:10px}
  .kpi-click:hover{border-color:var(--accent)}

  /* Кнопка-значок: квадратная, чтобы стрелка вращалась вокруг своего центра,
     а не съезжала. Кегль крупнее текста кнопок -- значок без подписи должен
     читаться сам. */
  .btn.icon{padding:4px 9px;font-size:18px;line-height:1.2;min-width:34px}
  /* Пока идёт обновление -- значок крутится. Это единственный ответ на
     вопрос «нажатие сработало?»: данные могут прийти теми же, и тогда на
     экране не изменится ничего. */
  @keyframes spin{to{transform:rotate(360deg)}}
  .btn.icon[data-busy="1"]{color:var(--accent);border-color:var(--accent);
                           animation:spin .7s linear infinite}
  @media (prefers-reduced-motion: reduce){
    /* Кому движение мешает -- показываем состояние цветом, без вращения. */
    .btn.icon[data-busy="1"]{animation:none}
  }
  /* Перезапуск панели -- действие другого веса, чем обновление данных, и
     выглядеть обязано иначе: красная кромка вместо обычной. Рядом с ↻ их
     слишком легко перепутать наощупь. */
  .btn.icon.danger{border-color:var(--bad);color:var(--bad)}
  .btn.icon.danger:hover{background:var(--bad);color:#fff}
  /* Полоса на время перезапуска: панели в эти секунды физически нет, и
     единственная честная вещь на экране -- строка о том, что происходит. */
  #restartbar{position:fixed;left:0;right:0;top:0;z-index:70;
              background:var(--accent);color:#fff;text-align:center;
              padding:7px 12px;font-size:15px}
  #restartbar.bad{background:var(--bad)}
  /* Запрещённая кнопка обязана ВЫГЛЯДЕТЬ запрещённой. Одного атрибута
     disabled мало: браузер её приглушает еле заметно, и человек продолжает
     жать, думая, что промахнулся. Курсор тоже меняем -- он отвечает на
     вопрос «оно вообще нажимается?» раньше любой подсказки. */
  .btn[disabled]{opacity:.45;cursor:not-allowed}
  .btn[disabled]:hover{border-color:var(--line)}

  /* Скелетон вместо слова «загрузка…».
     Разница не косметическая: слово «загрузка…» на месте таблицы читается
     как «тут ничего нет», а серые полосы показывают, ЧТО именно приедет и
     сколько места займёт -- страница не прыгает, когда данные пришли.
     Движение приглушаем для тех, кому оно мешает: анимация ради анимации
     в панели мониторинга не нужна. */
  .skel{display:block;height:12px;border-radius:6px;margin:9px 0;
        background:linear-gradient(90deg,var(--panel2) 25%,var(--line) 37%,var(--panel2) 63%);
        background-size:400% 100%;animation:skel 1.4s ease infinite}
  .skel.w70{width:70%}
  .skel.w45{width:45%}
  @keyframes skel{0%{background-position:100% 50%}100%{background-position:0 50%}}
  @media (prefers-reduced-motion: reduce){.skel{animation:none}}
  pre.log{background:var(--log-bg);border:1px solid var(--line);border-radius:8px;
          padding:11px;max-height:460px;overflow:auto;font-size:14px;
          font-family:ui-monospace,Consolas,monospace;white-space:pre;margin:8px 0 0}
  .msg{display:flex;gap:10px;padding:5px 0;border-bottom:1px solid var(--line)}
  .msg:last-child{border-bottom:none}
  .msg .t{color:var(--dim);white-space:nowrap;font-variant-numeric:tabular-nums}
  /* Соседний с .t span несёт произвольный текст -- хеши транзакций, адреса,
     detail от рига -- без единого пробела. Флекс-айтем по умолчанию не
     сжимается уже своего контента (min-width:auto), и такая строка раздвигает
     .msg, а с ней и всю страницу вбок (поймано в мобильной оболочке: 655px
     вьюпорт при экране 320-412px, источник -- 64-символьный sha256 в журнале
     аудита). min-width:0 разрешает сжатие, overflow-wrap перенос по месту. */
  .msg > *:not(.t){min-width:0;overflow-wrap:anywhere}

  /* --- графики --- */
  /* Контейнер позиционирован: подсказка одна на все графики и ставится
     относительно него, а не каждого по отдельности. */
  #charts, #fleetchart{position:relative}
  .chart-row{margin-bottom:2px}
  .chart-row svg, #charts>svg, #fleetchart>svg{display:block;width:100%;height:auto}
  .tip{position:absolute;pointer-events:none;background:var(--panel2);
       border:1px solid var(--line);border-radius:6px;padding:6px 9px;
       font-size:14px;white-space:nowrap;opacity:0;transition:opacity .08s;
       box-shadow:0 4px 14px rgba(0,0,0,.45);z-index:5}
  .tip .val{font-weight:600;font-variant-numeric:tabular-nums}
  .tip .ts{color:var(--dim);font-size:13px}
  .tip .key{display:inline-block;width:14px;height:2px;vertical-align:middle;
            margin-right:6px;border-radius:1px}

  /* Панель фильтров: ОДНА строка над содержимым, которое она сужает. */
  .filters{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:14px}
  .wdnum{background:var(--panel2);border:1px solid var(--line);color:var(--fg);
    padding:3px 7px;border-radius:5px;width:120px;font-size:14px}
  .filters input{background:var(--panel2);border:1px solid var(--line);
    color:var(--fg);padding:6px 10px;border-radius:6px;font-size:15px;min-width:180px}
  .filters input:focus{outline:2px solid var(--accent);outline-offset:1px}
  .chips{display:flex;gap:5px;flex-wrap:wrap;margin:2px 0 8px}
  .chip{font-size:13px;padding:1px 7px;border-radius:9px;background:var(--panel2);
        border:1px solid var(--line);color:var(--dim);white-space:nowrap}
  /* Локация отличается от тега видом: это не ещё один ярлык из того же
     ряда, а место, где риг стоит. Значок вместо подписи -- потому что
     подпись «локация:» на каждой плитке съела бы строку. */
  .chip.loc{color:var(--fg);border-color:var(--accent)}
  .chip.loc::before{content:"◉ ";color:var(--accent)}
  .btn.tag{font-size:14px;padding:3px 9px;border-radius:9px}
  .filters.bulk{background:var(--panel);border:1px solid var(--accent);
    border-radius:8px;padding:8px 10px}
  .card h2 input[type=checkbox],table.rigs input[type=checkbox]{accent-color:var(--accent);cursor:pointer}
  .spacer{flex:1}

  /* Вид таблицей — альтернатива плиткам, а не замена: на десяти ригах
     плитки нагляднее, на полусотне таблица позволяет сравнивать строки. */
  table.rigs td.name{font-weight:600}
  table.rigs tr{cursor:pointer}
  table.rigs th.sortable{cursor:pointer;user-select:none}
  table.rigs th.sortable:hover{color:var(--fg)}

  @media (max-width:600px){
    main{padding:14px 12px 50px}
    .header-inner{padding:10px 12px}
    .grid{grid-template-columns:1fr}
    .kpis{gap:16px}
    /* На узком экране таблица карт прокручивается сама, а не растягивает
       страницу по горизонтали. */
    .scroll-x{overflow-x:auto}
    th{position:static}
  }

/* --- диалоги ------------------------------------------------------------
   Окно живёт в <body>, поэтому перерисовка #view раз в пять секунд его не
   трогает. Цвета -- только через переменные темы: диалог обязан читаться и
   в светлой, и в тёмной. */
.modal-back{position:fixed;inset:0;background:rgba(0,0,0,.55);z-index:50;
  display:flex;align-items:center;justify-content:center;padding:16px}
.modal{background:var(--surface);border:1px solid var(--line);border-radius:10px;
  width:min(560px,100%);max-height:88vh;display:flex;flex-direction:column;
  box-shadow:0 12px 40px rgba(0,0,0,.35)}
.modal.danger{border-color:var(--bad)}
.modal-t{margin:0;padding:14px 18px;border-bottom:1px solid var(--line);
  font-size:17px;font-weight:600}
.modal-b{padding:14px 18px;overflow:auto}
.modal-f{padding:12px 18px;border-top:1px solid var(--line);
  display:flex;gap:8px;justify-content:flex-end}
.modal-intro{color:var(--dim);font-size:15px;margin-bottom:12px;white-space:pre-wrap}
.modal-text{white-space:pre-wrap;line-height:1.45}
.modal-row{display:block;margin-bottom:12px}
.modal-lab{display:block;font-size:15px;color:var(--dim);margin-bottom:4px}
.modal-row input[type=text],.modal-row input[type=number],
.modal-row input[type=datetime-local],.modal-row select{
  width:100%;background:var(--panel2);border:1px solid var(--line);color:var(--fg);
  border-radius:6px;padding:7px 9px;font:inherit}
.modal-row input:focus,.modal-row select:focus{outline:2px solid var(--accent);outline-offset:1px}
/* --dim, а не --off: у 12-пиксельного текста контраст --off к фону окна
   выходил 3.5 при норме 4.5 -- замерено, а не прикинуто на глаз. */
.modal-hint{display:block;font-size:14px;color:var(--dim);margin-top:4px}
.modal-checks{border:1px solid var(--line);border-radius:6px;padding:6px;
  max-height:200px;overflow:auto;background:var(--panel2);margin-bottom:6px}
.modal-check{display:flex;align-items:center;gap:8px;padding:3px 4px;cursor:pointer}
.modal-check:hover{background:var(--panel)}
.modal-note{color:var(--dim);font-size:14px;margin-left:auto}
/* Ошибка -- значком и словом, не одним цветом. */
.modal-err{margin:0 18px 12px;padding:8px 10px;border-radius:6px;
  background:var(--panel);border:1px solid var(--bad);color:var(--fg);font-size:15px}
.modal-err::before{content:"▲ ";color:var(--bad)}
.btn.primary{border-color:var(--accent)}
.btn.danger{border-color:var(--bad);color:var(--bad)}

/* Уведомления вместо alert: не блокируют страницу, панель продолжает
   обновляться, пока человек читает. */
#toasts{position:fixed;right:16px;bottom:16px;z-index:60;
  display:flex;flex-direction:column;gap:8px;max-width:min(420px,90vw)}
.toast{display:flex;align-items:flex-start;gap:8px;background:var(--surface);
  border:1px solid var(--line);border-left-width:3px;border-radius:8px;
  padding:10px 12px;box-shadow:0 6px 22px rgba(0,0,0,.28);font-size:15px;line-height:1.4}
.toast.info{border-left-color:var(--ok)}
.toast.error{border-left-color:var(--bad)}
.toast-x{flex:none}
.toast.error .toast-x{color:var(--bad)}
.toast.info .toast-x{color:var(--ok)}
.toast-close{margin-left:auto;background:none;border:0;color:var(--dim);
  cursor:pointer;font-size:18px;line-height:1;padding:0 2px}

/* --- плитки карт в списке парка -----------------------------------------
   Крашеная только проблема. Если раскрасить и норму, цвет перестаёт быть
   сигналом и становится фоном -- то же правило, что для пометки PCIe.
   Пороги 70/80 совпадают с tempClass(), 85 добавляет «критично». */
.gts{display:grid;grid-template-columns:repeat(auto-fill,minmax(62px,1fr));
  gap:5px;margin:8px 0 10px}
.gt{background:var(--panel2);border:1px solid var(--line);border-top-width:3px;
  border-radius:6px;padding:4px 5px;font-size:13px;line-height:1.25;min-width:0}
.gt-i{color:var(--dim);font-variant-numeric:tabular-nums;display:flex;
  justify-content:space-between;gap:2px}
.gt-m{font-weight:700}
.gt-t{font-size:16px;font-weight:600;font-variant-numeric:tabular-nums}
.gt-f{color:var(--dim);font-variant-numeric:tabular-nums}
.gt-h{color:var(--dim);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* Норма -- нейтральная рамка, а не зелёная: глаз должен цепляться только
   за отклонения. */
.gt-cool{border-top-color:var(--line)}
.gt-warm{border-top-color:var(--warn)}
.gt-hot {border-top-color:var(--serious)}
.gt-crit{border-top-color:var(--bad)}
.gt-none{border-top-color:var(--off);border-top-style:dashed}
.gt-crit .gt-t{color:var(--bad)}
.gt-hot  .gt-t{color:var(--serious)}
.gt-crit .gt-m,.gt-hot .gt-m{color:var(--bad)}
/* Простаивающая карта приглушена целиком: она не «плохая», она не в деле. */
.gt-idle{opacity:.62}
.gt-idle .gt-h{font-style:italic}

/* Сжатая полоска для табличного вида: места на плитки нет, но ответ
   «какая карта красная» нужен и там. */
.gstrip{display:inline-flex;gap:2px;vertical-align:middle}
.gs{display:inline-flex;align-items:center;justify-content:center;
  width:16px;height:16px;border-radius:3px;font-size:12px;font-style:normal;
  background:var(--panel2);border:1px solid var(--line);border-bottom-width:2px;
  color:var(--dim);font-variant-numeric:tabular-nums}

/* --- узкий экран ---------------------------------------------------------
   Сценарий, ради которого панель вообще нужна ночью: «упало, смотрю с
   телефона». Мерил на 375 px -- вёрстка не рвалась, но пользоваться было
   нельзя: флажок выбора рига 13 px, кнопки 22-29 px при рекомендованных 44,
   таблица парка 644 px на экране 375. */
@media (max-width: 640px){
  body{font-size:17px}
  .header-inner{padding:8px 12px;gap:4px 10px}
  header h1{font-size:18px}
  main{padding:12px}

  /* Шапка съедала до 40% экрана телефона. Причина не в её содержимом, а в
     раскладке: семь элементов во flex-wrap разъезжались на четыре строки,
     причём .spacer (flex:1) РВАЛ строку гарантированно -- он растягивался
     на весь остаток и уводил всё, что за ним, на следующую.
     Собираем в две строки: имя и кнопка темы сверху, все бейджи -- ниже
     одной строкой с переносом. На широком экране всё остаётся как было. */
  header .spacer{display:none}
  header h1{flex:1 1 auto}
  header .btn{order:1}
  header .sub{order:2}

  /* Четыре показателя в ряд на 375 px дают по 90 px на число -- не
     читается. Два в ряд крупно полезнее четырёх мелко. */
  .kpis{display:grid;grid-template-columns:1fr 1fr;gap:8px}
  .kpi{min-width:0}
  .kpi .v{font-size:22px}
  .kpi{padding:12px}
  .sect{padding:12px}
  .tabbar .tab{padding:10px 12px}
  .fleet-row{grid-template-columns:repeat(3,minmax(0,1fr));grid-template-areas:
    "name name name" "age hash temp" "algo power fans" "gpu gpu gpu";gap:4px 8px;padding:8px}
  .fleet-row-name{min-height:34px}
  .fleet-row-gpu .gstrip{display:none}
  .fleet-sort{flex:1 1 100%;min-height:40px}
  .fleet-sort select{flex:1;font-size:18px}

  /* Палец -- не курсор. 44 px -- то, что просят и Material, и HIG; на 40 px
     промахи ещё случаются, а на 34 (чипы фильтров) случаются постоянно.
     Замеры сессии приложения через CDP: «← ко всем ригам» был 133x30,
     чипы фильтра ленты -- 34 px по высоте.
     .back отдельной строкой не по прихоти: это НЕ .btn, и правило выше его
     не задевало -- ровно поэтому кнопка возврата и осталась тридцатью
     пикселями, когда всё остальное подняли. */
  .btn,.back{min-height:44px;padding:10px 14px}
  .btn.tag{min-height:44px;padding:10px 14px}
  .filters input{min-height:40px;font-size:18px} /* 16px -- иначе iOS зумит форму */
  /* Специфичность должна совпасть с базовым правилом, иначе шрифт
     останется 15px, а iOS зумит любую форму с полем мельче 16px. */
  .modal-row input[type=text],.modal-row input[type=number],
  .modal-row input[type=datetime-local],.modal-row select{
    min-height:42px;font-size:18px}
  /* Сам квадратик 22px, но область нажатия расширена отступом: попасть
     большим пальцем в 22 пикселя без промаха нельзя. */
  /* Отступ на самом флажке браузер игнорирует -- расширяем ОБЁРТКУ.
     Отрицательное поле возвращает раскладку на место, чтобы увеличенная
     область не разъезжала заголовок карточки. */
  input[type=checkbox]{width:22px;height:22px}
  .pick{display:inline-flex;align-items:center;padding:11px;margin:-11px 2px -11px -11px}
  .modal-check{padding:10px 4px}

  /* Таблица парка из девяти колонок на телефоне нечитаема даже с
     прокруткой. Оставляем то, ради чего в неё смотрят: кто, в каком
     состоянии, сколько выдаёт и не горит ли. */
  table.rigs th:nth-child(4), table.rigs td:nth-child(4),
  table.rigs th:nth-child(7), table.rigs td:nth-child(7),
  table.rigs th:nth-child(8), table.rigs td:nth-child(8),
  table.rigs th:nth-child(9), table.rigs td:nth-child(9){display:none}
  table.rigs{font-size:16px}
  table.rigs td,table.rigs th{padding:8px 4px}
  /* Полоска карт в строке таблицы на телефоне только распирает её вширь:
     плитки уже показаны в карточном виде, а тут важнее уместить колонки. */
  table.rigs .gstrip{display:none}

  /* Диалог на всю ширину, кнопки в полный размер: попасть в «Отмена»
     шириной 70 px большим пальцем нельзя. */
  .modal-back{padding:0;align-items:flex-end}
  .modal{width:100%;max-height:92vh;border-radius:12px 12px 0 0}
  .modal-f{flex-direction:column-reverse}
  .modal-f .btn{width:100%;min-height:46px}
  #toasts{left:12px;right:12px;bottom:12px;max-width:none}

  /* Плитки карт крупнее: на телефоне подсказки по наведению нет вовсе,
     и цифра должна читаться сразу. */
  .gts{grid-template-columns:repeat(auto-fill,minmax(72px,1fr));gap:6px}
  .gt{font-size:14px;padding:6px}
  .gt-t{font-size:18px}
}

/* --- теплокарта парка ---------------------------------------------------
   Цветом кодируется ТОЛЬКО температура. Ступени те же, что у плиток карт
   (70/80/85): разъехавшиеся пороги на двух экранах одной панели -- готовый
   способ поспорить с самим собой. */
.heat{display:flex;flex-direction:column;gap:4px}
.hrow{display:flex;align-items:center;gap:8px;min-width:0}
.hname{flex:0 0 130px;font-size:14px;color:var(--dim);white-space:nowrap;
  overflow:hidden;text-overflow:ellipsis}
.hcells{display:flex;gap:3px;flex-wrap:wrap}
.hc{display:inline-flex;align-items:center;justify-content:center;
  width:30px;height:22px;border-radius:4px;font-style:normal;font-size:13px;
  font-variant-numeric:tabular-nums;border:1px solid var(--line);
  background:var(--panel2);color:var(--dim)}
/* Норма приглушена, а не зелёная: цвет должен цеплять только на отклонении. */
.hc-k{background:var(--panel2)}
.hc-w{background:color-mix(in srgb, var(--warn) 22%, var(--panel2));color:var(--fg)}
.hc-h{background:color-mix(in srgb, var(--serious) 34%, var(--panel2));color:var(--fg)}
.hc-c{background:color-mix(in srgb, var(--bad) 42%, var(--panel2));color:var(--fg);font-weight:600}
.hc-n{border-style:dashed}
.hc-idle{opacity:.5}
.hlegend{display:flex;align-items:center;gap:6px;flex-wrap:wrap;
  margin-top:8px;font-size:14px;color:var(--dim)}
.hlegend .hc{width:22px;height:16px}
@media (max-width: 640px){
  .hrow{flex-direction:column;align-items:flex-start;gap:2px;margin-bottom:6px}
  .hname{flex:none}
  .hc{width:34px;height:26px;font-size:14px}
}

/* Карта под тестами: не приглушена, а помечена рамкой -- она в порядке и
   занята делом, просто не майнингом. Приглушать её как отвалившуюся значит
   приучать глаз пропускать настоящий отвал. */
.gt-held{opacity:1;border-style:dashed;border-color:var(--accent)}
.gt-held .gt-h{color:var(--accent);font-style:normal}
.gs.gt-held{border-color:var(--accent);border-style:dashed}

/* Операции не раздвигают обзор длинным ответом агента. */
#operations { margin: 12px 0; padding: 10px 14px; border: 1px solid var(--line); border-radius: 10px; background: var(--panel); max-height: 240px; overflow: auto; }
#operations[hidden] { display: none; }
#operations .msg { white-space: pre-wrap; overflow-wrap: anywhere; }

/* Редактор полётного листа: блоки как в HiveOS, без растянутых полей. */
.flight-editor{width:min(1180px,96vw);max-width:1180px}.flight-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;margin:10px 0}.flight-grid label,.flight-grid>div{display:flex;flex-direction:column;gap:8px;min-width:0}.flight-grid select,.flight-grid button{max-width:100%;overflow:hidden;text-overflow:ellipsis}.flight-block{padding:14px;border:1px solid var(--border,#626667);border-radius:8px;margin-bottom:12px}.flight-tools{display:flex;gap:8px;flex-wrap:wrap;margin-top:12px}@media(max-width:760px){.flight-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(max-width:440px){.flight-grid{grid-template-columns:1fr}}

.flight-grid select{width:100%;background:var(--panel2);border:1px solid var(--line);color:var(--fg);border-radius:6px;padding:7px 9px;font:inherit;min-height:36px}.flight-grid select:focus{outline:2px solid var(--accent);outline-offset:1px}.flight-block{border-color:var(--line)}.flight-tools{padding-bottom:8px}.flight-editor .modal-b{padding:14px 18px}.flight-block{padding:10px;margin-bottom:8px}.flight-grid{margin:7px 0;gap:10px}

/* Широкие поля Custom: URL и аргументы остаются читаемыми. */
.miner-config-dialog{width:min(920px,96vw);max-width:920px}.miner-config-dialog.modal-compact{width:min(920px,96vw);max-width:920px}.miner-config-dialog .modal-row{display:block}.miner-config-dialog .modal-lab{margin-bottom:6px}.miner-config-dialog input,.miner-config-dialog select,.miner-config-dialog textarea{width:100%;box-sizing:border-box}.modal-row textarea{width:100%;box-sizing:border-box;background:var(--panel2);color:var(--fg);border:1px solid var(--line);border-radius:6px;padding:9px;font:inherit;resize:vertical;min-height:76px}.miner-config-dialog [name="install_url"],.miner-config-dialog [name="extra_config"]{font-family:monospace}.miner-config-dialog .modal-row{margin-bottom:10px}

/* Сохранённые листы: компактная строка и раскрытие по месту. */
.saved-sheet{display:grid;grid-template-columns:30px minmax(0,1fr) 48px 44px;gap:8px;align-items:start;margin-bottom:10px}.saved-sheet .btn{box-shadow:none}.saved-sheet-favorite{border:0;background:transparent;font-size:25px;padding:12px 0;min-width:0;color:var(--dim)}.saved-sheet-favorite.on{color:var(--s4);background:transparent}.saved-sheet-main{min-width:0;background:var(--panel2);border:1px solid var(--line);border-radius:6px}.saved-sheet-heading{list-style:none;display:grid;grid-template-columns:minmax(160px,1.3fr) minmax(120px,.8fr) minmax(140px,1fr) 28px;gap:16px;align-items:center;min-height:64px;padding:10px 16px;cursor:pointer;box-sizing:border-box}.saved-sheet-heading::-webkit-details-marker,.saved-sheet-menu>summary::-webkit-details-marker{display:none}.saved-sheet-heading:focus-visible,.saved-sheet-menu>summary:focus-visible{outline:2px solid var(--accent);outline-offset:2px}.saved-sheet-coin{display:flex;align-items:center;gap:12px;min-width:0}.saved-sheet-coin-icon{color:var(--s2);font-size:23px}.saved-sheet-coin small{display:block;color:var(--dim);font-size:12px;margin-top:3px;overflow-wrap:anywhere}.saved-sheet-pool{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dim)}.saved-sheet-miner{min-width:0;overflow-wrap:anywhere}.saved-sheet-miner small{display:block;font-size:12px;margin-top:3px}.saved-sheet-count{border-radius:12px;text-align:center;background:var(--surface);color:var(--ok);font-size:12px;padding:2px}.saved-sheet-launch,.saved-sheet-menu{margin-top:10px}.saved-sheet-launch{font-size:23px;padding:6px;min-height:42px}.saved-sheet-menu{position:relative}.saved-sheet-menu>summary{list-style:none;cursor:pointer;text-align:center;padding:10px 4px;min-height:42px;box-sizing:border-box}.saved-sheet-menu-items{position:absolute;right:0;top:100%;z-index:8;min-width:155px;background:var(--surface);border:1px solid var(--line);border-radius:7px;padding:5px;box-shadow:0 8px 22px #0004}.saved-sheet-menu-items .btn{display:block;width:100%;text-align:left;margin-bottom:3px}.saved-sheet-body{padding:0 16px 12px;max-height:460px;overflow:auto}.saved-sheet-block{display:grid;grid-template-columns:minmax(0,1.3fr) minmax(0,1fr);gap:24px;padding:5px 0 12px}.saved-sheet-block+.saved-sheet-block{border-top:1px solid var(--line);padding-top:12px}.saved-sheet-wallet{padding-left:34px;font-size:14px}.saved-sheet-address{display:flex;gap:8px;align-items:start;color:var(--dim);margin-top:7px;overflow-wrap:anywhere}.saved-sheet-address>span{min-width:0}.saved-sheet-address .btn{padding:1px 5px;background:transparent;border:0}.saved-sheet-config{font-size:13px;line-height:1.5;min-width:0}.saved-sheet-value{overflow-wrap:anywhere;white-space:pre-wrap}.saved-sheet-value>span{color:var(--dim)}.saved-sheet-version{font-size:11px;text-align:right}@media(max-width:760px){.saved-sheet-heading{grid-template-columns:minmax(0,1fr) minmax(0,1fr) 24px;gap:8px;padding:10px}.saved-sheet-pool{display:none}.saved-sheet-block{grid-template-columns:1fr;gap:12px}.saved-sheet-wallet{padding-left:0}.saved-sheet{grid-template-columns:26px minmax(0,1fr) 38px 34px;gap:5px}.saved-sheet-launch{font-size:20px}}

.saved-sheet-block{grid-template-columns:minmax(160px,1.3fr) minmax(120px,.8fr) minmax(140px,1fr) 28px;gap:16px}.saved-sheet-wallet{grid-column:1/3}.saved-sheet-config{grid-column:3/5}@media(max-width:760px){.saved-sheet-block{grid-template-columns:1fr;gap:12px}.saved-sheet-wallet,.saved-sheet-config{grid-column:1}}

.saved-sheet-count{color:var(--fg)}

.miner-instances{padding:12px 16px;border-top:1px solid var(--border)}
.ai-flight-planner{margin:18px 0;padding:16px;border:1px solid var(--line);border-radius:8px;background:var(--panel2)}
.ai-flight-planner h3{margin-top:0}.ai-flight-planner p{color:var(--dim);font-size:14px}
.ai-flight-controls{display:grid;grid-template-columns:2fr 1fr 1fr 1.5fr 1fr;gap:12px}
.ai-flight-controls label{display:flex;flex-direction:column;gap:6px;font-size:13px;min-width:0}
.ai-flight-controls select,.ai-flight-controls input{width:100%;min-width:0;box-sizing:border-box;padding:9px;background:var(--surface);color:var(--fg);border:1px solid var(--line);border-radius:6px;font:inherit}
.ai-flight-actions{display:flex;flex-wrap:wrap;gap:8px;margin:14px 0}.ai-flight-actions .primary{margin-left:auto}
.ai-flight-rigs{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:6px;max-height:240px;overflow:auto}
.ai-flight-rig{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:8px;background:var(--surface);border-radius:5px;font-size:13px}.ai-flight-rig small{color:var(--dim);overflow-wrap:anywhere}
.ai-flight-result{overflow-wrap:anywhere}.ai-flight-row{display:grid;grid-template-columns:minmax(100px,1fr) 3fr;gap:14px;padding:8px 0;border-bottom:1px solid var(--line);font-size:14px}.ai-flight-result li{padding:5px 0}
@media(max-width:850px){.ai-flight-controls{grid-template-columns:1fr 1fr}.ai-flight-controls label:first-child{grid-column:1/-1}.ai-flight-actions .primary{margin-left:0}.ai-flight-row{grid-template-columns:1fr}.ai-flight-planner{padding:12px}}
.miner-instance{display:flex;align-items:center;gap:12px;flex-wrap:wrap;padding:6px 0;overflow-wrap:anywhere}
.miner-instance>span:first-child{font-weight:600;min-width:120px}
.policy-recovery-rows{max-height:22rem;overflow:auto;overflow-wrap:anywhere;margin-block:.5rem;border-block:1px solid var(--line)}
.policy-recovery-rows p{margin:.35rem 0;font-size:13px}
