/* components.css — библиотека компонентов дизайн-системы.
 *
 * Правило слоя: один компонент — один блок правил, имя блока совпадает с
 * именем партиала в app/templates/components/. Страничного здесь нет: всё,
 * что верно только для одной страницы, живёт в pages/<имя>.css.
 *
 * Цветовой закон: золото достаётся только работе агента (главная кнопка,
 * отметка агента, торец карточки с готовым планом), красный — только
 * критическому и разрушающему. Остальное нейтральное.
 */

/* ============================================================
   ВСПОМОГАТЕЛЬНЫЕ КЛАССЫ ТЕКСТА
   ============================================================ */
.icon{flex:none}
.mono{font-family:var(--f-mono)}
.muted{color:var(--w-ink-muted)}
.text-inverse{color:var(--on-accent)}
.doc-scope{color:var(--d-ink)}

/* ============================================================
   КНОПКА
   ============================================================ */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:var(--s2);
  min-height:var(--tap);padding:0 var(--s4);
  border-radius:var(--r-chip);border:1px solid transparent;
  font-size:var(--t-13);font-weight:500;white-space:nowrap;
  color:var(--w-ink-soft);
}
.btn--full{width:100%}
.btn--primary{background:var(--accent);color:var(--on-accent);font-weight:600}
.btn--primary:hover{background:var(--accent-hover)}
.btn--ghost{border-color:var(--w-hair);color:var(--w-ink-soft)}
.btn--ghost:hover{border-color:var(--w-ink-muted);color:var(--w-ink)}

/* Тихая кнопка — «Отмена», «Пропустить». Приглушённая, но не выключенная:
 * прежде она была набрана тем же токеном, что и недоступное, без фона и без
 * рамки, и в ряду с обычной кнопкой читалась как «сюда нельзя». Теперь у неё
 * цвет живого текста и подложка под курсором — видно, что это цель. */
.btn--quiet{color:var(--w-ink-soft)}
.btn--quiet:hover,.btn--quiet:focus-visible{background:var(--w-ground);color:var(--w-ink)}

/* Разрушающее действие обычным весом — рамкой, а не заливкой. Заливка в ряду
 * с золотой главной кричала громче неё, то есть звала сделать именно то, чего
 * делать не собирались. Громкость оставлена последнему шагу — кнопке
 * подтверждения в диалоге (--danger-strong), где выбор уже сделан. */
.btn--danger{border-color:var(--danger);color:var(--danger)}
.btn--danger:hover,.btn--danger:focus-visible{background:var(--danger-dim)}
.btn--danger-strong{background:var(--danger);color:var(--on-accent);font-weight:600}
.btn--danger-strong:hover{filter:brightness(.94)}
.btn[disabled],.btn[aria-disabled="true"]{opacity:.5;pointer-events:none}
.btn svg{flex:none}
/* Кнопка внутри листа документа берёт краски документа, а не мастерской. */
.doc-scope .btn--ghost{border-color:var(--d-hair);color:var(--d-ink-soft)}
.doc-scope .btn--ghost:hover{border-color:var(--d-ink-muted);color:var(--d-ink)}

.iconbtn{
  width:var(--tap);height:var(--tap);flex:none;
  display:grid;place-items:center;
  border:1px solid var(--w-hair);border-radius:var(--r-chip);color:var(--w-ink-soft);
}
.iconbtn:hover{border-color:var(--w-ink-muted);color:var(--w-ink)}

/* Переключатель темы: солнце в светлой, луна в тёмной. Обе иконки в разметке,
 * видимость решает тема — так переключатель работает без JS-перерисовки. */
.theme-toggle .icon--moon{display:none}
:root[data-theme="dark"] .theme-toggle .icon--sun{display:none}
:root[data-theme="dark"] .theme-toggle .icon--moon{display:block}
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]) .theme-toggle .icon--sun{display:none}
  :root:not([data-theme="light"]) .theme-toggle .icon--moon{display:block}
}

/* ============================================================
   ЧИП И БЕЙДЖ
   ============================================================ */
.chip{
  display:inline-flex;align-items:center;gap:6px;
  min-height:var(--tap);padding:0 var(--s3);
  border:1px solid var(--w-hair);border-radius:var(--r-chip);
  color:var(--w-ink-soft);font-size:var(--t-12);white-space:nowrap;
}
.chip:hover{border-color:var(--w-ink-muted);color:var(--w-ink)}
.chip--on{border-color:var(--accent-line);background:var(--accent-dim);color:var(--accent-text)}
.chip--on svg{color:var(--accent-text)}
.chip[disabled]{opacity:.5;pointer-events:none}
.chip__count{color:var(--w-ink-muted);font-variant-numeric:tabular-nums}

