/* ============================================================
   RFP Automation (TenderGuru prototype) — app UI tokens.
   Light content canvas + dark shell per the design-team spec:
   compliance-sensitive, hours-long daily reading -> full dark
   mode fails AA/fatigue tests, so only the top bar stays dark
   as the brand anchor. See 03_marketing/ + design artifact for
   the full rationale and page-by-page spec.
   ============================================================ */
:root {
    /* shell (dark, brand anchor) */
    --shell-bg: #0A1220;
    --shell-surface: #0D1A2E;
    --shell-border: #1C2E44;
    --shell-text: #E8F0F8;
    --shell-text-2: #6A8AAA;
    --shell-glow: #00A8E8; /* cyan-500, per 01_product/ui_design/color_critique.md — locked to the canonical logo gradient, not the old landing-page #00C8FF */

    /* content canvas (light) */
    --bg: #F4F7FA;
    --surface: #FFFFFF;
    --surface-hover: #EAF0F6;
    --border: #E1E8EF;
    --border-strong: #C7D3DE;

    /* text */
    --text: #101826;
    --text-muted: #45566B;
    --text-faint: #7C8FA3;

    /* interactive brand (deepened cyan for AA on light) */
    --cyan: #0072B8;
    --cyan-deep: #005E96;
    --cyan-tint: #E5F3FB;
    --cyan-accent: #00A8E8; /* cyan-500 — decorative/logo-accent only, never text/fill (see color_critique.md) */
    --gold: #8A5808;
    --gold-accent: #D4A820;

    /* status */
    --ok-t: #146B49; --ok-b: #E3F5EC; --ok-d: #1F8A5F;
    --warn-t: #7A5C10; --warn-b: #FBF3DF; --warn-d: #C97E2E;
    --bad-t: #A83636; --bad-b: #FBEAEA; --bad-d: #C64545;
    --off-t: #45566B; --off-b: #E4E9EE; --off-d: #7C8FA3; /* off-b darkened from #EEF1F4 — was ~1.13:1 against white cards, effectively invisible */

    --r-surface: 14px; /* было 4px — тикет 27, синхронизация с обновлённым platform_prototype.html (модуль 26) */
    --r-control: 8px;  /* было 6px — тикет 27 */
    --r-tag: 3px;
    --r-pill: 999px;
    --surface-elevated-shadow: 0 2px 8px rgba(10, 18, 32, .08), 0 0 0 1px rgba(10, 18, 32, .04); /* тикет 27 */
}

* { box-sizing: border-box; }

html { -webkit-font-smoothing: antialiased; }

body {
    margin: 0;
    background: var(--bg);
    color: var(--text);
    font-family: "Inter", -apple-system, "Segoe UI", sans-serif;
    font-size: 14px;
    line-height: 1.5;
}

h1, h2 {
    font-family: "Sora", "Inter", "Segoe UI", sans-serif;
    font-weight: 700;
    margin: 0 0 4px;
    color: var(--text);
}

a { color: var(--cyan); }

/* ---------------- top bar (dark shell) ---------------- */
.topnav {
    display: flex;
    align-items: center;
    gap: 16px;
    height: 56px;
    padding: 0 24px;
    background: var(--shell-bg);
    border-bottom: 1px solid var(--shell-border);
}
.brand {
    font-family: "Sora", "Inter", "Segoe UI", sans-serif;
    font-weight: 700;
    font-size: 15px;
    color: var(--shell-text);
    display: flex;
    align-items: center;
    gap: 9px;
    text-decoration: none; /* тикет 113 — теперь ссылка в topnav незалогиненных страниц */
}
.brand-icon {
    width: 20px; height: 20px; flex-shrink: 0;
    /* Canonical mark — see 01_product/ui_design/logo/logo_spec.html.
       Flat gradient fills only, no glow/blur/drop-shadow. */
}
.spacer { flex: 1; }
.user-badge { color: var(--shell-text-2); font-size: 13px; display: flex; align-items: center; gap: 10px; }
.role-pill {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 3px 8px 3px 7px;
    border-radius: var(--r-tag);
    border-left: 3px solid var(--shell-glow);
    background: rgba(0, 168, 232, 0.14);
    color: var(--shell-text);
    font-size: 11px;
    font-family: "JetBrains Mono", ui-monospace, monospace;
    text-transform: uppercase;
    letter-spacing: .02em;
}
.link-btn {
    background: none;
    border: 1px solid var(--shell-border);
    color: var(--shell-text-2);
    cursor: pointer;
    font-size: 12.5px;
    padding: 6px 12px;
    border-radius: var(--r-control);
}
.link-btn:hover { color: var(--shell-text); border-color: var(--shell-text-2); }

/* ---------------- app shell (sidebar + topbar), per platform_prototype.html ---------------- */
.app { display: flex; min-height: 100vh; width: 100%; }

.sidebar {
    width: 240px; flex: 0 0 auto; background: var(--shell-bg);
    border-right: 1px solid var(--shell-border);
    display: flex; flex-direction: column;
}
.sidebar-brand {
    height: 56px; display: flex; align-items: center; gap: 9px; padding: 0 20px;
    border-bottom: 1px solid var(--shell-border); flex: 0 0 auto;
    text-decoration: none; /* тикет 114 — теперь <a>, визуально не должен отличаться от старого <div> */
}
.sidebar-brand:hover { background: rgba(255,255,255,.03); }
.sidebar-brand .brand-icon { width: 22px; height: 22px; flex-shrink: 0; }
.sidebar-brand .brand-word { font-family: "Sora", "Inter", "Segoe UI", sans-serif; font-weight: 700; font-size: 15px; color: var(--shell-text); }

.nav { flex: 1 1 auto; padding: 12px 10px; }
/* Тикет 27 — группы сайдбара по образцу platform_prototype.html:97 (.nav-group-label),
   тот же паттерн, что .tile-label/.kcol-head (Space Mono, uppercase, letter-spacing). */
.nav-group-label {
    font-family: "JetBrains Mono", ui-monospace, monospace; font-size: 10px; letter-spacing: .14em;
    text-transform: uppercase; color: var(--shell-text-2); padding: 14px 12px 6px;
}
.nav-group-label:first-child { padding-top: 2px; }
.nav-item {
    display: flex; align-items: center; gap: 12px; width: 100%; padding: 10px 12px; margin-bottom: 2px;
    border-radius: var(--r-control); color: var(--shell-text-2); text-decoration: none;
    font-size: 14px; font-weight: 500;
}
.nav-item:hover { background: rgba(255,255,255,.04); color: var(--shell-text); }
.nav-item.active { background: var(--shell-surface); color: var(--shell-text); border-left: 2px solid var(--shell-glow); padding-left: 10px; }

.shell-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.topbar {
    height: 56px; flex: 0 0 auto; background: var(--shell-bg); border-bottom: 1px solid var(--shell-border);
    display: flex; align-items: center; gap: 16px; padding: 0 24px;
}
.canvas { flex: 1 1 auto; padding: 32px; max-width: 1200px; width: 100%; margin: 0 auto; }
@media (max-width: 1024px) { .canvas { padding: 16px; } }
/* Тикет 191 — см. base.html canvas_class block. Только для дашборда (плотная
   6-колоночная канбан-доска) — не меняет читающую ширину остального продукта. */
.canvas--wide { max-width: 1400px; }

/* ---------------- layout ---------------- */
.container { max-width: 960px; margin: 0 auto; padding: 40px 24px; }
.muted { color: var(--text-muted); }
.eyebrow {
    font-family: "JetBrains Mono", ui-monospace, monospace;
    font-size: 11px;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--text-faint);
    display: block;
    margin-bottom: 6px;
}

/* ---------------- user-picker (login) ---------------- */
.user-grid { display: flex; gap: 16px; flex-wrap: wrap; margin-top: 24px; }
.user-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--r-surface);
    box-shadow: var(--surface-elevated-shadow);
    padding: 22px 18px;
    width: 168px;
    text-align: center;
}
.user-card:hover { border-color: var(--border-strong); }
.avatar {
    width: 48px; height: 48px; border-radius: 50%;
    background: linear-gradient(135deg, var(--cyan-accent), var(--gold-accent));
    color: #06121e;
    display: flex; align-items: center; justify-content: center;
    font-family: "Sora", "Inter", "Segoe UI", sans-serif;
    font-weight: 700; margin: 0 auto 12px;
}
.name { font-weight: 500; margin-bottom: 8px; font-size: 13.5px; }
.user-card .role-pill { background: var(--off-b); color: var(--off-t); border-left-color: var(--off-d); }
.user-card button, .user-card .card-btn {
    display: block; margin-top: 14px; width: 100%; padding: 9px; box-sizing: border-box;
    background: var(--cyan); color: #fff; border: none; text-decoration: none;
    border-radius: var(--r-control); cursor: pointer; font-weight: 500; font-size: 13px;
}
.user-card button:hover, .user-card .card-btn:hover { background: var(--cyan-deep); }
/* Тикет 109 — карточка password-защищённого пользователя в пикере (login.html)
   отличается приглушённой рамкой, чтобы читаться как «другой путь входа»,
   не идентично passwordless-карточкам рядом (реальный смешанный аккаунт
   после self-serve регистрации, тикет 07). */
.user-card--password { border-style: dashed; }
.user-card--password .card-btn { background: var(--off-d); }
.user-card--password .card-btn:hover { background: var(--off-t); }

