  /* ==========================================================
     6. КВЕСТЫ: КАРТОЧКА
     ========================================================== */

  .quest-list { display: flex; flex-direction: column; gap: 6px; }

  .quest-row {
    position: relative;
    background: var(--surface-1);
    border: 1px solid var(--line);
    border-left: 3px solid var(--line-strong);
    padding: 10px 12px;
  }

  .quest-row:hover { border-color: var(--line-strong); }

  /* Заблокированный квест не выцветает почти в фон (прежний дефект —
     «сначала: …» было нечитаемо), а помечается штриховкой. */
  .quest-row.is-locked { background-color: #101208; }

  /* Полоса слева — код состояния, а не украшение. Цвет торговца здесь
     был бы честнее только со словарём «id → цвет» на клиенте, а его
     заводить нельзя: имена и состав торговцев приходят с сервера. */
  .quest-row.st-available { border-left-color: var(--ok); }
  .quest-row.st-started   { border-left-color: var(--started); }
  .quest-row.st-complete  { border-left-color: var(--done); }
  .quest-row.st-failed    { border-left-color: var(--danger); }
  .quest-row.st-locked    { border-left-color: var(--text-faint); }

  /* «Похоже, открыт»: блокировок нет, но есть условие, которое
     проверить нечем. Пунктир — уже сложившийся в проекте язык
     «не проверено» (.quest-unknown, .quest-badge.is-prestige), и это
     не цвет: полоса остаётся зелёной, меняется её фактура. Толщина
     4 px — на 3 px пунктир на глаз сливается в сплошную. */
  .quest-row.is-unverified { border-left-style: dashed; border-left-width: 4px; }

  /* «Выполнен» держался на одном канале — полосе 3 px — и та была
     ниже порога контраста. Каналов теперь три, и два из них не цвет:
     зачёркнутое имя и слово «выполнен» в селекте. Зачёркивание не
     выдумано здесь: так же помечены закрытые квесты в разборе Каппы
     (.ktask.is-done), то есть язык уже сложился. Имя при этом НЕ
     приглушается — тускнеть должно состояние, а не сам текст. */
  .quest-row.st-complete .quest-name {
    text-decoration: line-through;
    text-decoration-color: var(--line-strong);
    text-decoration-thickness: 1px;
  }

  .quest-main {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2);
  }

  .quest-name {
    color: var(--text);
    font-family: var(--font-display);
    font-size: 17px;
    font-weight: 600;
    letter-spacing: .02em;
    flex: 1 1 240px;
    min-width: 0;
  }

  .quest-row.is-locked .quest-name { color: var(--text-dim); }

  .quest-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px 10px;
    margin-top: 5px;
    font-size: 12px;
    color: var(--text-dim);
  }

  .quest-trader { display: inline-flex; align-items: center; gap: 6px; color: var(--text); }

  /* «0 из 3 целей» — 12 px в --text-faint давало 3.33:1, ниже AA (4.5).
     В компактном виде это к тому же ЕДИНСТВЕННОЕ, что говорит о целях,
     когда сам список целей свёрнут, — читаться должно наверняка.
     Иерархию внутри меты держит не тусклость, а порядок и то, что имя
     торговца набрано полным --text. */
  .quest-progress { font-variant-numeric: tabular-nums; color: var(--text-dim); }

  .quest-badge {
    font-family: var(--font-display);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .06em;
    color: var(--text-dim);
    border: 1px solid var(--line);
    padding: 1px 6px;
    flex-shrink: 0;
    white-space: nowrap;
  }

  .quest-badge.is-kappa { color: var(--accent); border-color: var(--accent); }
  .quest-badge.is-lk { color: var(--lightkeeper); border-color: var(--lightkeeper); }
  /* Престиж в гейте не участвует (отметки престижа у профиля нет) —
     пунктир вместо сплошной рамки и говорит «это пометка, а не условие». */
  .quest-badge.is-prestige { color: var(--text-dim); border-style: dashed; border-color: var(--line-strong); }
  /* Плашка «? не проверено» у доступного квеста. Костяной текст (не
     приглушённый): её обязаны прочитать, не вчитываясь, — ради этого
     она и заведена. Пунктир отличает её от «каппы»/«смотрителя». */
  .quest-badge.is-unverified { color: var(--text); border-style: dashed; border-color: var(--text-dim); }

  /* Статус — не служебная выпадашка у правого края (30 одинаковых
     стрелок выстраивались в полосу и перетягивали внимание с названий),
     а цветной индикатор состояния в начале строки.
     ⚠️ `order: -1` тут больше НЕТ: он тянул статус в начало независимо
     от разметки и потому оказывался ВПЕРЕДИ кнопки раскрытия, которую
     владелец 22.09.2026 попросил поставить первой. Порядок теперь
     задаёт разметка — кнопка, статус, имя. */
  .quest-status {
    flex-shrink: 0;
    background: var(--surface-2);
    border: 1px solid var(--line-strong);
    border-left-width: 3px;
    color: var(--text-dim);
    font-family: var(--font-display);
    font-size: 12px;
    letter-spacing: .04em;
    height: var(--chip-h);
    padding: 0 6px;
    cursor: pointer;
  }

  /* Раскрыть один квест, не выходя из компактного вида. Без этого
     компактный список был бы режимом «только посмотреть»: нашёл свой
     квест — и переключай плотность целиком, теряя обзор. Кнопка
     размером --tap, рисунок внутри мелкий — та же схема, что у
     чекбокса цели. */
  .quest-expand {
    flex-shrink: 0;
    width: var(--tap);
    height: var(--tap);
    background: transparent;
    /* Рамка постоянная, а не только на наведении: в компактном виде это
       единственный способ добраться до целей, и «кнопка, которая
       появляется при наведении» на сенсорном экране не появляется
       никогда. Тон --line — самый тихий из рамок, ряд не рябит. */
    border: 1px solid var(--line);
    color: var(--text-dim);
    font-size: 13px;
    line-height: 1;
    cursor: pointer;
  }

  /* Пустое место там, где раскрывать нечего: кнопка стоит первой, и
     без распорки селекты статуса соседних строк прыгали бы влево-вправо
     на ширину кнопки. Рамки у распорки нет — она держит место, а не
     изображает кнопку. */
  .quest-expand--ghost { border-color: transparent; pointer-events: none; }

  .quest-expand:hover { border-color: var(--line-strong); color: var(--text); }
  .quest-expand[aria-expanded="true"] { color: var(--accent); }

  .quest-status:hover:not(:disabled) { border-color: var(--accent); color: var(--text); }
  .quest-status:disabled { cursor: not-allowed; opacity: .7; }

  .quest-status.st-available { border-left-color: var(--ok); color: var(--ok); }
  .quest-status.st-started   { border-left-color: var(--started); color: var(--started); }
  /* Слово «выполнен» — второй носитель состояния, и раньше оно было
     самым бледным местом строки (--text-faint, 3.05:1 к фону селекта).
     Тон --done даёт 4.79:1 и заодно повторяет цвет полосы слева. */
  .quest-status.st-complete  { border-left-color: var(--done); color: var(--done); }
  .quest-status.st-failed    { border-left-color: var(--danger); color: var(--danger); }
  .quest-status.st-locked    { border-left-color: var(--text-faint); color: var(--text-faint); }

  /* Причина блокировки — одна строка на одно требование. Сервер отдаёт
     их типизированным списком (до 6 штук у одного квеста), и склеивать
     разнородные требования в одну фразу через «·» значит делать её
     нечитаемой: у каждой свой предмет речи. */
  .quest-reason {
    margin: 6px 0 0;
    font-size: 12px;
    color: var(--text-dim);
    border-left: 2px solid var(--fresh-stale);
    padding-left: 8px;
  }

  .quest-reason + .quest-reason { margin-top: 3px; }

  /* Непроверяемое требование — НЕ блокировка: владелец просто не
     отметил лояльность, и проверить нечем. Пунктирная засечка и
     знак «?» отличают его от настоящей причины выше, а кнопка
     ведёт туда, где отметку и ставят. Приглушённого тона здесь больше
     нет: 12 px в --text-faint — это 3.33:1, ниже AA, а фраза
     «не проверено: Егерь — уровень не ниже 2» читается целиком. */
  .quest-unknown {
    margin: 6px 0 0;
    padding-left: 8px;
    border-left: 2px dashed var(--line-strong);
    font-size: 12px;
    color: var(--text-dim);
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 2px 8px;
  }

  .quest-unknown__mark {
    font-family: var(--font-display);
    color: var(--line-strong);
    flex-shrink: 0;
  }

  .quest-unknown__text { min-width: 0; }

  .objective-list {
    margin-top: var(--sp-2);
    padding-top: var(--sp-2);
    border-top: 1px solid var(--line);
    display: flex;
    flex-direction: column;
  }

  .objective-row {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    font-size: 13px;
    padding: 2px 0;
    /* Строка не ниже цели нажатия: сам чекбокс остаётся мелким, но его
       <label> занимает всю высоту строки, и промахнуться уже некуда. */
    min-height: var(--tap);
  }

  /* Рисунок чекбокса намеренно оставлен мелким — 24 px в квадрат
     распухли бы на весь список, — а нажимается <label> вокруг него
     размером --tap (24 на мыши, 44 на сенсорной ширине). Клик по
     label штатно переключает вложенный input, JS для этого не нужен. */
  .objective-check-hit {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--tap);
    height: var(--tap);
    flex-shrink: 0;
    cursor: pointer;
  }

  .objective-check-hit:has(.objective-check:disabled) { cursor: not-allowed; }

  .objective-check {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
    accent-color: var(--ok);
    cursor: inherit;
  }

  .objective-desc {
    flex: 1 1 auto;
    min-width: 0;
    color: var(--text);
    line-height: 1.35;
  }

  .objective-row.is-done .objective-desc {
    color: var(--text-faint);
    text-decoration: line-through;
    text-decoration-color: var(--line-strong);
  }

  /* --text-dim, а не --text-faint: 11 px при 3.33:1 не читались бы
     вовсе, а «(необязательно)» меняет смысл цели целиком. */
  .objective-optional { color: var(--text-dim); font-size: 11px; }

  /* Предметы цели — это СПИСОК АЛЬТЕРНАТИВ («годится любой из»), а не
     набор к сдаче, поэтому у ряда есть подпись, и она обязательна:
     четыре иконки подряд без неё читаются как «нужно всё это».
     У единственного предмета подписи нет — описание цели его и так
     называет («Раздобыть Ключ от спецтехники»), а фраза «годится любой
     из» над одним предметом выглядела бы опечаткой. */
  /* inline-flex, а не flex: у 489 целей из 1433 предмет ровно один, и
     отдельная строка под одну картинку растила бы список целей ради
     пустоты. Иконка встаёт в конец описания и переносится вместе с ним. */
  .obj-items {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 3px 6px;
    margin-left: 6px;
    vertical-align: -7px;
  }

  .obj-items__label {
    font-family: var(--font-display);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: .1em;
    text-transform: uppercase;
    color: var(--text-faint);
  }

  .obj-items .item-icon { --ico: 24px; }

  /* Выполненная цель зачёркнута целиком — иконки внутри неё должны
     погаснуть вместе с текстом, иначе они остаются самым ярким пятном
     строки, которая уже не требует действий. */
  .objective-row.is-done .obj-items { opacity: .4; }

  /* Правый край строки цели прежде был рваным: то счётчик, то ссылка
     «на карте» в одном и том же месте. Теперь у обоих общий контейнер
     фиксированной ширины, и колонки не прыгают. */
  .objective-aside {
    flex-shrink: 0;
    display: grid;
    /* Вторая колонка — ровно ширина счётчика: две кнопки по --tap,
       значение 52 px и два зазора по 4 px. Считаем её формулой, а не
       числом, иначе на сенсорной ширине кнопки 44 px вылезли бы
       за фиксированные 104 px и столбец поехал. */
    grid-template-columns: 64px calc(var(--tap) * 2 + 60px);
    align-items: center;
    justify-items: end;
    gap: var(--sp-2);
  }

  /* Каждому — своя колонка, даже когда второго нет: иначе ссылка «на
     карте» у цели без счётчика уезжала к правому краю, а у цели со
     счётчиком стояла левее, и столбец шёл зигзагом. */
  .objective-aside .map-obj-link { grid-column: 1; }
  .objective-aside .objective-counter { grid-column: 2; }

  .objective-counter {
    display: flex;
    align-items: center;
    gap: 4px;
    flex-shrink: 0;
    font-variant-numeric: tabular-nums;
  }

  /* 22×22 было ниже порога WCAG 2.2 (24×24) — здесь, в отличие от
     чекбокса, дешевле вырастить саму кнопку: рисунок у неё и есть
     рамка, прятать нечего. */
  .counter-btn {
    background: var(--surface-2);
    border: 1px solid var(--line-strong);
    color: var(--text);
    width: var(--tap);
    height: var(--tap);
    line-height: 1;
    cursor: pointer;
    font-size: 13px;
  }

  .counter-btn:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
  .counter-btn:disabled { opacity: .3; cursor: not-allowed; }

  .counter-value { min-width: 52px; text-align: center; color: var(--text-dim); font-size: 12px; }

  /* «Найдено в рейде» у цели. Три состояния, а не два: ФИР требуется
     (продашь — придётся искать заново, ошибка необратима), покупное
     годится, и вопрос не стоит вовсе — у последнего метки нет никакой.
     Сплошная рамка акцентом — условие; пунктир — пометка «это не
     условие», тот же приём, что у бейджа престижа выше. */
  .fir {
    font-family: var(--font-display);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: .06em;
    text-transform: uppercase;
    padding: 1px 5px;
    white-space: nowrap;
    flex-shrink: 0;
  }

  .fir.is-yes { color: var(--accent); border: 1px solid var(--accent); }
  .fir.is-no { color: var(--text-faint); border: 1px dashed var(--line-strong); }

  /* Шаблон Vue схлопывает перенос строки между тегами в ничто, и метка
     прилипала к тексту цели вплотную («(необязательно)МОЖНО КУПИТЬ»).
     Отступ задаём стилем, а не &nbsp; в разметке. */
  .objective-desc .fir { margin-left: 6px; }

  /* Вес квеста в цепочке. Стоит в мете, а не в блоке наград: это
     единственный факт про «куда дальше», который нужен и в компактном
     виде — по нему видно узкое место вайпа, не раскрывая карточку. */
  .quest-unlocks { font-variant-numeric: tabular-nums; color: var(--text-dim); }
  .quest-unlocks b { color: var(--accent); font-weight: 600; }

  /* ---- награды квеста ----
     Блок раскрывается вместе с целями. В компактном виде его нет
     намеренно: список там и так плотный, а «зачем» — вопрос второго
     взгляда, не первого. */
  .quest-rewards {
    margin-top: var(--sp-2);
    padding-top: var(--sp-2);
    border-top: 1px solid var(--line);
  }

  .rw-list {
    list-style: none;
    margin: 4px 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 3px;
  }

  .rw {
    display: grid;
    grid-template-columns: 96px minmax(0, 1fr);
    align-items: baseline;
    gap: 1px var(--sp-2);
    font-size: 12px;
  }

  /* Тот же довод, что у .breason__kind: «покупка», «крафт»,
     «репутация» — это вид награды, то есть содержание строки, а не
     подпись блока. Подпись блока тут одна — маркер «НАГРАДЫ ЗА СДАЧУ». */
  .rw__kind {
    font-family: var(--font-display);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: .1em;
    text-transform: uppercase;
    color: var(--text-dim);
  }

  .rw__text { color: var(--text-dim); min-width: 0; line-height: 1.4; }
  .rw__text b { color: var(--text); font-weight: 600; }
  /* Разблокировка покупки и крафта — то, ради чего квест берут
     заранее: подсвечиваем сам факт, а не только предмет. */
  .rw.is-unlock .rw__kind { color: var(--ok); }

  /* Награда предметами раньше была одной строкой текста через запятую —
     восемь названий подряд читаются как абзац, а не как список добычи.
     Иконки возвращают предмету лицо: узнаётся он по картинке быстрее,
     чем по названию из пяти слов. Название рядом остаётся — иконка 26px
     не отличает два похожих магазина. */
  .rw__items {
    display: flex;
    flex-wrap: wrap;
    gap: 4px var(--sp-2);
  }

  .rw-item {
    display: flex;
    align-items: center;
    gap: 5px;
    min-width: 0;
    max-width: 100%;
    color: var(--text-dim);
    line-height: 1.25;
  }

  .rw-item__name { min-width: 0; overflow-wrap: anywhere; }
  .rw-item__name b { color: var(--text); font-weight: 600; }

  /* У разблокировок иконка стоит ВНУТРИ фразы («Прапор начнёт
     продавать …»), а не отдельным чипом: фраза — это и есть награда,
     рвать её на части ради картинки нельзя. */
  .rw__text .item-icon {
    display: inline-block;
    vertical-align: -6px;
    margin-right: 4px;
  }

  /* Английский слаг станции убежища. Русского имени станции у нас нет
     вовсе (фид убежища мы не качаем), и выдавать слаг за название
     нельзя — поэтому он набран как чужая строка и объяснён в title. */
  .rw__slug {
    font-family: var(--font-display);
    color: var(--text);
    border-bottom: 1px dotted var(--line-strong);
    cursor: help;
  }

  /* Аванс при взятии квеста — не награда за сдачу и не повод его
     брать: игрок получит это, как только возьмёт, и планировать по
     нему нельзя. Пунктир и тусклый тон отделяют его от настоящих
     наград так же, как непроверяемое требование от блокировки. */
  .rw-start {
    margin: var(--sp-2) 0 0;
    padding-left: 8px;
    border-left: 2px dashed var(--line-strong);
    font-size: 11px;
    color: var(--text-faint);
    line-height: 1.4;
  }