.badge{
  display:inline-flex;align-items:center;gap:6px;height:22px;padding:0 var(--s2);
  border-radius:var(--r-chip);font-size:var(--t-11);font-weight:600;
  background:var(--st-done-bg);color:var(--st-done-text);
}
.badge--agent{background:var(--accent-dim);color:var(--accent-text);border:1px solid var(--accent-line)}
.badge--danger{background:var(--danger-dim);color:var(--danger)}
.badge--now{background:var(--st-now-bg);color:var(--st-now-text)}
.badge__dot{width:7px;height:7px;border-radius:var(--r-pill);flex:none;background:currentColor}

/* ============================================================
   ПЛАШКА-УВЕДОМЛЕНИЕ
   ============================================================ */
.alert{
  display:flex;gap:var(--s3);padding:var(--s4);
  border:1px solid var(--w-hair);border-radius:var(--r-card);
  background:var(--w-panel);color:var(--w-ink-soft);
}
.alert__icon{flex:none;color:var(--w-ink-muted);margin-top:2px}
.alert__content{min-width:0}
.alert__title{font-size:var(--t-13);font-weight:600;color:var(--w-ink);margin-bottom:var(--s1)}
.alert__body{font-size:var(--t-13);line-height:1.6;max-width:64ch}
.alert__meta{margin-top:var(--s2);font-family:var(--f-mono);font-size:var(--t-11);color:var(--w-ink-muted)}
.alert__cta{display:flex;flex-wrap:wrap;gap:var(--s2);margin-top:var(--s3)}
/* Успех подтверждает сделанное и гаснет: своя краска нужна ему затем, чтобы
   «участник привязан» не читалось так же, как «вот что стоит знать». */
.alert--success{border-color:var(--ok);background:var(--ok-dim)}
.alert--success .alert__icon,.alert--success .alert__title{color:var(--ok)}
/* Сообщение после перенаправления. Стоит прямым ребёнком рабочей области, а
   содержимое страницы — во вложенной секции со своими полями: без этих полей
   плашка растягивалась во всю ширину окна, и её левый край не совпадал ни с
   заголовком, ни с текстом под ним. Ширина ограничена: сообщение в одну
   строку, растянутое на два монитора, читается как системный баннер, а не как
   ответ на только что нажатую кнопку. */
.flash{margin:var(--s5) var(--s5) 0;max-width:46rem}

.alert--danger{border-color:var(--danger);background:var(--danger-dim)}
.alert--danger .alert__icon,.alert--danger .alert__title{color:var(--danger)}
.alert--agent{border-color:var(--accent-line);background:var(--accent-dim)}
.alert--agent .alert__icon,.alert--agent .alert__title{color:var(--accent-text)}
/* Удача рисуется нейтрально: золото в продукте означает работу агента, и
 * тратить его на надпись «сохранено» нельзя. Отличает вариант галочка. */
.alert--success{border-color:var(--w-ink-muted)}
.alert--success .alert__icon{color:var(--w-ink)}

/* Сообщение после перенаправления стоит первым в рабочей области. */
.flash{margin-bottom:var(--s5)}

/* Переключатель проекта на <details>: раскрытие, Esc и работа с клавиатуры
 * достаются от браузера, список остаётся рабочим без JS. */