/* ---------------- Тикет 114 — /signup сплит-лейаут ----------------
   Boxed split-card внутри существующего .container (960px, не edge-to-edge —
   /signup рендерится через base.html:167 .container как и все незалогиненные
   страницы, менять эту обёртку ради одной страницы не стал). Левая
   decorative-панель использует тот же принцип градиента cyan→gold, что
   .avatar чуть выше (app.css:191, тикет 72's Live Scoring card) — маркетинговый
   декоративный элемент, НЕ функциональный status-тег, поэтому не нарушает
   хардрул "gold только для tag--won" (design_system_spec.md §2, тот же
   комментарий уже есть у .avatar/.tile-value.tier-studio в этом файле). */
.signup-split {
    display: flex;
    min-height: 500px;
    margin-top: 16px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--r-surface);
    box-shadow: var(--surface-elevated-shadow);
    overflow: hidden;
}
.signup-pitch {
    flex: 0 0 42%;
    position: relative;
    overflow: hidden;
    padding: 30px 30px 34px;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    background: var(--shell-bg);
}
.signup-pitch::before {
    /* Тикет 114, Standards-ревью: изначально был cyan→gold градиент,
       обоснованный (неверно) прецедентами .avatar/тикета 72 — оба не
       выдерживают проверки (.avatar — необоснованный pre-existing долг без
       тикета, тикет 72 относится к docs/index.html, отдельной системе
       токенов лендинга, не к этому app.css). design_system_spec.md §2 и
       минимум 6 других мест в этом же файле прямо говорят: gold зарезервирован
       ТОЛЬКО под .tag--won, hard rule, без исключений. Cyan-only glow. */
    content: "";
    position: absolute;
    top: -30%;
    right: -35%;
    width: 85%;
    height: 85%;
    background: var(--cyan-accent);
    opacity: .22;
    filter: blur(70px);
    border-radius: 50%;
    pointer-events: none;
}
.signup-pitch::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(200deg, transparent 35%, var(--shell-bg) 92%);
    pointer-events: none;
}
.signup-pitch-brand {
    position: relative; z-index: 1;
    display: flex; align-items: center; gap: 9px;
    font-family: "Sora", "Inter", "Segoe UI", sans-serif; font-weight: 700; font-size: 15px;
    color: var(--shell-text); text-decoration: none;
}
.signup-pitch-brand .brand-icon { width: 22px; height: 22px; flex-shrink: 0; }
.signup-pitch-brand:hover { color: var(--shell-text); }
.signup-pitch-copy { position: relative; z-index: 1; margin-top: 40px; }
.signup-pitch-copy .signup-pitch-headline {
    font-family: "Sora", "Inter", "Segoe UI", sans-serif; font-weight: 800; font-size: 23px; line-height: 1.32;
    color: var(--shell-text); margin: 0 0 12px;
}
.signup-pitch-copy p {
    font-size: 13px; line-height: 1.6; color: var(--shell-text-2); margin: 0; max-width: 300px;
}
/* Тикет 114 — дозаполнение по мокапу v2 (10.08.2026): буллеты-фичи и цитата-
   отзыв были в исходном ТЗ/мокапе, но пропущены при первой реализации
   (заметно только после того, как мокап стал доступен через WebFetch).
   Cyan-only, тот же hard rule, что уже применён к .signup-pitch::before. */
.signup-pitch-features {
    position: relative; z-index: 1; margin: 22px 0 0; padding: 0; list-style: none;
    display: flex; flex-direction: column; gap: 10px;
}
.signup-pitch-features li {
    position: relative; padding-left: 20px; font-size: 12px; line-height: 1.5; color: var(--shell-text-2);
}
.signup-pitch-features li::before {
    content: ""; position: absolute; left: 2px; top: 5px;
    width: 6px; height: 6px; border-radius: 50%; background: var(--cyan-accent);
}
.signup-pitch-quote {
    position: relative; z-index: 1; margin: 26px 0 0; padding-top: 16px;
    border-top: 1px solid rgba(255,255,255,0.12);
}
.signup-pitch-quote p {
    font-size: 12px; font-style: italic; line-height: 1.55; color: var(--shell-text-2); margin: 0 0 6px;
}
.signup-pitch-quote cite {
    display: block; font-style: normal; font-size: 10.5px; letter-spacing: .03em;
    font-family: "JetBrains Mono", ui-monospace, monospace; color: var(--text-faint);
}
.signup-legal {
    text-align: center; font-size: 10.5px; line-height: 1.6; color: var(--text-faint); margin: 14px 0 0;
}
.signup-form-col {
    flex: 1 1 auto; min-width: 0;
    padding: 30px 40px;
    display: flex; flex-direction: column;
}
.signup-back {
    align-self: flex-start;
    font-size: 12.5px; color: var(--text-faint); text-decoration: none; margin-bottom: 22px;
}
.signup-back:hover { color: var(--text-muted); }
.signup-form-wrap { width: 100%; max-width: 360px; margin: 0 auto; }
.signup-trust { text-align: center; font-size: 11.5px; color: var(--text-faint); margin: 10px 0 0; }
.signup-divider {
    display: flex; align-items: center; gap: 10px; margin: 22px 0;
    color: var(--text-faint); font-family: "JetBrains Mono", ui-monospace, monospace; font-size: 10.5px; letter-spacing: .06em;
}
.signup-divider::before, .signup-divider::after { content: ""; flex: 1 1 auto; height: 1px; background: var(--border); }
.signup-google { width: 100%; text-align: center; }
@media (max-width: 860px) {
    .signup-pitch { display: none; }
    .signup-split { min-height: 0; }
    .signup-form-col { padding: 26px 20px; }
}

/* ---------------- dashboard ---------------- */
/* Компактные инфо-чипы, не полноразмерные карточки — 2 коротких значения не должны
   занимать столько же места, сколько контентные .acc-card ниже (Денис, 05.08.2026:
   "иконки воруют место"). Переиспользует масштаб/паттерн .tag (pill, левый акцент). */
.stat-tiles { display: flex; gap: 8px; margin-top: 14px; flex-wrap: wrap; }
.tile {
    display: inline-flex; align-items: baseline; gap: 6px;
    /* Тикет 56 — было var(--surface-2), нигде не объявлен (grep подтвердил
       единственное упоминание — здесь), резолвился в прозрачный. --off-b —
       тот же нейтрально-серый tint, что .tag--off использует для "нейтрального"
       состояния той же chip-семьи (см. комментарий выше: ".tile переиспользует
       масштаб/паттерн .tag"), не белый var(--surface) — .tile не полноразмерная
       карточка типа .acc-card/.kcard, а маленький pill-чип, тот же смысл, что
       у .tag--off, просто без цветного левого акцента. */
    background: var(--off-b); border: 1px solid var(--border);
    border-radius: var(--r-tag); padding: 5px 11px;
}
.tile-label {
    font-family: "JetBrains Mono", ui-monospace, monospace; font-size: 9.5px; text-transform: uppercase;
    letter-spacing: .05em; color: var(--text-faint);
}
/* Тикет 166 — карточка тендера (rfp_detail.html): грид метаданных (Заказчик/
   Страна/Бюджет/Дедлайн/Формат) по референсу Дениса на чистоту/сканируемость —
   переиспользует .tile-label/.tile-value (тот же, что вкладка Score в этом же
   файле), не новый визуальный язык. auto-fit + minmax — колонки сами
   подстраиваются под ширину экрана, честно перетекают на мобильном, не режутся. */
.tender-meta-grid {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
    gap: 14px 24px; margin-top: 16px;
}
.tile-value { font-family: "Sora", "Inter", "Segoe UI", sans-serif; font-size: 14px; font-weight: 700; color: var(--text); }
.tile-value.tier-solo { color: var(--off-t); }
.tile-value.tier-team { color: var(--cyan-deep); } /* тот же токен, что .dash-header .tile-value ниже задаёт для всех значений — не конфликт */
.tile-value.tier-studio {
    /* Было var(--gold) — прямое нарушение хардрула "gold только для tag--won" (см. выше в
       файле), найдено при перестилизации этого чипа под тикет 53. tier-studio — реальные
       custom/Enterprise-аккаунты (BFC-тип, см. tier_naming_canonical.md), не мёртвый код.
       Класс tier-* используется только на дашборде (grep templates/), так что этот фикс
       не нужно отдельно скоупить под .dash-header — он и так нигде больше не применяется. */
    color: var(--cyan-deep);
}

/* Тикет 210 — дедлайн-бейдж в .tender-meta-grid: те же семантические токены,
   что --bad-t/--warn-t уже красят score-ring.low/.tag--lost и .tag--warn
   выше в этом файле — не новая палитра. .dl-badge--normal намеренно НЕ
   стилизуется отдельно (наследует обычный .tile-value) — >14 дней "не шуметь,
   когда шуметь не о чем" (см. тикет), только bad/warn получают акцент. */
.dl-badge--bad { color: var(--bad-t); }
.dl-badge--warn { color: var(--warn-t); }
.dl-badge-dot {
    display: inline-block; width: 6px; height: 6px; border-radius: 50%;
    margin: 0 4px 0 8px; background: currentColor; vertical-align: middle;
}

/* Тикет 210 — кнопки жизненного цикла тендера (Отметить как поданный/Выиграно/
   Проиграно/Отклонить), перенесённые из шапки в подвал карточки — разделитель
   сверху отделяет их от метаданных/файлов, тот же .row-приём (flex-wrap,
   gap), что уже применяется по всему файлу для рядов кнопок, просто теперь
   внутри карточки, не снаружи. */
.card-actions-footer {
    display: flex; flex-wrap: wrap; align-items: flex-start; gap: 10px;
    margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--border);
}

/* Тикет 53 — шапка дашборда: заголовок слева, чипы справа на одном уровне, cyan-акцент
   бренда вместо нейтрального серого. Скоуплено конкретно под .dash-header (не переопределяет
   голые .tile/.tile-label/.tile-value выше) — иначе тот же класс, переиспользуемый в
   credits.html/settings.html (те же "статы"-чипы, другой контекст) и голый .tile-label в
   rfp_detail.html (label без чипа-обёртки), тоже незаметно перекрасился бы — этого ТЗ не
   просило и Денис показывал скриншот именно шапки дашборда, не остальных страниц. */
.dash-header { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 12px; }
.dash-header .stat-tiles { margin-top: 0; }
.dash-header .tile {
    /* Те же токены, что .tag--active (--cyan-tint фон, --cyan-deep текст, --cyan рамка) —
       не выдуманный новый оттенок. Gold (--gold-accent/--gold) здесь не используется —
       зарезервирован только под .tag--won, см. хардрул выше в файле. */
    background: var(--cyan-tint); border-color: var(--cyan);
}
.dash-header .tile-label { color: var(--cyan-deep); }
.dash-header .tile-value { color: var(--cyan-deep); }
.dash-header .tile-value.tier-solo { color: var(--off-t); }

/* Тикет 157 — «Загрузить»/«Источники»/«Фильтр» на /rfps объединены в одну
   панель вместо 3 отдельных .acc-card (двойные отступы/границы между тремя
   почти всегда свёрнутыми блоками съедали место без пользы). Одна внешняя
   .acc-card-панель + внутренние секции разделены border-top вместо своих
   собственных border/shadow/margin — тот же принцип, что уже .row-foot
   (граница-разделитель внутри карточки, не новая карточка). */
/* Тикет 164 удалил обёртку .toolbar-panel (панель больше не всегда на
   странице, живёт в .toolbar-popover) — сами .toolbar-section переиспользуются
   внутри popover'а без изменений. */
.toolbar-section { padding: 16px 20px; }
.toolbar-section + .toolbar-section { border-top: 1px solid var(--border); }
/* Пояснение про порог 80% раньше висело постоянным абзацем под полем —
   теперь title-тултип на иконке, виден только по наведению внутри и так
   уже развёрнутой формы (не занимает место в свёрнутом состоянии). */
.info-icon {
    display: inline-flex; align-items: center; justify-content: center;
    width: 15px; height: 15px; border-radius: 50%; border: 1px solid var(--text-faint);
    color: var(--text-faint); font-size: 10.5px; font-style: normal; cursor: help; flex-shrink: 0;
}

/* Живой репорт Дениса (10.08.2026) — фильтр «Регион» был single-select
   (<select>), не давал выбрать несколько. Чекбокс-попап — тот же принцип
   multi-select фильтра, что у конкурентов, без Ctrl+клика по нативному
   <select multiple> (неочевидный паттерн для большинства пользователей). */
/* Тикет 164 — «Источники»/«Фильтр» на /rfps спрятаны за иконкой ⚙, тот же
   визуальный язык (border/shadow/radius), что уже .multiselect-panel чуть
   ниже — не изобретаем новый стиль popover для той же по сути механики. */
.toolbar-popover-wrap { position: relative; }
.icon-btn {
    display: inline-flex; align-items: center; justify-content: center;
    width: 30px; height: 30px; border-radius: var(--r-control);
    background: var(--surface); color: var(--text); border: 1px solid var(--border-strong);
    cursor: pointer; font-size: 15px; line-height: 1;
}
.icon-btn:hover { background: var(--surface-hover); }
.toolbar-popover {
    position: absolute; top: calc(100% + 6px); left: 0; z-index: 20;
    background: var(--surface); border: 1px solid var(--border-strong); border-radius: var(--r-surface);
    box-shadow: var(--surface-elevated-shadow); min-width: 340px; max-width: 420px;
    padding: 16px;
}
.toolbar-popover-right { left: auto; right: 0; }
/* Тикет 164 (возврат, 11.08.2026) — явная кнопка-крестик закрытия попапа
   настроек, по утверждённому мокапу v2 (.panel-head/.close-x) — клик вне/
   Escape уже были, но живой репорт Дениса про "не закрывается" показал, что
   явный видимый способ закрыть тоже нужен, не полагаться только на два
   неочевидных способа. */
/* Тикет 178 (возврат, находка 8) — заголовок попапа (лейбл + ✕), теперь
   используется двумя попапами (Settings/Загрузить) — вынесено в класс вместо
   дублирования inline display:flex на каждом месте использования. */
.toolbar-popover-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.toolbar-popover-close {
    width: 22px; height: 22px; border-radius: var(--r-control);
    background: var(--surface-hover); border: none; color: var(--off-t);
    cursor: pointer; font-size: 12px; line-height: 1; flex-shrink: 0;
}
.toolbar-popover-close:hover { background: var(--off-b); }
@media (max-width: 480px) {
    /* Тикет 164 задумывал "фиксируем к краям экрана" через left/right:8px,
       но popover — position:absolute внутри узкого (~30px, вокруг иконки)
       .toolbar-popover-wrap (position:relative) — left/right считаются от
       ГРАНИЦ ЭТОГО WRAP'а, не от viewport, поэтому реально получался бокс
       шириной в единицы px у самого края экрана, а не "во всю ширину минус
       8px с каждой стороны", как предполагалось. Найдено тикетом 178 (живая
       мобильная проверка редизайна панели фильтров, 11.08.2026) — баг
       общий для обоих popover'ов (Загрузить и Настройки), не специфичен
       для фильтра. Фикс — position:fixed на этом брейкпоинте: left/right
       становятся честно viewport-relative. */
    /* top:calc(100% + 6px) из базового правила резолвился бы против viewport
       при position:fixed (проценты у fixed считаются от initial containing
       block, не от кнопки) — фиксированный top:5vh вместо этого, popover
       превращается в подобие мобильного "листа" сверху экрана, что не
       зависит от скролл-позиции кнопки-триггера на странице. */
    .toolbar-popover { position: fixed; min-width: 0; max-width: none; left: 8px; right: 8px; top: 5vh; }
    .toolbar-popover-right { left: 8px; right: 8px; }
}

.multiselect { position: relative; display: inline-block; }
.multiselect-panel {
    position: absolute; top: calc(100% + 4px); left: 0; z-index: 20;
    background: var(--surface); border: 1px solid var(--border-strong); border-radius: var(--r-control);
    box-shadow: var(--surface-elevated-shadow); max-height: 260px; overflow-y: auto;
    min-width: 220px; padding: 6px 0;
}
.multiselect-option {
    display: flex; align-items: center; gap: 8px; padding: 6px 12px; font-size: 13px;
    cursor: pointer; white-space: nowrap;
}
.multiselect-option:hover { background: var(--surface-hover); }

/* Тикет 213 — реальный чекбокс-квадрат вместо цветной плашки-обводки вокруг
   всей строки (было — тикет 169, см. git blame). Денис: при быстром скролле
   по длинному списку (200+ стран) цветовой фон плашки недостаточно заметен,
   легко потерять что уже отмечено; референс — плоский список с квадратом
   слева, не цветная обводка вокруг строки. Мокап (утверждён, ревью
   дизайнера+критика) — https://claude.ai/code/artifact/a5643345-1344-4d4c-a0cf-c4f925272bfd
   Классы .filter-chip/.filter-chip--in/.filter-chip--out НЕ переименованы —
   их продолжает переключать существующий JS (onchange/toggleFilterChip),
   здесь только меняется, что эти классы визуально означают: раньше красили
   фон/рамку всей строки, теперь только управляют состоянием вложенного
   .chip-mark (единственный переиспользуемый квадрат-компонент — Region/CPV/
   Источники получили тот же <span class="chip-mark"> в разметке, что уже
   был у тройного состояния тикета 169). */
.filter-chip-grid { display: flex; flex-direction: column; gap: 1px; margin-bottom: 4px; }
/* Регион — единственная группа с сотнями пунктов (189 уникальных значений в
   реальных данных аккаунта, тикет 178) — 3 колонки как в утверждённом
   мокапе, иначе список неоправданно длинный вертикально. Остальные группы
   короче (≤30 пунктов) — одна колонка, ничего не меняет их layout. */
/* auto-fill, не жёсткие repeat(3,...) — на узкой панели (мобильный, попап на
   всю ширину экрана минус отступы) фиксированные 3 колонки по 150px не
   влезли бы (450px+ минимум), пришлось бы городить отдельный media query;
   auto-fill сам схлопывается до 1-2 колонок, когда места мало. */
#regionChipGrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 2px 12px; }
.filter-chip {
    font-size: 13px; border-radius: var(--r-control); padding: 6px 6px;
    display: flex; align-items: center; gap: 9px; cursor: pointer;
    border: none; color: var(--text); background: none; user-select: none;
}
.filter-chip:hover { background: var(--surface-hover); }
/* .filter-chip--in/--out больше не красят строку целиком (см. докстринг
   выше) — только цвет текста, для минимального дополнительного сигнала
   поверх квадрата, не как основной носитель состояния. */
.filter-chip--in { color: var(--text); font-weight: 500; }
.filter-chip--out { color: var(--text-faint); text-decoration: line-through; }
.chip-mark {
    width: 18px; height: 18px; border-radius: 5px; border: 1.5px solid var(--text-faint);
    background: var(--surface); flex: 0 0 auto; position: relative;
}
/* Обе формы разметки: .chip-mark как потомок .filter-chip--in (реальные
   группы фильтра — класс на родительском <label>/<span>) И .chip-mark с
   классом .filter-chip--in НА СЕБЕ (легенда — компактная разметка одним
   элементом, см. ниже) — оба должны красить один и тот же квадрат. */