.pswitch{position:relative}
.pswitch__current{
    display:flex;align-items:center;gap:var(--s3);
    min-height:var(--tap);padding:var(--s2) var(--s3);
    background:var(--w-raised);border:1px solid var(--w-hair);border-radius:var(--r-card);
    cursor:pointer;list-style:none;
}
.pswitch__current::-webkit-details-marker{display:none}
.pswitch__current:hover{border-color:var(--w-ink-muted)}
.pswitch__key{font-family:var(--f-mono);font-size:var(--t-12);font-weight:600;color:var(--accent-text);flex:none}
.pswitch__name{font-size:var(--t-13);color:var(--w-ink-soft);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pswitch__chev{margin-left:auto;color:var(--w-ink-muted);flex:none}
.pswitch[open] .pswitch__chev{transform:rotate(180deg)}
.pswitch__menu{
    display:flex;flex-direction:column;margin-top:var(--s1);
    background:var(--w-panel);border:1px solid var(--w-hair);border-radius:var(--r-card);
    overflow:hidden;
}
.pswitch__item{
    display:flex;align-items:center;gap:var(--s3);
    min-height:var(--tap);padding:var(--s2) var(--s3);color:var(--w-ink-soft);
}
.pswitch__item:hover{background:var(--w-raised);color:var(--w-ink)}
.pswitch__item:not(:last-child){border-bottom:1px solid var(--w-hair-soft)}
.pswitch__item--all{font-size:var(--t-13)}

/* ============================================================
   ПОЛЯ ВВОДА
   ============================================================ */
/* У поля нет внешнего отступа: расстояние между полями задаёт контейнер своим
 * gap. Прежде поле несло margin-bottom, который складывался с gap родителя, и
 * расстояние получалось суммой двух решений — отсюда и брались зазоры 9, 21,
 * 32, которых никто не выбирал. Внутри поля 8, между полями 16: вдвое больше,
 * иначе подпись не читается как принадлежащая своему полю. */
.field{display:flex;flex-direction:column;gap:var(--s2)}
.field__label{font-size:var(--t-13);font-weight:500;color:var(--d-ink)}
.field__req{color:var(--danger);margin-left:2px}
.field__hint{font-size:var(--t-12);color:var(--d-ink-muted);line-height:1.55}
.field__error{font-size:var(--t-12);color:var(--danger);line-height:1.55}

/* Однострочное поле держит ровно ту же высоту, что кнопка и чип: прежде
 * текстовое поле выходило 47, селект 48, поле даты 49, а кнопка 44 — четыре
 * высоты у того, что стоит в одной строке формы. Разница набегала из
 * внутренних метрик браузера, поэтому высота задана явно, а не отступом. */
.input{height:var(--tap);padding-block:0}
.input,.textarea{
  width:100%;min-height:var(--tap);padding-inline:var(--s4);
  border:1px solid var(--d-hair);border-radius:var(--r-card);
  background:var(--d-sheet);color:var(--d-ink);
  font:inherit;font-size:var(--t-16);line-height:1.6;
}
.textarea{resize:vertical;padding-block:var(--s3)}
.input::placeholder,.textarea::placeholder{color:var(--d-ink-muted)}
.input:focus,.textarea:focus{outline:2px solid var(--accent-fill);outline-offset:1px;border-color:transparent}
.input--error,.textarea--error{border-color:var(--danger)}

/* Выбор из закрытого набора. Стрелку рисует браузер: своя, нарисованная
   фоном, разошлась бы с системной на телефоне и в тёмной теме. */
.select{
  width:100%;height:var(--tap);min-height:var(--tap);padding-block:0;padding-inline:var(--s4);
  border:1px solid var(--d-hair);border-radius:var(--r-card);
  background:var(--d-sheet);color:var(--d-ink);
  font:inherit;font-size:var(--t-16);line-height:1.6;
}
.select:focus{outline:2px solid var(--accent-fill);outline-offset:1px;border-color:transparent}

/* Отметка строки списка. Цель нажатия — вся подпись вместе с квадратом:
   попасть пальцем в квадрат 16 на 16 нельзя. */
.check{display:inline-flex;align-items:center;gap:var(--s2);min-height:var(--tap)}
.check__box{width:18px;height:18px;flex:none;accent-color:var(--accent-fill)}
.check__box:focus-visible{outline:2px solid var(--accent-fill);outline-offset:2px}
.check__label{font-size:var(--t-13);color:var(--d-ink);cursor:pointer}
/* Галочка без видимой подписи: цель нажатия — вся метка, а не квадратик в
   18px, по которому пальцем не попасть. */
.check--bare{justify-content:center;min-width:var(--tap);cursor:pointer}

/* Переключатель варианта ответа: ответ агенту нажатием, а не набором текста. */
.radio{display:inline-flex;align-items:center;gap:var(--s2);min-height:var(--tap)}
.radio__dot{width:18px;height:18px;flex:none;accent-color:var(--accent-fill)}
.radio__dot:focus-visible{outline:2px solid var(--accent-fill);outline-offset:2px}
.radio__label{font-size:var(--t-13);color:var(--d-ink);cursor:pointer}

.file-input{
  display:block;border:1px dashed var(--d-hair);border-radius:var(--r-card);
  background:var(--d-sheet);
}
.file-input:hover{border-color:var(--d-ink-muted)}
.file-input input[type="file"]{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)}
.file-input__label{
  display:flex;flex-direction:column;align-items:center;gap:var(--s2);
  padding:var(--s6);text-align:center;cursor:pointer;
  color:var(--d-ink-muted);font-size:var(--t-12);min-height:var(--tap);
}
.file-input__icon{color:var(--d-ink-muted)}
.file-input__filename{color:var(--d-ink);font-size:var(--t-13)}
.file-input input[type="file"]:focus-visible+.file-input__label{outline:2px solid var(--accent);outline-offset:2px}

@media (min-width:640px){
  .input,.textarea{font-size:var(--t-13)}
  .flash{margin:var(--s6) var(--s6) 0}
}

/* ============================================================
   ХЛЕБНЫЕ КРОШКИ
   ============================================================ */
/* Крошки живут в шапке — узком flex-ряду рядом с брендом и переключателем
   темы. Без min-width:0 элемент flex не сжимается ниже своего содержимого, и
   длинное название проекта выталкивало переключатель темы за правый край
   экрана вместе с горизонтальной прокруткой всей страницы. */