.filter-chip--in .chip-mark, .chip-mark.filter-chip--in { background: var(--cyan); border-color: var(--cyan); }
.filter-chip--in .chip-mark::after, .chip-mark.filter-chip--in::after {
    content: ""; position: absolute; left: 5.5px; top: 2px; width: 5px; height: 9px;
    border: solid #fff; border-width: 0 2px 2px 0; transform: rotate(40deg);
}
.filter-chip--out .chip-mark, .chip-mark.filter-chip--out { background: var(--bad-d); border-color: var(--bad-d); }
.filter-chip--out .chip-mark::before, .filter-chip--out .chip-mark::after,
.chip-mark.filter-chip--out::before, .chip-mark.filter-chip--out::after {
    content: ""; position: absolute; left: 3px; top: 7.5px; width: 10px; height: 2px; background: #fff;
}
.filter-chip--out .chip-mark::before, .chip-mark.filter-chip--out::before { transform: rotate(45deg); }
.filter-chip--out .chip-mark::after, .chip-mark.filter-chip--out::after { transform: rotate(-45deg); }

/* Легенда тройного состояния — одна, постоянно видна в верхней части панели
   фильтров (не дублируется под каждой группой, замечание дизайнера при
   ревью мокапа тикета 213). */
.filter-legend { display: flex; gap: 16px; flex-wrap: wrap; margin: 0 0 12px; font-size: 11.5px; color: var(--text-muted); }
.filter-legend-item { display: flex; align-items: center; gap: 6px; }
.filter-legend .chip-mark { width: 13px; height: 13px; border-radius: 4px; position: static; }
.filter-legend .chip-mark::after, .filter-legend .chip-mark::before { display: none; }

/* Тикет 178 — плоская структура панели фильтров: каждая группа (Источники,
   Регион, Сектор/CPV, ...) — свой <details>, один визуальный паттерн,
   нативное сворачивание/разворачивание без ручного JS-тумблера на каждую
   группу (в отличие от прежних filter-summary/sources-summary — ручных
   пар "свёрнуто ⇄ развёрнуто" с отдельными обработчиками на каждую). */
.settings-flat-label {
    font-family: "JetBrains Mono", ui-monospace, monospace; font-size: 11px; letter-spacing: .05em;
    text-transform: uppercase; color: var(--text-muted); margin: 2px 0 2px;
}
.filter-accordion { border: 1px solid var(--border); border-radius: var(--r-control); margin-bottom: 8px; overflow: hidden; }
.filter-accordion > summary {
    padding: 10px 12px; cursor: pointer; font-size: 13px; list-style: none;
    display: flex; align-items: center; justify-content: space-between; gap: 8px;
}
.filter-accordion > summary::-webkit-details-marker { display: none; }
.filter-accordion > summary::after { content: "▾"; color: var(--text-muted); font-size: 11px; transition: transform .15s; }
.filter-accordion[open] > summary::after { transform: rotate(180deg); }
.filter-accordion > summary:hover { background: var(--surface-hover); }
.filter-accordion-body { padding: 4px 12px 12px; border-top: 1px solid var(--border); }

.chip-search {
    width: 100%; font-size: 12.5px; padding: 6px 10px; margin: 8px 0;
    border: 1px solid var(--border-strong); border-radius: var(--r-control);
    background: var(--surface); color: var(--text);
}

/* Двухползунковый диапазон (Бюджет) — тот же визуальный язык, что у
   одноползунковой Релевантности (квадратик с рамкой цвета cyan + слайдер
   под ним), см. тикет 178 п.4. Классический паттерн "два <input type=range>
   на одном треке": сам range прозрачный/pointer-events:none, кликабелен
   только его thumb — иначе верхний слайдер целиком перехватывал бы клики
   по нижнему. */
.range-value-box {
    font-family: "JetBrains Mono", ui-monospace, monospace; font-size: 12.5px; width: 100px; padding: 5px 8px;
    border: 1px solid var(--cyan); border-radius: var(--r-control); text-align: right;
    background: var(--surface); color: var(--text);
}
.dual-slider-track { position: relative; height: 26px; margin-top: 10px; }
.dual-slider-bg { position: absolute; left: 0; right: 0; top: 11px; height: 4px; background: var(--border-strong); border-radius: 2px; }
.dual-slider-fill { position: absolute; top: 11px; height: 4px; background: var(--cyan); border-radius: 2px; }
.dual-slider-track input[type=range] {
    position: absolute; left: 0; right: 0; top: 3px; width: 100%; margin: 0; height: 18px;
    -webkit-appearance: none; appearance: none; background: transparent; pointer-events: none;
}
.dual-slider-track input[type=range]::-webkit-slider-thumb {
    -webkit-appearance: none; pointer-events: auto; width: 16px; height: 16px; border-radius: 50%;
    background: var(--cyan); cursor: pointer; margin-top: 1px; position: relative;
}
.dual-slider-track input[type=range]::-moz-range-thumb {
    pointer-events: auto; width: 16px; height: 16px; border-radius: 50%; background: var(--cyan);
    border: none; cursor: pointer;
}
.dual-slider-track input[type=range]::-webkit-slider-runnable-track { height: 4px; background: transparent; }
.dual-slider-track input[type=range]::-moz-range-track { height: 4px; background: transparent; }

/* Тикет 178 п.6 — Применить/Сбросить/Свернуть, одинаковый визуальный вес,
   все три настоящие <button>. */
.filter-actions-row { display: flex; gap: 10px; margin-top: 12px; }
.filter-actions-row button { flex: none; }

/* ---------------- admin ---------------- */
.acc-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-surface); box-shadow: var(--surface-elevated-shadow); padding: 20px; margin-bottom: 16px; }
.acc-card h2 { font-size: 16px; display: flex; align-items: center; gap: 10px; }
.row { display: flex; gap: 12px; align-items: center; margin-bottom: 10px; flex-wrap: wrap; }
.row label { color: var(--text-faint); font-size: 12.5px; min-width: 90px; font-family: "JetBrains Mono", ui-monospace, monospace; text-transform: uppercase; letter-spacing: .03em; }
select, input[type=text], input[type=password], input[type=email], input[type=date] {
    background: var(--surface); color: var(--text); border: 1px solid var(--border-strong);
    border-radius: var(--r-control); padding: 7px 10px; font-family: inherit; font-size: 13px;
}
select:focus, input:focus { outline: 2px solid var(--cyan); outline-offset: 1px; border-color: var(--cyan); }
.feat-grid { display: flex; flex-wrap: wrap; gap: 12px; }
.feat-grid label { display: flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--text); min-width: auto; }
button.small, .btn {
    padding: 7px 14px; background: var(--cyan); color: #fff; border: none;
    border-radius: var(--r-control); cursor: pointer; font-weight: 500; font-size: 13px;
    /* Модуль 13, баг 2: .btn на <a> (напр. "Review ↓" в approvals.html) без
       этого переносился по словам в узкой колонке таблицы — <a> инлайновый
       по умолчанию, текст в нём заворачивается как обычный текст. */
    white-space: nowrap; display: inline-block; text-decoration: none;
}
button.small:hover, .btn:hover { background: var(--cyan-deep); }

/* Тикет 42 — до сих пор ЕДИНСТВЕННЫМ стилем кнопки во всём продукте был
   .small/.btn (один и тот же cyan fill везде), поэтому "Взять в работу" ничем
   не отличалась от соседних служебных кнопок ("Прикрепить"/"Пересчитать") —
   ровно жалоба Дениса. .btn-cta досоставляет .small (используется вместе,
   class="small btn-cta" — не дублирует background/border/radius/cursor/
   white-space/display/text-decoration), переопределяет только размер/вес —
   крупнее, для момента "явный следующий шаг" (сейчас только Overview →
   "Взять в работу" после анализа). Цвет остаётся тем же cyan.ui, НЕ gold —
   gold зарезервирован только под tag--won (design_system_spec.md §2, тот же
   принцип, что уже закомментирован в rfp_detail.html рядом с этой кнопкой).
   Размер (14.5px/600) сознательно НЕ 14px/500 из design_system_spec.md §3
   ("Button label ... 14/20 ... All buttons") — тот пункт спеки унифицирует
   ТЕКСТ кнопок, но не описывает сценарий "явно более заметная кнопка", а
   именно это явно просил тикет 42 ("крупнее/заметнее обычных .small") —
   живой репорт Дениса важнее унификации типографики прототипной спеки,
   отклонение минимальное (+0.5px/+100 веса), не ломает сетку. */
button.small.btn-cta, .btn-cta {
    /* Специфичность важна: голое .btn-cta (0,1,0) слабее, чем "button.small"
       из правила выше (0,1,1) — без явного "button.small.btn-cta" (0,2,1)
       переопределение padding/font-size тихо проигрывало бы каскаду. */
    padding: 11px 22px; font-weight: 600; font-size: 14.5px;
    box-shadow: var(--surface-elevated-shadow);
}

/* ---------------- status tag (per 01_product/ui_design/design_system_spec.md §6) ----------------
   Rectangular, 3px left color bar, icon/dot + text. Tint = in-motion, solid = final.
   Gold (--gold-accent) is reserved for tag--won ONLY — do not reuse it elsewhere (spec §2, hard rule). */