.breadcrumbs{min-width:0;overflow:hidden}
.breadcrumbs__list{
  display:flex;align-items:center;gap:var(--s2);
  list-style:none;padding:0;margin:0;
  font-size:var(--t-13);color:var(--w-ink-muted);min-width:0;
}
.breadcrumbs__item{display:flex;align-items:center;gap:var(--s2);min-width:0}
.breadcrumbs__link,.breadcrumbs__current{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.breadcrumbs__link:hover{color:var(--w-ink)}
.breadcrumbs__current{font-weight:500;color:var(--w-ink)}
.breadcrumbs__sep{color:var(--w-hair);flex:none}

/* На узком экране от цепочки остаётся только текущая страница: путь целиком
   в 390px не помещается ни при каком сжатии, а «где я нахожусь» на телефоне
   отвечает выдвижная рельса. Разделитель перед уцелевшим элементом убирается
   — иначе строка начиналась бы со стрелки в никуда. */
@media (max-width:879px){
  .breadcrumbs__item:not(:last-child){display:none}
  .breadcrumbs__item:last-child .breadcrumbs__sep{display:none}
}

/* ============================================================
   АВАТАР
   ============================================================ */
/* Кружок обязан быть виден на любой поверхности. Прежде он был залит тем же
 * --w-raised, что и карточка на доске, — то есть на карточке кружка не было
 * вовсе, и от аватара оставалась одинокая буква «К», неотличимая от обрезанного
 * слова. Поэтому у него своя светлота и волосяная рамка: рамка держит форму
 * даже там, где заливка совпадёт с фоном. */
.avatar{
  width:28px;height:28px;border-radius:var(--r-pill);flex:none;
  display:grid;place-items:center;
  font-size:var(--t-12);font-weight:600;
  background:var(--w-ground);border:1px solid var(--w-hair);color:var(--w-ink-soft);
}
.avatar--sm{width:22px;height:22px;font-size:10px}

/* ============================================================
   ДОСКА: КОЛОНКА И КАРТОЧКА
   ============================================================ */
.board{
  flex:1;display:flex;gap:var(--s3);padding:var(--s3);
  overflow-x:auto;align-items:stretch;scroll-snap-type:x mandatory;
}
.col{
  flex:0 0 84vw;scroll-snap-align:start;
  display:flex;flex-direction:column;min-width:0;
  background:var(--w-panel);border:1px solid var(--w-hair);border-radius:var(--r-panel);
}
.col__head{display:flex;align-items:center;gap:var(--s3);padding:var(--s4) var(--s4) var(--s3)}
.col__name{font-size:var(--t-13);font-weight:600;letter-spacing:-.005em}
.col__count{margin-left:auto;font-size:var(--t-12);color:var(--w-ink-muted)}
.col__body{
  display:flex;flex-direction:column;gap:var(--s2);
  padding:0 var(--s3) var(--s3);overflow-y:auto;min-height:0;list-style:none;margin:0;
}
/* Пустой столбец объясняет, что сюда попадает и откуда, а не молчит
 * надписью «нет данных». */
.col__empty{
  display:flex;flex-direction:column;align-items:center;gap:var(--s2);flex:none;
  padding:var(--s6) var(--s3);text-align:center;
  border:1px dashed var(--w-hair);border-radius:var(--r-card);color:var(--w-ink-muted);
}
.col__empty b{font-size:var(--t-12);font-weight:600;color:var(--w-ink-soft)}
.col__empty span{font-size:var(--t-11);line-height:1.5}
/* Как только в колонку переносят карточку, приглашение уходит: страница при
 * этом не перерисовывается, состояние колонки меняет только DOM. */
.col__body:has(.card) .col__empty{display:none}

/* Столбец «План готов» — единственный, где на доске появляется золото: его
 * ищут глазами, там лежит то, что можно брать в работу. Раньше золото несла
 * плашка с номером столбца; номер убран, потому что его принимали за счётчик
 * тикетов, а золото переехало на имя столбца — на то, что и называет его. */
.col[data-state="plan_ready"] .col__name{color:var(--accent-text)}
.col--over{border-color:var(--accent-line)}
.col--over .col__head{background:var(--accent-dim)}

.card{
  position:relative;display:block;width:100%;flex:none;text-align:left;
  padding:var(--s3) var(--s3) var(--s3) var(--s4);
  background:var(--w-raised);border:1px solid var(--w-hair);border-radius:var(--r-card);
  overflow:hidden;cursor:grab;user-select:none;
}
.card::before{content:"";position:absolute;left:0;top:0;bottom:0;width:3px;background:transparent}
/* Цвет состояния с карточек убран намеренно: столбец уже говорит о состоянии.
 * Единственное исключение — золотой торец у готового плана: его видно с одного
 * взгляда, и это значит «можно брать в работу». */
.card[data-state="plan_ready"]::before{background:var(--accent-fill)}
.card:hover{border-color:var(--w-ink-muted)}
.card:active{cursor:grabbing}
.card--done .card__title{color:var(--w-ink-soft)}
.card__link{display:block;color:inherit}
.card__top{display:flex;align-items:center;gap:var(--s2);margin-bottom:var(--s2)}
.card__key{font-family:var(--f-mono);font-size:var(--t-11);font-weight:500;color:var(--w-ink-muted);letter-spacing:.01em}
.card__pr{width:6px;height:6px;border-radius:var(--r-pill);flex:none}
.card__pr--critical{background:var(--danger)}
.card__pr--high{background:var(--danger);opacity:.5}
.card__pr--normal{background:var(--w-ink-muted);opacity:.45}
.card__title{
  margin:0;font-size:var(--t-13);line-height:1.4;font-weight:450;color:var(--w-ink);
  display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;
}
.card__foot{display:flex;align-items:center;gap:var(--s2);margin-top:var(--s3)}
.card__foot .avatar{margin-left:auto}
.card__due{font-size:var(--t-11);color:var(--w-ink-muted)}
.card__due--soon{color:var(--danger)}
/* Состояние подписано словом, а не передано цветом: цветной торец нечитаем на
 * чёрно-белой печати и при нарушении цветовосприятия, а на узком экране
 * заголовок столбца уходит из виду при прокрутке. */
.card__epic{
  display:inline-flex;align-items:center;gap:6px;height:20px;padding:0 var(--s2);
  border:1px solid var(--w-hair);border-radius:var(--r-chip);background:var(--w-panel);
  font-size:var(--t-11);color:var(--w-ink-soft);
  max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.card__agent{
  display:inline-flex;align-items:center;gap:5px;height:20px;padding:0 var(--s2);
  border:1px solid var(--accent-line);border-radius:var(--r-chip);background:var(--accent-dim);
  font-size:var(--t-11);font-weight:500;color:var(--accent-text);
}

/* Перенос карточки. Мышью карточка берётся из любого места, пальцем — после
 * удержания: иначе колонку невозможно прокрутить. touch-action отключается
 * заранее только на точном указателе, на пальце — лишь после взведения. */
@media (pointer:fine){.card{touch-action:none}}
.card--armed{touch-action:none}
.card--hole{background:transparent;border-style:dashed;border-color:var(--w-hair);cursor:grabbing}
.card--hole>*{visibility:hidden}
.card--hole::before{background:transparent}
.card--ghost{
  position:fixed;z-index:100;margin:0;pointer-events:none;
  box-shadow:var(--shadow-drag);border-color:var(--accent-line);
  transform:rotate(1.5deg);opacity:.97;
}
body.is-dragging{cursor:grabbing}
body.is-dragging .card:not(.card--ghost){cursor:grabbing}
@media (prefers-reduced-motion:no-preference){
  .card,.col{transition:border-color .12s ease}
}

@media (min-width:880px){
  .board{gap:var(--s4);padding:var(--s6);scroll-snap-type:none}
  .col{flex:0 0 296px;min-height:420px;max-height:calc(100vh - 130px)}
}

/* ============================================================
   СОСТОЯНИЯ: ПУСТО, ЗАГРУЗКА, ОШИБКА
   ============================================================ */
.empty{
  display:flex;flex-direction:column;align-items:center;gap:var(--s3);
  padding:var(--s8) var(--s5);text-align:center;
  background:var(--d-sheet);border:1px solid var(--d-hair);border-radius:var(--r-panel);
}
/* Сжатый вид: строка внутри чужой панели. Ни рамки, ни фона, ни кружка —
 * панель вокруг уже всё это дала, и повторять её значит рисовать карточку
 * внутри карточки. */
.empty--compact{
  display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--s2);
  margin:0;padding:0;text-align:left;
  background:none;border:0;border-radius:0;
  font-size:var(--t-13);line-height:1.6;
}
.empty--compact .empty__title{font-weight:500}
.empty--compact .empty__text{color:var(--d-ink-muted)}

.empty__art{
  width:44px;height:44px;border-radius:var(--r-card);display:grid;place-items:center;
  background:var(--d-sunken);color:var(--d-ink-muted);
}
.empty__title{font-size:var(--t-14);font-weight:600;color:var(--d-ink)}
.empty__text{font-size:var(--t-13);color:var(--d-ink-soft);line-height:1.6;max-width:44ch}

.skeleton{display:flex;flex-direction:column;gap:var(--s2)}
.skeleton__line{
  height:11px;border-radius:4px;
  background:linear-gradient(90deg,var(--d-sunken) 0%,var(--d-hair-soft) 40%,var(--d-sunken) 80%);
  background-size:400% 100%;
}
/* Полосы разной длины — это форма будущего текста, а не украшение: четыре
 * одинаковых прямоугольника ничего не обещают и работают как спиннер,
 * разложенный в столбик. Последняя короче всех, как последняя строка абзаца. */
.skeleton__line:nth-child(3n+1){width:100%}
.skeleton__line:nth-child(3n+2){width:92%}
.skeleton__line:nth-child(3n){width:78%}
.skeleton__line:last-child{width:54%}

.skeleton__note{display:flex;align-items:center;gap:var(--s3);font-size:var(--t-13);color:var(--d-ink-soft)}
.skeleton__pulse{width:8px;height:8px;border-radius:var(--r-pill);background:var(--accent-fill);flex:none}
@media (prefers-reduced-motion:no-preference){
  .skeleton__line{animation:shimmer 1.6s linear infinite}
  @keyframes shimmer{0%{background-position:100% 0}100%{background-position:-100% 0}}
  .skeleton__pulse{animation:pulse 1.4s ease-in-out infinite}
  @keyframes pulse{0%,100%{opacity:.35;transform:scale(.85)}50%{opacity:1;transform:scale(1)}}
}

/* Страница ошибки и плашка сорвавшейся операции: что случилось, почему,
 * что сделать, и сколько было попыток. */
.state{
  display:flex;flex-direction:column;align-items:flex-start;gap:var(--s3);
  padding:var(--s7) var(--s5);
  background:var(--d-sheet);border:1px solid var(--d-hair);border-radius:var(--r-panel);
}
.state--error{border-color:var(--danger)}
.state__code{font-family:var(--f-mono);font-size:var(--t-12);color:var(--d-ink-muted)}
.state__title{font-size:var(--t-20);color:var(--d-ink)}
.state__text{font-size:var(--t-13);color:var(--d-ink-soft);line-height:1.65;max-width:60ch}
.state__attempt{font-size:var(--t-12);color:var(--d-ink-muted)}
.state__actions{display:flex;flex-wrap:wrap;gap:var(--s2)}

/* ============================================================
   ДИАЛОГ ПОДТВЕРЖДЕНИЯ
   ============================================================ */
.confirm-dialog{
  padding:0;border:1px solid var(--d-hair);border-radius:var(--r-panel);
  background:var(--d-sheet);color:var(--d-ink);
  max-width:min(440px,calc(100vw - var(--s6)));box-shadow:var(--shadow-sheet);
}
.confirm-dialog::backdrop{background:rgba(13,14,16,.55)}
.confirm-dialog__panel{padding:var(--s5);display:flex;flex-direction:column;gap:var(--s3);margin:0}
.confirm-dialog__title{font-size:var(--t-16);color:var(--d-ink)}
.confirm-dialog__body{font-size:var(--t-13);color:var(--d-ink-soft);line-height:1.6}
.confirm-dialog__actions{display:flex;flex-wrap:wrap;gap:var(--s2);justify-content:flex-end}

/* --- pager ---------------------------------------------------------------- */
.pager{display:flex;align-items:center;gap:var(--s3);flex-wrap:wrap}
.pager__pos{font-size:var(--t-12);color:var(--w-ink-muted);font-variant-numeric:tabular-nums}

/* ============================================================
   ПАНЕЛЬ — контракт классов
   ============================================================

   Раздел страницы со своей рамкой: перечень с заголовком и счётчиком, форма
   заведения, блок настроек. Заведён потому, что это правило было написано
   дважды — .ap__panel и .ag__panel совпадали побайтово, включая шапку и
   отступ на широком экране. Два имени на одно правило приглашают завести
   третье.

       <section class="panel">
         <div class="panel__head">
           <h2 class="panel__title">Репозитории</h2>
           <span class="panel__count">8 репозиториев</span>
         </div>
         … содержимое …
       </section>

   Счётчик приглушён и мельче заголовка: он уточняет, а не называет. */

.panel{
  display:flex;flex-direction:column;gap:var(--s3);
  padding:var(--s4);border:1px solid var(--w-hair);border-radius:var(--r-panel);
  background:var(--w-panel);
}
.panel__head{display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--s3)}
.panel__title{margin:0;font-size:var(--t-16);font-weight:600;color:var(--w-ink)}
.panel__count{font-size:var(--t-12);color:var(--w-ink-muted);font-variant-numeric:tabular-nums}