.tag {
    display: inline-flex; align-items: center; gap: 6px;
    font-size: 12px; font-weight: 500; padding: 4px 9px 4px 8px;
    border-radius: var(--r-tag); border-left: 3px solid; white-space: nowrap;
}
.tag-dot { width: 6px; height: 6px; border-radius: 50%; flex: 0 0 auto; }
.tag--high { color: var(--ok-t); background: var(--ok-b); border-color: var(--ok-d); }
.tag--high .tag-dot { background: var(--ok-d); }
.tag--low { color: var(--bad-t); background: var(--bad-b); border-color: var(--bad-d); }
.tag--low .tag-dot { background: var(--bad-d); }
.tag--ochre { color: var(--warn-t); background: var(--warn-b); border-color: var(--warn-d); }
.tag--ochre .tag-dot { background: var(--warn-d); }
.tag--active { color: var(--cyan-deep); background: var(--cyan-tint); border-color: var(--cyan); }
.tag--active .tag-dot { background: var(--cyan); }
.tag--off { color: var(--off-t); background: var(--off-b); border-color: var(--off-d); }
.tag--off .tag-dot { background: var(--off-d); }
.tag--solid { color: #fff; border-left-color: rgba(255,255,255,.35); }
.tag--submitted { background: var(--cyan); }
.tag--won { background: var(--gold); }
.tag--lost { background: #45566B; }

/* ---------------- tabs (Workspace: Overview/Score/Documents/Experts/Activity) ---------------- */
.tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--border); margin: 20px 0; }
.tab-btn {
    border: 0; background: none; padding: 10px 4px; margin-right: 24px;
    font-size: 13px; font-weight: 500; color: var(--text-faint); cursor: pointer;
    border-bottom: 2px solid transparent; position: relative; top: 1px;
}
.tab-btn.active { color: var(--cyan-deep); border-bottom-color: var(--cyan); }
.tabpane { display: none; }
.tabpane.active { display: block; }

/* ---------------- kanban (Pipeline) ---------------- */
/* Тикет 96 — было repeat(5, ...), PIPELINE_COLUMNS (app.py) теперь 6 бакетов
   (Lost/Declined разделены) — 6-я колонка иначе попадала бы в implicit track
   с дефолтным размером, не minmax(200px, 1fr), как остальные.

   Тикет 191 — живой репорт Дениса: 6×200px+5×16px=1280px обязательного
   минимума не помещалось НИ ПРИ КАКОМ размере окна в старый .canvas (max-
   width:1200px, доступно после сайдбара 240px + паддинга 64px ещё меньше на
   типичном 13-14" ноутбуке) — колонка Won (крайняя справа, намеренно по
   тикету 144) пропадала первой, БЕЗ единого визуального намёка, что можно
   проскроллить (overflow-x:auto технически работал, но и только). Два
   изменения вместе: (а) 200px→170px — сузили карточки, но не до
   нечитаемости (см. .kcard ниже, всё ещё умещает заголовок+тег+meta),
   1280px минимума стало 1090px; (б) .canvas--wide (см. выше) даёт странице
   дашборда 1400px вместо 1200px — на большинстве современных 13-14"
   экранов (эффективный CSS-viewport 1440px+) этого достаточно, чтобы вся
   доска уместилась без скролла вообще. На более узких окнах скролл
   остаётся, но теперь ВИДИМЫЙ (постоянный неавтоскрывающийся скроллбар
   ниже) — Won всё ещё доступен, просто явно скроллом, не молча обрезан. */
.kanban {
    display: grid; grid-template-columns: repeat(6, minmax(170px, 1fr)); gap: 14px;
    overflow-x: auto; padding-bottom: 10px;
    /* Firefox — тонкий, но постоянно видимый скроллбар (не auto-hide по
       бездействию, как системный дефолт на macOS/большинстве тачпадов —
       ровно то отсутствие подсказки, на которое пожаловался Денис). */
    scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent;
}
/* Chromium/WebKit — тот же принцип, отдельный API. */
.kanban::-webkit-scrollbar { height: 8px; }
.kanban::-webkit-scrollbar-track { background: transparent; }
.kanban::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 4px; }
.kanban::-webkit-scrollbar-thumb:hover { background: var(--text-faint); }
/* Тикет 191 (регрессия 12.08.2026, повторный живой репорт Дениса) — постоянно
   видимый скроллбар выше технически применяется, но на macOS "показывать
   скроллбары: при скролле" (системный дефолт большинства Mac, а тикет прямо
   называет "13-14 ноутбук" — с высокой вероятностью MacBook) ОС РИСУЕТ
   overlay-скроллбар только во время активного скролла, игнорируя page-level
   scrollbar-width/color для видимости в состоянии покоя — на практике для
   такого юзера подсказки не видно вообще, ровно как до первой версии фикса.
   Второй, ОС-независимый слой: чистый CSS градиент у правого края обёртки
   (не самого .kanban — тот скроллится, обёртка нет, поэтому градиент остаётся
   на месте при любой прокрутке), не зависит от того, рисует ли конкретная
   ОС/браузер сам скроллбар видимым в состоянии покоя. */
.kanban-scroll-wrap { position: relative; }
.kanban-scroll-wrap::after {
    content: ""; position: absolute; top: 0; bottom: 10px; right: 0; width: 32px;
    background: linear-gradient(to right, transparent, var(--bg));
    pointer-events: none;
}
.kcol-head {
    font-family: "JetBrains Mono", ui-monospace, monospace; font-size: 11px; text-transform: uppercase;
    letter-spacing: .04em; color: var(--text-muted); margin-bottom: 10px;
    display: flex; justify-content: space-between;
}
.kcard {
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-surface);
    box-shadow: var(--surface-elevated-shadow);
    padding: 12px; margin-bottom: 10px; cursor: pointer; display: block; text-decoration: none; color: inherit;
}
/* Тикет 199 — переход с плашки воронки (#kanban-{key}) к своей колонке.
   :target остаётся активным сколько угодно (пока хэш в URL), а не только
   "на секунду" — glow нарочно анимирован с fade-out на фиксированной
   длительности через animation, не через transition на исчезающее :target
   (у :target нет промежуточного состояния для transition при снятии). */
.kanban > [id^="kanban-"]:target .kcol-head {
    animation: kanbanTargetGlow 1.6s ease-out;
    border-radius: var(--r-control);
}
@keyframes kanbanTargetGlow {
    0% { background: var(--cyan-tint); box-shadow: 0 0 0 2px var(--cyan); }
    100% { background: transparent; box-shadow: 0 0 0 2px transparent; }
}
/* Тикет 204 — переход от «Соответствие команды» к конкретной карточке позиции
   на вкладке Experts; тот же приём/keyframe, что тикет 199 (не transition —
   :target не имеет промежуточного состояния при снятии). */
.position-card:target {
    animation: kanbanTargetGlow 1.6s ease-out;
    border-radius: var(--r-surface);
}
.kcard:hover { border-color: var(--border-strong); }
.kcard .t { font-weight: 500; font-size: 13px; margin-bottom: 6px; }
.kcard .meta { font-size: 11px; color: var(--text-faint); }

/* ---------------- match-score meter (per spec §6 — rectangular, never a ring) ---------------- */
.meter-wrap { display: flex; align-items: center; gap: 10px; }
.meter { position: relative; flex: 1 1 auto; height: 8px; background: var(--border); border-radius: 2px; min-width: 64px; overflow: hidden; }
.meter-fill { position: absolute; inset: 0 auto 0 0; border-radius: 2px; }
.meter-fill.high { background: var(--ok-d); }
.meter-fill.mid { background: var(--warn-d); }
.meter-fill.low { background: var(--bad-d); }
.meter-num { font-family: "JetBrains Mono", ui-monospace, monospace; font-weight: 700; font-size: 13px; white-space: nowrap; }
.meter-num.high { color: var(--ok-t); } .meter-num.mid { color: var(--warn-t); } .meter-num.low { color: var(--bad-t); }
.meter-qual { font-size: 11px; color: var(--text-faint); white-space: nowrap; }
.meter-lg .meter { height: 14px; }
.meter-lg .meter-num { font-size: 20px; }

/* ---------------- Тикет 189 — match-score КОЛЬЦО (реверс Sev.1-решения,
   design_system_spec.md §0, 12.08.2026, явное решение Дениса). Отдельные
   классы от .meter- и .meter-fill выше — те намеренно НЕ трогаются здесь,
   потому что #scoreAllProgressFill (прогресс-бар батч-оценки экспертов,
   rfp_detail.html) переиспользует именно .meter/.meter-fill по совпадению
   имени класса, а семантически это % выполнения фоновой задачи, не
   match-score конкретного тендера — реверс его не касается (см. тикет).
   mask-based donut (не border-radius+overflow с "залатанной" серединой),
   чтобы прозрачная дырка кольца корректно ложилась на ЛЮБОЙ фон карточки
   (белая .acc-card, табличная строка, тёмная kanban-плитка) без шва —
   заливка-по-цвету потребовала бы отдельного punch-цвета на каждый фон. */
.score-ring-wrap {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    --ring-size: 40px;
    --ring-thick: 5px;
    width: var(--ring-size);
    height: var(--ring-size);
}
.score-ring-wrap.lg { --ring-size: 84px; --ring-thick: 8px; }
.score-ring-wrap.sm { --ring-size: 30px; --ring-thick: 4px; }
.score-ring {
    position: absolute; inset: 0;
    border-radius: 50%;
    background: conic-gradient(var(--ring-color, var(--border)) calc(var(--pct, 0) * 1%), var(--border) 0);
    -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - var(--ring-thick) - 1px), #000 calc(100% - var(--ring-thick)));
    mask: radial-gradient(farthest-side, transparent calc(100% - var(--ring-thick) - 1px), #000 calc(100% - var(--ring-thick)));
}
.score-ring.high { --ring-color: var(--ok-d); }
.score-ring.mid { --ring-color: var(--warn-d); }
.score-ring.low { --ring-color: var(--bad-d); }
.score-ring-num { position: relative; font-family: "JetBrains Mono", ui-monospace, monospace; font-weight: 700; font-size: 11px; white-space: nowrap; }
.score-ring-num.high { color: var(--ok-t); } .score-ring-num.mid { color: var(--warn-t); } .score-ring-num.low { color: var(--bad-t); }
.score-ring-wrap.lg .score-ring-num { font-size: 21px; }
.score-ring-wrap.sm .score-ring-num { font-size: 9px; }