/* Подзаголовок внутри панели — отбивка «перечень кончился, дальше форма
 * заведения». Волосяная черта нужна именно потому, что форма стоит открытой:
 * без неё её поля читались бы продолжением перечня. Меньше и легче заголовка
 * панели: это её часть, а не второй раздел. */
.panel__sub{
  margin:0;padding-top:var(--s4);border-top:1px solid var(--w-hair-soft);
  font-size:var(--t-13);font-weight:600;color:var(--w-ink);
}

@media (min-width:768px){
  .panel{padding:var(--s5)}
}

/* ============================================================
   РАСКРЫВАЮЩИЙСЯ БЛОК — контракт классов
   ============================================================

   Форма заведения и правки раскрывается по требованию: раздел читают чаще,
   чем правят, и восемь развёрнутых форм на списке из четырёх строк — это
   страница, на которой не найти сам список.

   До контракта этот блок был написан семь раз в пяти наборах значений: две
   величины шрифта, три цвета подписи, две ширины поля. Хуже разнобоя было то,
   что он прятал: у четырёх из семи подписей не было `min-height: var(--tap)`,
   то есть цель нажатия была меньше 44px вопреки правилу проекта. Разнобой не
   виден поштучно — виден только его итог.

   Разметка — договор, а не рекомендация:

       <details class="disclosure disclosure--framed">
         <summary class="disclosure__summary">Завести проект</summary>
         <div class="disclosure__body"> … </div>
       </details>

   Свой класс вида `.<префикс>__new-summary` — нарушение договора, а не
   местная правка: именно из таких правок и вырос разнобой выше. */

/* Основа контракта — то, ради чего он и заводился: подпись раскрывающегося
 * блока это цель для пальца, а не для курсора. Правило стоит на самом
 * <summary>, а не на классе подписи: тогда высоту получает любой блок, какой
 * бы подпись ни была — хоть составная строка с номером круга диалога. */
.disclosure > summary{cursor:pointer;min-height:var(--tap)}

/* У подписи две оси, и их не надо путать.
 *
 * Форма: в своём поле (по умолчанию) или в потоке строки (--inline).
 * Тон: обычный, приглушённый (--quiet) или подчёркнутый как ссылка (--link).
 *
 * Цвет чернил у обоих семейств поверхностей совпадает значением, поэтому
 * отдельных правил для листа документа здесь нет: семейства различаются
 * поверхностями, а не текстом. */
.disclosure__summary{
  display:flex;align-items:center;gap:var(--s2);
  padding:var(--s3) var(--s4);
  font-size:var(--t-13);font-weight:500;color:var(--w-ink);
}

/* Шеврон — обещание, что здесь раскроется. Без него блок читается как
 * заголовок: браузерный треугольник у <summary> пропадает, как только подписи
 * задан display:flex, и до контракта он был у одних подписей и не был у
 * других — по тому же признаку «кому как написалось». */