/* Тикет 189 п.5 — чек-лист требуемых документов на kanban-карточке
   («В работе»/Preparing) — тонкая горизонтальная полоска на документ,
   переиспользует те же семантические токены (--ok-d готов / --warn-d нет),
   не отдельная палитра. */
.doc-chip-row { display: flex; gap: 3px; margin-top: 6px; }
.doc-chip { flex: 1 1 auto; height: 4px; border-radius: 2px; background: var(--warn-d); min-width: 6px; }
.doc-chip.ready { background: var(--ok-d); }
.doc-chip-count { font-size: 10px; color: var(--text-faint); font-family: "JetBrains Mono", ui-monospace, monospace; margin-top: 3px; }
/* Тикет 206 — третье состояние только для карточки «Готовность тендера»:
   документ ещё не начат (pending, серый) отличается от "черновик есть, не
   approved" (draft — просто НЕ переопределяет базовый .doc-chip выше, тот
   и так уже --warn-d/оранжевый, та же семантика, что у kanban-чипа). Не
   трогает базовый .doc-chip/.ready — kanban-чек-лист тикета 189 не задет. */
.doc-chip.pending { background: var(--border); }

/* ---------------- Тикет 206 — карточка «Готовность тендера» (шапка Overview) ---------------- */
.readiness-card {
    /* Тикет 206 — раньше отдельная соседняя .acc-card (flex: 0 0 300px) в шапке,
       теперь блок внутри той же карточки, что status-текст "что делать
       дальше" (Денис, 12.08.2026) — сама больше не flex-item, флекс-размер не
       нужен. */
    display: flex; flex-direction: column; align-items: center; text-align: center; gap: 4px;
    max-width: 300px;
}
.readiness-label {
    font-family: "JetBrains Mono", ui-monospace, monospace; font-size: 9.5px; text-transform: uppercase;
    letter-spacing: .05em; color: var(--text-faint); align-self: flex-start;
}
.subscore-list { width: 100%; display: flex; flex-direction: column; gap: 9px; padding-top: 12px; margin-top: 4px; border-top: 1px solid var(--border); text-align: left; }
.subscore-row { display: flex; flex-direction: column; gap: 3px; }
.subscore-head { display: flex; justify-content: space-between; align-items: baseline; font-size: 11.5px; color: var(--text-muted); }
.subscore-num { font-family: "JetBrains Mono", ui-monospace, monospace; font-weight: 700; font-size: 11.5px; }
.subscore-num.high { color: var(--ok-t); } .subscore-num.mid { color: var(--warn-t); } .subscore-num.low { color: var(--bad-t); }
.subscore-num.empty { color: var(--text-faint); font-weight: 400; }
/* Та же ВИЗУАЛЬНАЯ логика (тонкая полоса, пороги 80/50, семантические цвета),
   что .meter/.meter-fill (§6) — но НЕ те же классы: .meter/.meter-fill уже
   переиспользованы #scoreAllProgressFill под другую метрику (прогресс batch-
   оценки экспертов, см. комментарий тикета 189 выше по файлу), третий смысл
   на то же имя класса не вешаем. */
.subscore-bar { position: relative; height: 6px; background: var(--border); border-radius: 3px; overflow: hidden; }
.subscore-fill { position: absolute; inset: 0 auto 0 0; border-radius: 3px; }
.subscore-fill.high { background: var(--ok-d); }
.subscore-fill.mid { background: var(--warn-d); }
.subscore-fill.low { background: var(--bad-d); }
.subscore-bar.empty { background: repeating-linear-gradient(135deg, var(--border) 0 4px, transparent 4px 8px); }
.doc-chip-section { width: 100%; margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border); text-align: left; }

/* ---------------- list-row (Тендеры) — score/статус вынесены из тесной угловой
   колонки над кнопкой (Денис, 05.08.2026: "не должен прогресс идти в этом углу").
   Метрика — в одной строке с заголовком (вертикально по центру, не стек), действие/
   статус — отдельной footer-строкой с разделителем, тот же паттерн card-link-row,
   что уже используется в project_dashboard.html. */
.row-head { display: flex; justify-content: space-between; align-items: center; gap: 16px; }
.row-head-score { flex: 0 0 auto; min-width: 130px; }
.row-foot {
    margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border);
    display: flex; align-items: center; justify-content: flex-end; gap: 10px;
}

/* ---------------- approval chain (модуль 03 — Approvals) ----------------
   Author -> На проверке -> Approver, см. approvals/routes.py + templates/approvals.html.
   Реиспользует существующие токены (--cyan, --ok-*, --border), не вводит новых цветов. */