.disclosure__chev{flex:none;color:var(--w-ink-muted);transition:transform 150ms ease}
.disclosure[open] > .disclosure__summary .disclosure__chev{transform:rotate(180deg)}
@media (prefers-reduced-motion:reduce){
  .disclosure__chev{transition:none}
}
.disclosure__summary--inline{display:inline-flex;padding:0}
.disclosure__summary--quiet{font-weight:400;color:var(--w-ink-muted)}
.disclosure[open] > .disclosure__summary--quiet{color:var(--w-ink)}
.disclosure__summary--link{
  font-weight:400;color:var(--w-ink-soft);
  text-decoration:underline;text-underline-offset:3px;
}
.disclosure[open] > .disclosure__summary--link{color:var(--w-ink)}

.disclosure__body{padding:0 var(--s4) var(--s4)}
/* Подпись без своего поля не создаёт и поля для содержимого — иначе текст
 * уезжает вправо от подписи, к которой он относится. */
.disclosure__summary--inline + .disclosure__body,
.disclosure__summary--link + .disclosure__body{padding:var(--s3) 0 0}

/* Обрамлённый — самостоятельный блок на фоне страницы. Сегодня им пользуется
 * одно место: удаление агента. Форма заведения им не пользуется и не должна —
 * она стоит открытой в панели, а раскрытием прячут подробность и разрушающее
 * действие. Тон белый, а не приподнятый: на фоне страницы приподнятый почти не
 * отличается от неё. */
.disclosure--framed{border:1px solid var(--w-hair);border-radius:var(--r-panel);background:var(--w-panel)}

/* Вложенный — правка внутри строки списка, которая уже белая. Приподнятый тон
 * здесь не украшение: на белом белое не читается как отдельный блок. */
.disclosure--nested{border:1px solid var(--w-hair);border-radius:var(--r-panel);background:var(--w-raised)}

/* Лист документа — своя светлота и свой отступ по краю: подпись обязана
 * стоять на одной вертикали с содержимым листа, иначе видно, что это
 * приклеено. */
.disclosure--sheet{border-top:1px solid var(--d-hair-soft);background:var(--d-sunken)}
.disclosure--sheet > .disclosure__summary{padding-inline:var(--s5)}
.disclosure--sheet > .disclosure__body{padding:0 var(--s5) var(--s5)}

/* ============================================================
   ШАПКА СТРАНИЦЫ И ПОЯСНЕНИЕ
   ============================================================ */

/* Одна шапка на все страницы. Прежде её писал каждый раздел сам, и значения
 * разошлись: заголовок в двух размерах, пояснение в двух, интервал то s1, то
 * s2. Ни одну из этих разниц никто не выбирал. */
.phead{
  display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;
  gap:var(--s2) var(--s4);
}
.phead__text{display:flex;flex-direction:column;gap:var(--s1);min-width:0}
.phead__eyebrow{margin:0;font-family:var(--f-mono);font-size:var(--t-12);font-weight:600;color:var(--accent-text)}
/* Подпись приглушена, значение — нет: сама по себе строка «PRSM — Persium.ai»
 * читалась как потерянная, а не как «настраиваем вот этот проект». */
.phead__meta{margin:0;display:flex;flex-wrap:wrap;gap:var(--s2) var(--s4)}
.phead__pair{display:inline-flex;align-items:baseline;gap:var(--s2)}
.phead__k{font-size:var(--t-12);color:var(--w-ink-muted)}
.phead__v{font-size:var(--t-13);color:var(--w-ink)}
.phead__title{margin:0;font-size:var(--t-20);font-weight:600;color:var(--w-ink)}
.phead__lead{margin:0;font-size:var(--t-13);line-height:1.6;color:var(--w-ink-soft);max-width:72ch}
.phead__action{flex:none}

/* Пояснение краем глаза. Мера 72ch — не украшение: пояснение читают по
 * диагонали, и строка длиннее теряет начало следующей. */