.chain { display: flex; align-items: flex-start; gap: 0; margin: 14px 0; }
.cstep { display: flex; flex-direction: column; align-items: center; gap: 6px; min-width: 96px; text-align: center; }
.cstep .av {
    width: 32px; height: 32px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
    font-family: "Sora", "Inter", "Segoe UI", sans-serif; font-weight: 700; font-size: 12px;
    background: var(--off-b); color: var(--off-t); border: 2px solid var(--border-strong);
}
.cstep.done .av { background: var(--ok-b); color: var(--ok-t); border-color: var(--ok-d); }
.cstep.current .av { background: var(--cyan-tint); color: var(--cyan-deep); border-color: var(--cyan); }
.cstep .role { font-family: "JetBrains Mono", ui-monospace, monospace; font-size: 10px; text-transform: uppercase; letter-spacing: .04em; color: var(--text-faint); }
.cstep .nm { font-size: 12px; font-weight: 500; }
.cline { flex: 1 1 auto; height: 2px; background: var(--border); margin-top: 15px; min-width: 24px; }
.cline.done { background: var(--ok-d); }
.wait-badge {
    font-size: 10px; font-family: "JetBrains Mono", ui-monospace, monospace; color: var(--warn-t);
    background: var(--warn-b); border-radius: var(--r-pill); padding: 2px 8px; margin-top: 2px;
}
.btn-danger { background: var(--bad-d); color: #fff; }
.btn-danger:hover { background: var(--bad-t); }
/* Тикет 52 — тот же гочтя специфичности, что уже решён для .btn-cta выше
   (0,1,1) "button.small" бьёт голое (0,1,0) .btn-danger, cyan-заливка тихо
   побеждала — найдено живой проверкой (getComputedStyle), не по чтению кода.
   button.small.btn-danger (0,2,1) переопределяет только цвет, не размер. */
button.small.btn-danger { background: var(--bad-d); color: #fff; }
button.small.btn-danger:hover { background: var(--bad-t); }
.btn-outline { background: var(--surface); color: var(--text); border: 1px solid var(--border-strong); }
.btn-outline:hover { border-color: var(--text-faint); }

/* Тикет 75 — disabled-кнопки визуально неотличимы от активных: во всём этом
   файле не было НИ ОДНОГО правила на :disabled/[disabled] до этой строки
   (подтверждено grep'ом перед постановкой задачи). JS уже реально дизейблит
   ~9+ кнопок (analyzeBtn/bid-btn/reparseBtn и т.д., паттерн btn.disabled =
   true) — но браузер показывал только едва заметный нативный дефолт. Одно
   общее правило на уровне базовых классов кнопок (не по кнопке отдельно) —
   покрывает все существующие и будущие disabled-кнопки проекта разом.
   :disabled:hover переопределяется отдельно на каждый цветовой вариант —
   тот же гочтя специфичности, что уже решён для .btn-cta/.btn-danger выше
   (button.small.btn-X (0,2,1) бьёт голое button.small (0,1,1)), иначе
   потускневшая disabled-кнопка при наведении мышью снова "оживала" бы
   обратно в активный cyan/danger цвет. */
button:disabled, .btn:disabled, [disabled].small, [disabled].btn {
    opacity: .55;
    cursor: not-allowed;
}
button.small:disabled:hover, .btn:disabled:hover { background: var(--cyan); }
button.small.btn-cta:disabled:hover, .btn-cta:disabled:hover { background: var(--cyan); }
button.small.btn-danger:disabled:hover, .btn-danger:disabled:hover { background: var(--bad-d); }
.btn-outline:disabled:hover { border-color: var(--border-strong); }

/* ---------------- модуль 04 — Drawer quick-view + Generator Wizard ----------------
   Портировано из 01_product/ui_design/platform_prototype.html (.drawer/#genOverlay),
   упрощено под реальные токены/переменные этого приложения — новых цветов не вводит. */
.backdrop {
    display: none; position: fixed; inset: 0; background: rgba(10, 18, 32, .35); z-index: 40;
}
.backdrop.open { display: block; }
.drawer {
    position: fixed; top: 0; right: 0; bottom: 0; width: 480px; max-width: 92vw;
    background: var(--surface); box-shadow: -8px 0 24px rgba(10, 18, 32, .18); z-index: 41;
    transform: translateX(100%); transition: transform .2s ease; display: flex; flex-direction: column;
}
.drawer.open { transform: translateX(0); }
.drawer-head { padding: 20px; border-bottom: 1px solid var(--border); flex: 0 0 auto; }
.drawer-head-top { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 12px; }
.drawer-body { flex: 1 1 auto; overflow-y: auto; padding: 20px; display: flex; flex-direction: column; gap: 20px; }
.drawer-section h3 { font-size: 13px; font-weight: 700; margin: 0 0 10px; }
.close-x {
    background: none; border: none; font-size: 16px; line-height: 1; cursor: pointer; color: var(--text-faint);
    padding: 4px;
}
.close-x:hover { color: var(--text); }
.link-ghost { color: var(--cyan-deep); font-size: 12.5px; text-decoration: none; }
.link-ghost:hover { text-decoration: underline; }
/* Тикет 165 — имя эксперта в Talent Pool как триггер разворота карточки (не текст). */
.expert-name-toggle { cursor: pointer; }
.expert-name-toggle:hover { text-decoration: underline; }
.expert-name-toggle .expand-indicator { color: var(--text-faint); font-size: 11px; }
.gap-list { display: flex; flex-direction: column; gap: 8px; }
.gap-item {
    font-size: 12.5px; padding: 8px 10px; background: var(--warn-b); color: var(--warn-t);
    border-left: 3px solid var(--warn-d); border-radius: var(--r-tag);
}
.gap-item p { margin: 0; }

/* wizard */
.wizard-overlay {
    display: none; position: fixed; inset: 5% 8%; background: var(--bg); border-radius: var(--r-surface);
    z-index: 60; overflow-y: auto; box-shadow: 0 30px 60px rgba(0, 0, 0, .35); padding: 28px;
}
.wizard-steps { display: flex; align-items: center; margin-bottom: 22px; }
.wstep { display: flex; flex-direction: column; align-items: center; gap: 6px; min-width: 110px; text-align: center; }
.wstep .n {
    width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
    font-family: "Sora", "Inter", "Segoe UI", sans-serif; font-weight: 700; font-size: 12px;
    background: var(--off-b); color: var(--off-t); border: 2px solid var(--border-strong);
}
.wstep.done .n { background: var(--ok-b); color: var(--ok-t); border-color: var(--ok-d); }
.wstep.current .n { background: var(--cyan-tint); color: var(--cyan-deep); border-color: var(--cyan); }
.wstep .lbl { font-size: 11px; color: var(--text-faint); }
.wline { flex: 1 1 auto; height: 2px; background: var(--border); margin: 0 4px 20px; }
.wline.done { background: var(--ok-d); }
.spend-gate {
    display: flex; align-items: center; gap: 16px; padding: 14px 16px; background: var(--cyan-tint);
    border-radius: var(--r-surface); border: 1px solid var(--border);
}
.spend-gate .cost { font-family: "JetBrains Mono", ui-monospace, monospace; font-weight: 700; font-size: 16px; color: var(--cyan-deep); }
.tray-item {
    display: flex; align-items: center; gap: 10px; padding: 8px 0; border-bottom: 1px solid var(--border);
    font-size: 13px;
}
.req-row { display: flex; align-items: center; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid var(--border); }
.req-row .doc { font-size: 13px; font-weight: 500; }

/* Тикет 18 — публичная /pricing. featured-карточка (Analyst) выделяется цветом
   --cyan (не --gold-accent — тот зарезервирован только под tag--won, см. §2 выше).
   Тикет 232 — лестница 2.0: 4 карточки (€0/€40/€89/€590); на планшете 2x2,
   чтобы Starter €40 и Analyst €89 оставались в одном ряду (якорь, история 13a). */
.pricing-grid {
    display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 16px; margin-top: 20px;
}
.price-amount {
    font-family: "Sora", "Inter", sans-serif; font-weight: 800; font-size: 26px;
    color: var(--text); margin: 6px 0 2px;
}
.price-amount .price-period { font-family: "Inter", sans-serif; font-weight: 400; font-size: 12px; color: var(--text-muted); }
.price-features li.excluded { color: var(--text-faint); }
.price-features li.excluded::before { content: "—"; color: var(--text-faint); font-weight: 400; }
.price-card {
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-surface);
    box-shadow: var(--surface-elevated-shadow);
    padding: 20px; display: flex; flex-direction: column; position: relative;
}
.price-card.featured { border-color: var(--cyan); border-width: 2px; padding-top: 28px; }
.price-card-badge {
    position: absolute; top: -11px; left: 20px; background: var(--cyan); color: #fff;
    font-family: "JetBrains Mono", ui-monospace, monospace; font-size: 10.5px; text-transform: uppercase; letter-spacing: .04em;
    padding: 3px 8px; border-radius: 4px;
}
.price-card h2 { font-size: 18px; margin-top: 4px; }
.price-features { list-style: none; margin: 14px 0 0; padding: 0; flex: 1 1 auto; }
.price-features li {
    font-size: 12.5px; color: var(--text); padding: 5px 0 5px 18px; position: relative;
    border-bottom: 1px solid var(--border);
}
.price-features li:last-child { border-bottom: none; }
.price-features li::before {
    content: "✓"; position: absolute; left: 0; color: var(--cyan); font-weight: 700;
}
@media (max-width: 1100px) {
    /* 2x2: Monitor+Starter / Analyst+Team — €40 и €89 соседствуют по вертикали,
       лимиты первой строкой всё ещё сравниваются одним взглядом. */
    .pricing-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
    .pricing-grid { grid-template-columns: 1fr; }
    .price-card.featured { padding-top: 20px; }
}
/* Тикет 143 — FAQ на /pricing (AEO/GEO: явные вопрос-ответ пары для LLM-поисковиков). */
.pricing-faq { margin-top: 12px; display: flex; flex-direction: column; gap: 8px; }
.pricing-faq details {
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-control);
    padding: 12px 16px;
}
.pricing-faq summary {
    cursor: pointer; font-weight: 500; font-size: 13.5px; color: var(--text);
    list-style: none;
}
.pricing-faq summary::-webkit-details-marker { display: none; }
.pricing-faq summary::before { content: "+"; display: inline-block; width: 14px; color: var(--cyan); font-weight: 700; }
.pricing-faq details[open] summary::before { content: "−"; }
.pricing-faq details p { font-size: 12.5px; margin: 8px 0 2px 22px; }
/* ---------------- Тикет 19 — Dashboard: приоритеты / pipeline snapshot / credits sparkline / лента активности ----------------
   Портировано из 01_product/ui_design/platform_prototype.html #view-dashboard, цвета
   переведены на уже существующие токены этого файла (--ok-*/--warn-*/--bad-*/--cyan-*/--off-*),
   новых цветов не вводит (см. §2 design_system_spec.md — gold зарезервирован под tag--won). */
/* Тикет 58 — 3 колонки → 2: Credits вынесен из dash-grid в отдельный блок внизу
   страницы, осталось 2 карточки. repeat(3, ...) с 2 детьми оставлял бы пустую
   треть ширины на десктопе (третий трек грида просто не заполнен). */
.dash-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
@media (max-width: 1024px) { .dash-grid { grid-template-columns: 1fr; } }

.priority-row {
    display: flex; gap: 10px; align-items: flex-start; padding: 8px 0; border-bottom: 1px solid var(--border);
    text-decoration: none; color: inherit; margin: 0 -6px; padding-left: 6px; padding-right: 6px; border-radius: var(--r-control);
}
.priority-row:last-of-type { border-bottom: none; }
.priority-row:hover { background: var(--bg); }
.priority-row p { margin: 0; font-size: 12.5px; line-height: 1.4; }
.priority-ic {
    width: 20px; height: 20px; border-radius: 50%; flex: 0 0 auto; margin-top: 1px;
    display: flex; align-items: center; justify-content: center; font-size: 10px; font-weight: 700;
}
.priority-ic--ok { background: var(--ok-b); color: var(--ok-t); }
.priority-ic--warn { background: var(--warn-b); color: var(--warn-t); }
.priority-ic--bad { background: var(--bad-b); color: var(--bad-t); }
.priority-ic--active { background: var(--cyan-tint); color: var(--cyan-deep); }
.priority-ic--off { background: var(--off-b); color: var(--off-t); }
.priority-ic--ai { background: var(--off-b); color: var(--cyan-deep); font-size: 8px; } /* AI — переиспользует off+cyan-текст, отдельного ai-токена в app.css нет (не заводил ради одной иконки в тикете 19) */

.pipeline-chips { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 10px; }
/* Тикет 103 — раньше ширина плашки зависела от длины её текста ("PREPARING"
   шире "WON"), с 6 плашками (тикет 96 разделил Lost/Declined) разброс стал
   заметен на скриншоте Дениса. flex: 1 1 90px, не grid — в отличие от
   .kanban ниже (overflow-x: auto, горизонтальный скролл), здесь плашки
   должны именно ОБОРАЧИВАТЬСЯ на узком экране (см. тикет), не скроллиться —
   flex-wrap уже был, только сами чипы не росли равномерно. */
.pchip {
    background: var(--bg); border: 1px solid var(--border); border-radius: var(--r-control);
    padding: 8px 10px; flex: 1 1 90px; text-align: center; text-decoration: none; color: inherit;
    position: relative; overflow: hidden;
}
.pchip:hover { border-color: var(--border-strong); }
.pchip b { display: block; font-family: "Sora", "Inter", "Segoe UI", sans-serif; font-size: 18px; font-weight: 700; }
.pchip span { display: block; font-size: 10px; color: var(--text-faint); text-transform: uppercase; letter-spacing: .03em; font-family: "JetBrains Mono", ui-monospace, monospace; margin-top: 2px; }
/* Тикет 103, живой репорт Дениса #2 — сплошная заливка фона (первый заход,
   см. git history/старый отчёт) читалась как «два разных виджета, сшитых
   вместе». Заменено на вариант B2 из живого мокапа (артефакт
   0029de41-74e5-4fa7-bc7a-b2a73f0dbfb2, раздел «Уточнение варианта B») —
   3px акцент-полоска сверху через ::before, сама плашка остаётся на обычном
   .pchip фоне/рамке. watching/preparing/submitted получают прогрессию
   фирменного градиента cyan→gold («Innovation to Gold» — та же метафора,
   что в логотипе), won/lost/declined — семантические токены (исход сделки,
   не бренд, сознательно другая палитра, см. коммент выше про --off-* для
   declined). CSS-значения скопированы дословно из HTML/CSS мокапа B2, не
   реконструированы по описанию. */
.pchip::before { content: ""; position: absolute; top: 0; left: 0; right: 0; height: 3px; }
.pchip--watching::before { background: var(--cyan); }
.pchip--preparing::before { background: linear-gradient(90deg, var(--cyan), var(--gold)); }
.pchip--submitted::before { background: var(--gold); }
/* Тикет 144 — Won выделен фирменным градиентом лого («The Duality»), не
   семантическим статус-цветом — единственная колонка, где это уместно
   (правило тикета 27: gold зарезервирован строго под Won и больше нигде).
   --cyan-accent/--gold-accent, не --cyan/--gold — та же пара, что и
   декоративный --shell-glow (canonical, см. color_critique.md), не
   AA-затемнённые текстовые токены. */
.pchip--won::before { background: linear-gradient(90deg, var(--cyan-accent), var(--gold-accent)); }
.pchip--lost::before { background: var(--bad-d); }
.pchip--declined::before { background: var(--off-d); }
.pchip--won b { color: var(--ok-t); }
.pchip--lost b { color: var(--bad-t); }
.pchip--declined b { color: var(--off-t); }

/* Тикет 47 — переиспользуемый спиннер (первый в проекте, до этого во всём
   app.css не было ни одного @keyframes/.spinner). Рассчитан на показ рядом
   с текстом статуса (inline-block, маленький), не как fullscreen-оверлей. */
.spinner {
    display: inline-block;
    width: 14px;
    height: 14px;
    vertical-align: middle;
    margin-right: 6px;
    border: 2px solid var(--border-strong);
    border-top-color: var(--cyan);
    border-radius: 50%;
    animation: spin .8s linear infinite;
}
@keyframes spin {
    to { transform: rotate(360deg); }
}

/* Тикет 211 — Talent Pool (#membersList, templates/profile.html): ни
   <table>, ни <td> не задавали vertical-align, дефолт браузера для <td> —
   middle, поэтому короткая однострочная ячейка «Имя» центрировалась по
   высоте соседней ячейки «Специальность», раздутой переносом тегов на 2
   строки (display:flex;flex-wrap:wrap) — визуально «зазоры», имя «плавает»
   не у верха строки. Единственное место в проекте с этим паттерном
   (проверено остальные таблицы templates/ — там теги одной строкой, без
   flex-wrap, риска нет). */
#membersList td { vertical-align: top; }
@media (prefers-reduced-motion: reduce) {
    .spinner { animation: none; }
}

/* Тикет 76 — полноэкранный блокирующий оверлей на любой долгой AI-операции
   (жалоба Дениса: "8 раз нажал на кнопку, прежде чем понял, что он
   работает" — статус-текст рядом с кнопкой и .spinner тикета 47 недостаточно
   заметны, особенно если кнопка не в зоне видимости при клике, как
   wizGenerateBtn). position:fixed + z-index достаточно, чтобы блокировать
   клики по контенту под оверлеем — оверлей физически перехватывает hit-test
   в своих границах, отдельный pointer-events не нужен. */
.blocking-overlay {
    position: fixed; inset: 0; z-index: 500;
    background: rgba(10, 18, 32, .55);
    display: flex; align-items: center; justify-content: center;
}
.blocking-overlay-box {
    background: var(--surface); border-radius: var(--r-control); padding: 24px 32px;
    display: flex; flex-direction: column; align-items: center; gap: 14px;
    box-shadow: var(--surface-elevated-shadow); max-width: 360px;
}
.blocking-overlay .spinner-lg {
    width: 30px; height: 30px; border-width: 3px; margin-right: 0;
}
.blocking-overlay-text { font-size: 13px; color: var(--text); text-align: center; }

/* Тикет 185 — плавающая кнопка "Сообщить о проблеме" + модалка формы.
   z-index ниже blocking-overlay (500) — если долгая AI-операция уже идёт,
   оверлей закономерно должен быть поверх, юзер не должен открыть форму
   репорта посреди заблокированного экрана и запутаться, что реально активно. */
.bug-report-fab {
    position: fixed; right: 20px; bottom: 20px; z-index: 400;
    width: 46px; height: 46px; border-radius: 50%;
    background: var(--surface); border: 1px solid var(--border-strong);
    box-shadow: var(--surface-elevated-shadow); cursor: pointer; font-size: 20px;
    display: flex; align-items: center; justify-content: center;
}
.bug-report-fab:hover { background: var(--surface-hover); }
.bug-report-overlay {
    position: fixed; inset: 0; z-index: 410;
    background: rgba(10, 18, 32, .45);
    align-items: center; justify-content: center;
}
.bug-report-modal {
    background: var(--surface); border-radius: var(--r-surface); padding: 18px;
    width: 420px; max-width: calc(100vw - 32px); box-shadow: var(--surface-elevated-shadow);
}
.bug-report-modal-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
@media (max-width: 480px) {
    .bug-report-fab { right: 12px; bottom: 12px; }
}

/* Тикет 58 — бейдж "N новых" на карточке "Воронка тендеров". Переиспользует
   --cyan-tint/--cyan-deep/--cyan (те же токены, что .tag--active/шапка
   тикета 53) — НЕ gold, зарезервирован строго под статус Won. Pulse — тонкое
   мигание непрозрачности, не яркая вспышка; играет всегда, пока n>0 (см.
   тикет — "разумный дефолт", не "мигнуть один раз при появлении"). */
.new-badge {
    display: inline-flex; align-items: center;
    background: var(--cyan-tint); color: var(--cyan-deep); border: 1px solid var(--cyan);
    border-radius: var(--r-control); padding: 3px 10px; font-size: 11px; font-weight: 600;
    text-decoration: none; animation: badge-pulse 2s ease-in-out infinite;
}
.new-badge:hover { border-color: var(--border-strong); }
@keyframes badge-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: .55; }
}
@media (prefers-reduced-motion: reduce) {
    .new-badge { animation: none; }
}

/* Тикет 146 — корневая причина, общая для rfp_detail/rfps_list/profile/
   settings/approvals: сам app-shell (.app/.sidebar/.topbar) не адаптировался
   под узкий экран вообще — фикс сюда закрывает все 5 экранов разом,
   отдельные точечные правки ниже — только для того, что ломается сверх
   этого (длинные однострочные .tag/полоса вкладок). Найдено и подтверждено
   живьём (document.documentElement.clientWidth=375, bodyScrollWidth=1586 на
   /rfps/3 до фикса — 240px несжимаемый сайдбар + переполненный topbar).
   Блок нарочно в самом конце файла: при равной специфичности CSS-каскад
   решает по порядку в файле, а не по тому, активен ли @media — если бы
   этот блок стоял РАНЬШЕ базового .tag{white-space:nowrap} (~строка 478),
   тот безусловный более поздний по файлу правил бы даже при активном
   медиа-условии (реально пойманная живьём ошибка при первой версии этого
   фикса — .app/.sidebar/.canvas применялись верно, .tag — нет, ровно
   потому что там не было более позднего конфликтующего правила). */
@media (max-width: 767px) {
    .app { flex-direction: column; }
    .sidebar { width: 100%; height: auto; flex-direction: row; border-right: none; border-bottom: 1px solid var(--shell-border); }
    .sidebar-brand { flex: 0 0 auto; border-bottom: none; border-right: 1px solid var(--shell-border); }
    /* Тикет 191 DoD п.4 — тот же класс проблемы, что у .kanban (overflow-x:
       auto без видимого сигнала, что скроллить есть куда), проверено: да,
       страдает и здесь на узком экране. Тот же фикс — постоянно видимый
       скроллбар, не auto-hide. */
    .nav { display: flex; align-items: center; padding: 0 6px; overflow-x: auto; scrollbar-width: thin; scrollbar-color: var(--shell-text-2) transparent; }
    .nav::-webkit-scrollbar { height: 6px; }
    .nav::-webkit-scrollbar-track { background: transparent; }
    .nav::-webkit-scrollbar-thumb { background: var(--shell-text-2); border-radius: 3px; }
    .nav-group-label { display: none; } /* вертикальные группы-подписи не нужны в горизонтальной полосе */
    .nav-item { flex: 0 0 auto; width: auto; white-space: nowrap; margin-bottom: 0; }
    .nav-item.active { border-left: none; border-bottom: 2px solid var(--shell-glow); padding-left: 12px; }
    .topbar { height: auto; flex-wrap: wrap; padding: 8px 12px; gap: 8px 12px; }
    .canvas { padding: 12px; }
    /* .tag — глобально white-space:nowrap (design_system_spec.md §6), но
       свободный текст в мета-чипах тендера ("формат: ...", "дедлайн: ...")
       может быть длиннее экрана целиком — на мобильном ширина важнее
       однострочности, короткие статус-пилюли (Available/Won/...) от этого
       визуально не страдают (и так помещаются в одну строку). */
    .tag { white-space: normal; }
    .tabs { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent; }
    .tabs::-webkit-scrollbar { height: 6px; }
    .tabs::-webkit-scrollbar-track { background: transparent; }
    .tabs::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 3px; }
    .tab-btn { flex: 0 0 auto; white-space: nowrap; }
}