.note{margin:0;font-size:var(--t-12);line-height:1.55;color:var(--w-ink-muted);max-width:72ch}
.note--soft{font-size:var(--t-13);color:var(--w-ink-soft);line-height:1.6}

@media (min-width:768px){
  .phead__title{font-size:var(--t-26)}
}

/* Живая лента прогона агента: что он печатает прямо сейчас.
 *
 * Моноширинный здесь не украшение: в ленте идут пути файлов и команды, и
 * пропорциональный шрифт превращает столбец путей в рваный край. Время и вид
 * строки стоят отдельными столбцами постоянной ширины — по ним лента
 * сканируется глазом, а не вычитывается.
 *
 * Высота ограничена и прокручивается внутри: страница прогона не должна расти
 * на сто экранов, а шапка с этапом обязана остаться на виду. */
/* Машинная строка внутри плашки: причина отказа от клиента — не проза, и в
 * пропорциональном наборе она читается как оборванное предложение. */
.alert__body--mono{font-family:var(--f-mono);font-size:var(--t-12);overflow-wrap:anywhere}

.console{
  display:flex;flex-direction:column;min-height:0;
  background:var(--d-sheet);border:1px solid var(--d-hair);border-radius:var(--r-panel);
  overflow:hidden;
}
.console__head{
  display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--s3);
  padding:var(--s3) var(--s4);
  border-bottom:1px solid var(--d-hair-soft);background:var(--d-sunken);
}
.console__stage{margin:0;display:flex;align-items:center;gap:var(--s2);min-width:0}
.console__stage-name{font-size:var(--t-13);font-weight:600;color:var(--d-ink)}
.console__detail{font-size:var(--t-12);color:var(--d-ink-muted);overflow-wrap:anywhere}
.console__pulse{width:8px;height:8px;border-radius:var(--r-pill);background:var(--accent-fill);flex:none}
.console__meta{margin:0;display:flex;align-items:center;gap:var(--s3)}
/* Цифры времени — табличные: иначе счётчик дёргается на каждой секунде. */
.console__clock-label{font-size:var(--t-12);color:var(--d-ink-muted)}
.console__clock{font-family:var(--f-mono);font-size:var(--t-13);color:var(--d-ink);font-variant-numeric:tabular-nums}
.console__runner{font-size:var(--t-12);color:var(--d-ink-muted)}

/* Лента растёт по содержимому и упирается в потолок, а не заполняет экран
 * пустотой: семь строк в белом поле высотой в экран читаются как поломка, а
 * не как «это всё, что есть». */
.console__lines{
  list-style:none;margin:0;padding:var(--s3) var(--s4);
  display:flex;flex-direction:column;gap:2px;
  max-height:min(60dvh,40rem);
  overflow-y:auto;overflow-x:hidden;
  font-family:var(--f-mono);font-size:var(--t-12);line-height:1.65;
  scrollbar-gutter:stable;
}
.console__line{display:grid;grid-template-columns:auto auto minmax(0,1fr);gap:var(--s2);align-items:baseline}
.console__at{color:var(--d-ink-muted);font-variant-numeric:tabular-nums}
/* Вид строки набран узким столбцом: он подписан словом ради экранного
 * диктора, но глазом его читают как метку, а не как текст. */
.console__kind{color:var(--d-ink-muted);min-width:7ch}
/* Речь модели — самое содержательное в ленте, и набрана она основным цветом.
 * Шаги исполнителя приглушены: их много, и читают их только когда ищут, где
 * встало. */
.console__text{color:var(--d-ink);white-space:pre-wrap;overflow-wrap:anywhere}

/* Вызов инструмента — то, ради чего ленту и открывают: по нему видно, что
 * агент делает руками. Он выделен акцентом, речь модели набрана обычным
 * текстом, шаг исполнителя приглушён, жалоба клиента красная. */
.console__line--tool .console__text{color:var(--accent-text);font-weight:500}
.console__line--sys .console__text{color:var(--d-ink-muted)}
.console__line--err .console__text{color:var(--danger)}

.console__empty{margin:0;padding:var(--s6) var(--s4);font-size:var(--t-13);color:var(--d-ink-muted);text-align:center}

/* На узком экране три колонки съедали 40% ширины, и сообщение вываливалось на
 * три строки в оставшиеся восемнадцать знаков. Время и вид уходят отдельной
 * приглушённой строкой, текст занимает всю ширину. */
@media (max-width:36rem){
  /* Зазор внутри записи меньше, чем между записями: иначе метка времени
     зрительно липнет к чужому сообщению, и лента распадается на куски,
     которые приходится собирать глазами обратно. */
  .console__lines{gap:var(--s3)}
  .console__line{grid-template-columns:auto 1fr;gap:0 var(--s2)}
  .console__text{grid-column:1 / -1}
  .console__kind{min-width:0}
}

@media (prefers-reduced-motion:no-preference){
  .console__pulse{animation:pulse 1.4s ease-in-out infinite}
}
