body {
    margin: 0;
    background: black;
    overflow: hidden;
    font-family: Arial, sans-serif;
}

/* =========================
   CAMERA
========================= */
video {
    position: fixed;
    inset: 0;

    width: 100vw;
    height: 100vh;

    object-fit: cover;
    z-index: 1;
}

/* Оверлей заморозки — рисуем сюда текущий кадр и показываем ВМЕСТО живого
   видео, пока идёт отправка. Не трогаем play/pause самого <video> — это
   раньше приводило к зависанию на повторной попытке (см. правку 2026-07-11):
   на некоторых устройствах video.pause() на потоке с камеры мешает
   ImageCapture.grabFrame()/takePhoto() читать следующий кадр из того же
   трека. Реальный поток продолжает жить своей жизнью под этим канвасом. */
.freeze-canvas {
    position: fixed;
    inset: 0;

    width: 100vw;
    height: 100vh;

    object-fit: cover;
    z-index: 2;

    display: none;
}

/* 2026-07-16: было — крупный текстовый блок с числами (blur/яркость/блик/
   углы/MRZ/состояние) для отладки порогов на реальных тестах. Оставили
   только визуальную суть, для гостя, не для разработчика — три маленьких
   кружка (резкость/яркость/блик, слева направо), зелёный = проходит порог,
   красный = нет. Вся логика/пороги на бэкенде не изменились вообще —
   поменялось только то, как это показано на экране. */
.quality-dots {
    position: fixed;
    top: max(10px, env(safe-area-inset-top));
    right: max(10px, env(safe-area-inset-right));
    z-index: 10;

    display: flex;
    gap: 7px;
    padding: 6px 10px;
    border-radius: 20px;
    background: rgba(0, 0, 0, 0.55);
}

.quality-dot {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.35); /* пока нет ни одного измерения */
    transition: background-color 0.2s ease;
}
.quality-dot.dot-ok {
    background: #0f8;
}
.quality-dot.dot-bad {
    background: #ff3b30;
}

/* 2026-07-28: geometry debug-панель — тот же дух, что и убранный в
   2026-07-16 текстовый блок выше (числа для разработчика, не для гостя),
   но: (1) только про ГЕОМЕТРИЮ (края/покрытие/наклон/перспектива/MRZ) —
   .quality-dots про блюр/яркость/блик уже есть отдельно; (2) показывается
   ТОЛЬКО в TEST_MODE (см. renderDebugPanel в script.js) — обычный гость
   её никогда не увидит, это чисто инструмент калибровки при пристрелке
   новых профилей документов (изначально понадобилась для Kimlik). */
.debug-panel {
    position: fixed;
    top: max(10px, env(safe-area-inset-top));
    left: max(10px, env(safe-area-inset-left));
    z-index: 10;

    max-width: 200px;

    color: #0f8;
    font: 9px/1.4 ui-monospace, "SF Mono", Menlo, Consolas, monospace;
    text-shadow: 0 0 3px rgba(0,0,0,0.9), 0 0 3px rgba(0,0,0,0.9);
    white-space: pre;
    pointer-events: none;
}
.debug-panel[hidden] {
    display: none;
}

/* =========================
   DARK OVERLAY
========================= */
.overlay {
    position: fixed;
    inset: 0;
    z-index: 2;

    pointer-events: none;
}

/* =========================
   ROTATING SCANNER UI
========================= */
.scanner-shell[hidden] {
    display: none;
}

.scanner-shell {
    position: fixed;
    inset: 0;

    display: flex;
    align-items: center;
    justify-content: center;

    padding: 1dvh 1dvw;
    padding-top: max(1dvh, env(safe-area-inset-top));
    padding-right: max(1dvw, env(safe-area-inset-right));
    padding-bottom: max(1dvh, env(safe-area-inset-bottom));
    padding-left: max(1dvw, env(safe-area-inset-left));
    box-sizing: border-box;

    z-index: 3;
    transform-origin: center;
}

.scanner-layout {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: clamp(10px, 2.4dvw, 24px);
}

.frame-wrap {
    display: flex;
    align-items: center;
    justify-content: center;
}

/* =========================
   SCANNER FRAME (FIT VIEWPORT)

   2026-07-27: раньше aspect-ratio/ширина были жёстко зашиты под паспорт
   (1.396, формула ниже). При добавлении Kimlik (другие пропорции карты —
   ISO/IEC 7810 ID-1, 85.6×53.98мм ≈ 1.586 — и рамка меньше, чтобы снимать
   с той же дистанции) стало ясно, что сама ФОРМУЛА одна и та же для
   любого документа, отличаются только два числа: пропорция (--doc-ratio)
   и масштаб ширины (--doc-width-scale, 1 для паспорта, меньше для карты).
   Задаются классом .doc-{docType} на .camera-frame (см. setActiveDocumentProfile
   в script.js) — сама формула ниже написана один раз через переменные,
   а не продублирована под каждый тип документа отдельно.
========================= */
.camera-frame {
    --doc-ratio: 1.396;       /* паспорт (TD3, разворот с фото) — дефолт */
    --doc-width-scale: 1;     /* паспорт — 100% базовой ширины */

    position: relative;

    width: min(
        calc(86dvw * var(--doc-width-scale)),
        calc(1180px * var(--doc-width-scale)),
        calc(92dvh * var(--doc-ratio) * var(--doc-width-scale))
    );
    height: auto;

    aspect-ratio: var(--doc-ratio);

    display: flex;
    align-items: center;
    justify-content: center;

    transform-origin: center;
    z-index: 1;
}

/* Kimlik (TD1, ID-1 карта) — на 30% уже паспорта по ширине (по просьбе:
   чтобы снимать примерно с той же дистанции, что и паспорт — карта
   физически меньше самого паспортного разворота), высота уже сама
   следует из aspect-ratio под пропорции карты, а не паспорта. */
.camera-frame.doc-id_card {
    --doc-ratio: 1.586;
    --doc-width-scale: 0.7;
}

@media (orientation: portrait) {
    .scanner-shell {
        inset: auto;
        top: 50%;
        left: 50%;
        width: 100dvh;
        height: 100dvw;
        transform: translate(-50%, -50%) rotate(90deg);
    }

    .scanner-layout {
        gap: clamp(8px, 2.4dvh, 18px);
    }

    .camera-frame {
        width: min(
            calc(88dvh * var(--doc-width-scale)),
            calc(84dvw * var(--doc-ratio) * var(--doc-width-scale))
        );
    }
}

/* =========================
   PASSPORT FRAME

   2026-07-13: раньше рамка была ВСЕГДА зелёной — гость не понимал, что
   зелёный тут ничего не значит (документ мог быть криво/размыто, а рамка
   всё равно "радостно" зелёная). Теперь 3 состояния, задаются классами
   на .passport-frame из script.js (analyzeDocumentAlignment +
   analyzeFrameQuality → evaluateFrameState):
   - по умолчанию (класс не добавлен) — "ищем документ", нейтральный цвет;
   - .state-aligned — документ найден и стоит ровно в рамке, но кадр ещё
     не готов (смазано/темно/блик) — жёлтый/янтарный;
   - .state-ready — всё в порядке, можно снимать — зелёный (старый цвет).
========================= */
:root {
    --frame-color-searching: rgba(255, 255, 255, 0.55);
    --frame-color-aligned: rgba(255, 179, 0, 0.9);
    --frame-color-ready: rgba(0, 255, 120, 0.85);
}

.passport-frame {
    position: relative;

    width: 100%;
    height: 100%;

    border: 2px solid var(--frame-color-searching);
    box-sizing: border-box;

    box-shadow:
        0 0 0 9999px rgba(0,0,0,0.8),
        0 0 0 1px rgba(255,255,255,0.08),
        0 0 20px rgba(255,255,255,0.08);

    border-radius: 6px;

    transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.passport-frame.state-aligned {
    border-color: var(--frame-color-aligned);
    box-shadow:
        0 0 0 9999px rgba(0,0,0,0.8),
        0 0 0 1px rgba(255,179,0,0.12),
        0 0 25px rgba(255,179,0,0.18);
}

.passport-frame.state-ready {
    border-color: var(--frame-color-ready);
    box-shadow:
        0 0 0 9999px rgba(0,0,0,0.8),
        0 0 0 1px rgba(0,255,120,0.10),
        0 0 25px rgba(0,255,120,0.15);
}

/* =========================
   GRID
========================= */
.passport-frame::before {
    content: "";
    position: absolute;
    inset: 0;

    background:
        linear-gradient(to right, rgba(255,255,255,0.03) 1px, transparent 1px),
        linear-gradient(to bottom, rgba(255,255,255,0.03) 1px, transparent 1px);

    background-size: 24px 24px;

    pointer-events: none;
}

/* =========================
   CORNERS
========================= */
.corner {
    position: absolute;
    width: 44px;
    height: 44px;

    border: 4px solid var(--frame-color-searching);
    filter: drop-shadow(0 0 8px rgba(255,255,255,0.5));

    transition: border-color 0.2s ease, filter 0.2s ease;
}

.state-aligned .corner {
    border-color: var(--frame-color-aligned);
    filter: drop-shadow(0 0 10px rgba(255,179,0,0.85));
}

.state-ready .corner {
    border-color: var(--frame-color-ready);
    filter: drop-shadow(0 0 10px rgba(0,255,122,0.9));
}

/* 2026-07-28: живой четырёхугольник по 4 найденным точкам — тот же
   принцип цвета state, что у .corner выше (нейтральный/жёлтый/зелёный).
   Пустой polygon points="" рисует ничего — когда cornersValid=false, JS
   просто не обновляет points, старая (или пустая) форма и остаётся, но
   толку от неё не видно без реальных координат. */
.corner-quad {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;

    color: var(--frame-color-searching);
    pointer-events: none;
    z-index: 2;

    transition: color 0.2s ease;
}

.state-aligned .corner-quad {
    color: var(--frame-color-aligned);
}

.state-ready .corner-quad {
    color: var(--frame-color-ready);
}

/* =========================
   KEYSTONE (перспективное искажение / трапеция) — 2026-07-13
   Не просто текст "выровняйте верхние углы", а явная подсветка ИМЕННО
   той пары углов рамки, где документ не дотягивает до края (см.
   keystoneSide в analyzeDocumentAlignment/evaluateFrameState в
   script.js). Красный + пульсация — сознательно отличается от обычной
   бело/жёлто/зелёной прогрессии state-aligned/state-ready ниже, это не
   "почти готово", а "конкретно тут проблема, смотри сюда".
========================= */
@keyframes corner-warn-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.35; }
}

.state-keystone-top .corner.tl,
.state-keystone-top .corner.tr,
.state-keystone-bottom .corner.bl,
.state-keystone-bottom .corner.br {
    border-color: #ff3b30;
    filter: drop-shadow(0 0 10px rgba(255, 59, 48, 0.85));
    animation: corner-warn-pulse 1s ease-in-out infinite;
}

.corner.tl { top: -2px; left: -2px; border-right: none; border-bottom: none; }
.corner.tr { top: -2px; right: -2px; border-left: none; border-bottom: none; }
.corner.bl { bottom: -2px; left: -2px; border-right: none; border-top: none; }
.corner.br { bottom: -2px; right: -2px; border-left: none; border-top: none; }

/* =========================
   COVERAGE CORNERS (2026-07-23)
   Дополнительные маленькие уголки РОВНО по границе целевого покрытия
   документом (inset: 3% = визуально те же 94%, что и раньше — 2026-07-28:
   по прямой просьбе оставили визуал как был, поменяли только реальный
   порог захвата, coverageMax=0.97 в script.js — рамка визуально
   показывает 94%, а фактически захват срабатывает вплоть до 97%,
   сознательно разошлись). Обычные .corner выше —
   это большие "ручки" по краю ВСЕЙ .passport-frame (для общей рамки
   кадра), а эти — прицельные засечки на границе, где должны оказаться
   углы самого документа, чтобы совместить их было точнее, чем просто
   "на глаз" по силуэту. Цвет — те же переменные state, что и у
   .passport-frame/.corner, чтобы вся система подсказок оставалась
   визуально одним целым (нейтральный → жёлтый → зелёный).
========================= */
.coverage-corner {
    position: absolute;
    width: 18px;
    height: 18px;

    border: 2px solid var(--frame-color-searching);
    filter: drop-shadow(0 0 4px rgba(255,255,255,0.4));

    pointer-events: none;
    transition: border-color 0.2s ease, filter 0.2s ease;
}

.state-aligned .coverage-corner {
    border-color: var(--frame-color-aligned);
    filter: drop-shadow(0 0 5px rgba(255,179,0,0.7));
}

.state-ready .coverage-corner {
    border-color: var(--frame-color-ready);
    filter: drop-shadow(0 0 5px rgba(0,255,122,0.75));
}

/* 2026-07-28: убрали "гаснет при подтверждении MRZ" — по прямому запросу:
   уголки-засечки — это ориентир, ГДЕ должен быть документ в кадре, они
   должны быть видны ВСЕГДА, только менять цвет вместе с общей рамкой
   (нейтральный -> жёлтый -> зелёный), не пропадать. Раньше .state-mrz-
   confirmed .coverage-corner{opacity:0} их прятал — и не возвращал
   обратно, поскольку сам класс state-mrz-confirmed больше не снимается
   между попытками (см. историю правок про гонку с автосъёмкой) —
   уголки гасли почти сразу после разрешения камеры и больше никогда не
   появлялись.
========================= */

.coverage-corner.tl { top: 3%; left: 3%; border-right: none; border-bottom: none; }
.coverage-corner.tr { top: 3%; right: 3%; border-left: none; border-bottom: none; }
.coverage-corner.bl { bottom: 3%; left: 3%; border-right: none; border-top: none; }
.coverage-corner.br { bottom: 3%; right: 3%; border-left: none; border-top: none; }

/* =========================
   PASSPORT SILHOUETTE (2026-07-14)
   Заменяет прежние .photo-zone/.mrz-zone (жёсткие геометрические рамки) —
   вместо линий-подсказок теперь примерный силуэт разворота паспорта
   (фото + текстовые поля + два ряда MRZ-шевронов внизу), полупрозрачный,
   плавно пульсирует — понятнее для гостя, куда и как класть документ,
   чем абстрактный прямоугольник.

   94% от .passport-frame (inset: 3% с каждой стороны) — визуальный
   ориентир, НЕ строго равный реальному порогу захвата. 2026-07-28:
   coverageMax подняли до 0.97 (было 0.95) — по прямой просьбе визуал
   (эти 94%) оставили как был, поменяли только сам порог в script.js,
   так что реально документ может занимать чуть больше кадра, чем
   формально "помечено" уголками/силуэтом — это осознанный компромисс,
   не рассинхрон по ошибке.

   Пульсация — через CSS-анимацию (плавный ease-in-out, не резкий мигающий
   0/1). 2026-07-28: раньше прятался ещё и по MRZ (.state-mrz-confirmed) —
   по просьбе убрали: шаблон теперь виден до самого момента срабатывания
   затвора и гаснет ТОЛЬКО через state-frozen (см. freezePreview() в
   script.js), никогда раньше. .state-mrz-confirmed остался только для
   .coverage-corner (маленькие уголки-засечки чуть выше) — это другой,
   более тонкий индикатор, шаблона он больше не касается.
========================= */
.passport-silhouette-wrap {
    position: absolute;
    inset: 3%;

    pointer-events: none;
    color: rgba(255, 255, 255, 0.92);
}

@keyframes silhouette-pulse {
    0%, 100% { opacity: 0.24; }
    50% { opacity: 0.52; }
}

.passport-silhouette {
    width: 100%;
    height: 100%;
    display: block;

    opacity: 0.24;
    animation: silhouette-pulse 2.6s ease-in-out infinite;
    transition: opacity 0.7s ease;
}

.state-frozen .passport-silhouette {
    animation: none;
    opacity: 0;
}

/* =========================
   KIMLIK SILHOUETTE (2026-07-27)
   Тот же принцип, что у .passport-silhouette-wrap выше (inset:3% = 94%,
   тот же самый визуальный ориентир, что и у .coverage-corner — реальный
   порог захвата (coverageMax) при этом 0.97, см. комментарий там же) —
   шаблон вписывается в те же вспомогательные уголки, что и паспортный,
   просто с другой картинкой внутри (см. #kimlikSilhouette в index.html:
   МРЗ из 3 строк вместо 2, место чипа вместо фото, 4 строки текста —
   по макету карты). Показывается/скрывается через hidden в JS
   (setActiveDocumentProfile) — .passport-silhouette-wrap и
   .kimlik-silhouette-wrap никогда не видны одновременно.
========================= */
.kimlik-silhouette-wrap {
    position: absolute;
    inset: 3%;

    pointer-events: none;
    color: rgba(255, 255, 255, 0.92);
}

.kimlik-silhouette {
    width: 100%;
    height: 100%;
    display: block;

    opacity: 0.24;
    animation: silhouette-pulse 2.6s ease-in-out infinite;
    transition: opacity 0.7s ease;
}

.state-frozen .kimlik-silhouette {
    animation: none;
    opacity: 0;
}

/* =========================
   BUTTON
========================= */
/* 2026-08-21: этот блок описывает ТОЛЬКО круглую кнопку-затвор камеры
   (#captureBtn) — но селектор `button` без ID/класса цепляет ЛЮБУЮ
   <button> на странице, а не только эту. Ловили на этом реальный баг:
   новые кнопки (.public-copy-btn/.public-copy-all-btn/
   .public-scan-another-btn) молча становились 76×76px кругом с
   обрезанным текстом, потому что их классовые правила переопределяли
   padding/border-radius/font-size (класс сильнее элемента по
   специфичности), но НЕ width/height/flex — эти три свойства нужно
   переопределять явно в любой новой кнопке на этой странице, иначе
   она унаследует форму затвора. См. .doc-type-btn/.public-copy-btn/
   .public-copy-all-btn как примеры такого переопределения. */
button {
    position: relative;

    width: 76px;
    height: 76px;
    padding: 0;
    flex: 0 0 auto;

    z-index: 4;

    /* Полностью белая кнопка, как затвор камеры iPhone — тонкое серое
       кольцо вместо цветной заливки, надпись CAPTURE пока оставляем
       (задел на будущее, когда появится иконка вместо текста). */
    color: rgba(0, 0, 0, 0.35);
    background: #ffffff;
    border: 4px solid rgba(255,255,255,0.55);
    box-shadow: 0 0 0 1.5px rgba(0, 0, 0, 0.12);
    border-radius: 50%;

    font-size: 12px;
    line-height: 1;
    font-weight: bold;
}

button:active {
    background: #eeeeee;
}

button:disabled {
    opacity: 0.6;
}

@media (orientation: portrait) {
    button {
        width: 56px;
        height: 56px;
        font-size: 9px;
    }
}

/* =========================
   STATUS
========================= */
.status-rail {
    position: relative;

    width: 20px;
    min-height: 76px;
    flex: 0 0 20px;

    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 10px;

    z-index: 4;
}

.status {
    position: relative;

    color: rgba(0, 255, 122, 0.82);
    font-size: 10px;
    line-height: 1;
    font-weight: bold;
    white-space: nowrap;
    text-align: center;

    transform: rotate(-90deg);
    transform-origin: center;
}

/* =========================
   FRAME MESSAGE (единственное место для ВСЕГО текста внутри рамки)

   2026-07-13: изначально — только короткая живая подсказка выравнивания
   ("БЛИЖЕ", "СДВИНЬТЕ ВПРАВО" и т.п.) от evaluateFrameState(). По просьбе
   Андрея (2026-07-13, чат) сюда же перенесён ВЕСЬ остальной текст, что
   раньше показывался в .status сбоку — ошибки, "СЪЁМКА...", "ОТПРАВКА...",
   "СОХРАНЕНО", "ПАСПОРТ ПРОСРОЧЕН" и т.д. Единственные два исключения,
   которые остаются НЕ в рамке: #qualityDots (три кружка резкость/яркость/
   блик, см. 2026-07-16) и самая первая надпись "СФОТОГРАФИРУЙТЕ ПАСПОРТ: <ИМЯ>"
   в #status — её Андрей попросил оставить как есть.

   Живая геометрия (evaluateFrameState, работает только пока идёт
   startLiveQualityMonitor) и разовые статус-сообщения (showFrameMessage()
   ниже в script.js) делят один и тот же элемент — раньше конфликта не
   было, т.к. они жили в разных местах DOM. Теперь, если ничего не
   разруливать, статус вроде "ПАСПОРТ ПРОСРОЧЕН!" исчезнет через 150мс —
   его тут же перезапишет очередной тик живой геометрии при возобновлении
   съёмки. Решение — flag "липкости" в script.js (frameMessageSticky) с
   таймером: пока сообщение "липкое", живой тик его не трогает. Здесь, в
   CSS, это выражается через отдельные msg-* классы (эксплицитно ставятся
   в showFrameMessage()), которые должны побеждать цвет от .state-aligned/
   .state-ready — поэтому объявлены НИЖЕ этих правил по каскаду (при
   равной специфичности порядок в файле решает).

   2026-07-13 (правка после реального теста на телефоне): текст был
   развёрнут на 90° по часовой от исходного rotate(-90deg) — то есть
   -90+90 = 0deg (без поворота), а НЕ rotate(90deg), как ошибочно стояло
   тут раньше (смена знака -90→+90 — это разворот на 180°, а не на 90°,
   перелёт ровно на 90° лишних). .status снаружи рамки не трогали, там
   остаётся исходный rotate(-90deg).
========================= */
.frame-message {
    position: absolute;
    left: 50%;
    /* 2026-07-28: было top:-10px — у паспорта (рамка шире и часто выше
       упирается в потолок доступной высоты, чем у Kimlik — там своя,
       на 30% уже, поэтому вокруг неё больше запаса) отступа не хватало:
       пилюля обрезалась сверху. Подвинули почти вплотную к рамке. */
    top: -2px;
    transform: translate(-50%, -100%) rotate(0deg);
    transform-origin: center;

    /* 2026-07-28: раньше max-width:min(90%,320px) считался от ширины
       ЭКРАНА — теперь пилюля позиционируется относительно самой рамки
       (.camera-frame), у которой есть свободная широкая сторона (ровно то,
       о чём просили — "по широкой стороне место есть"), так что можно
       взять почти всю её ширину вместо жёсткого предела в 320px, который
       заставлял длинные инструкции переноситься на 3-4 строки. */
    width: max-content;
    max-width: 96%;
    padding: 8px 16px;
    border-radius: 16px;
    background: rgba(0, 0, 0, 0.6);
    box-sizing: border-box;

    color: var(--frame-color-searching);
    font-size: 11px;
    line-height: 1.35;
    font-weight: 600;
    white-space: normal;
    text-align: center;
    letter-spacing: 0.04em;

    pointer-events: none;
    z-index: 5;

    transition: color 0.2s ease;
}

.frame-message:empty {
    display: none;
}

.state-aligned .frame-message {
    color: var(--frame-color-aligned);
}

.state-ready .frame-message {
    color: var(--frame-color-ready);
}

/* Явные статус-сообщения (showFrameMessage() в script.js) — побеждают
   цвет геометрии, потому что важнее (ошибка/успех важнее "чуть левее"),
   и объявлены позже правил .state-aligned/.state-ready выше. Крупнее и
   заметнее обычной подсказки — это уже не микро-коучинг, а факт ("не
   получилось", "сохранено"), должно бросаться в глаза. */
.frame-message.msg-error,
.frame-message.msg-warn,
.frame-message.msg-success,
.frame-message.msg-info {
    font-size: 13px;
    padding: 7px 16px;
}

/* 2026-07-15: живые информационные сообщения ДО съёмки (статичная
   инструкция/держите неподвижно/предупреждения про блик-темноту-
   выравнивание) — мельче итоговых статусов после реальной съёмки (те
   используют то же .msg-* — см. правило выше). Комбинированный селектор
   (3 класса) — специально, чтобы гарантированно перебить .frame-message.
   msg-info по specificity, а не полагаться на порядок объявления в файле. */
.frame-message.msg-error.frame-message-small,
.frame-message.msg-warn.frame-message-small,
.frame-message.msg-success.frame-message-small,
.frame-message.msg-info.frame-message-small {
    font-size: 11px;
    padding: 6px 14px;
}

.frame-message.msg-error {
    color: #ff3b30;
    background: rgba(0, 0, 0, 0.72);
}

.frame-message.msg-warn {
    color: var(--frame-color-aligned);
    background: rgba(0, 0, 0, 0.72);
}

.frame-message.msg-success {
    color: var(--frame-color-ready);
    background: rgba(0, 0, 0, 0.72);
}

.frame-message.msg-info {
    color: rgba(255, 255, 255, 0.9);
    background: rgba(0, 0, 0, 0.72);
}

/* .status.status-expired убран (2026-07-13) — сообщения, которые её
   использовали (просрочен/смазано/ошибка), переехали в .frame-message
   (см. showFrameMessage() в script.js и .frame-message.msg-error там же
   в этом файле). #status теперь показывает только исходную надпись
   "СФОТОГРАФИРУЙТЕ ПАСПОРТ: <ИМЯ>" и не меняет стиль. */

/* =========================
   ORIENTATION PROMPT (2026-07-13, по просьбе Андрея; упрощено 2026-07-13
   после реального теста на телефоне — см. showOrientationHintOnce() в
   script.js для полной истории)

   Сначала пытались через window.matchMedia("(orientation: portrait)") —
   тот же сигнал, что управляет поворотом .scanner-shell ниже — блокировать
   съёмку до подтверждённого поворота. На реальном устройстве оказалось:
   видеопоток с камеры поворачивается корректно на уровне ОС независимо от
   раскладки страницы, а вьюпорт САМОЙ СТРАНИЦЫ физически не меняет форму —
   то есть matchMedia в этом случае в принципе не мог дать сигнал (не
   особенность конкретного слушателя, дело в самом вьюпорте). Настоящий
   гироскоп (DeviceOrientationEvent) читал бы физический наклон напрямую и
   не имел бы этой проблемы, но требует лишнего разрешения-диалога на iOS —
   решили, что для этого случая точность не критична, и не усложняли.

   Теперь — просто разовая подсказка на фиксированные 5 секунд при старте
   камеры, ничего не блокирует (ни живой мониторинг, ни кнопку съёмки).

   Перекрывает ВСЮ .passport-frame (dark background, inset:0), поэтому
   ВНЕШНИЙ .orientation-prompt НЕ имеет своего rotate() — тень/фон должны
   точно совпадать с видимой на экране формой рамки (которая уже повёрнута
   через ambient-поворот .scanner-shell). Поворот нужен только ВНУТРЕННЕМУ
   контенту (.orientation-prompt-inner: иконка + текст), чтобы он читался
   человеком, — тот же rotate(-90deg), что и у .status.

   2026-07-13: НЕ трогать это правило вслед за .frame-message — оказалось,
   что там (текст статусов) действительно требовался поворот, а здесь
   (иконка "поверните телефон") -90deg был верным с самого начала.
   Ошибочно поменяли оба разом в одной из правок — вернули обратно.

   Направление стрелки на иконке отзеркалено по правке от 2026-07-13
   (была "не в ту сторону") — если снова окажется не той стороной, менять
   в index.html: path/polygon у .orientation-icon.
========================= */
.orientation-prompt {
    position: absolute;
    inset: 0;
    z-index: 6;

    display: none;
    align-items: center;
    justify-content: center;

    background: rgba(0, 0, 0, 0.82);
    border-radius: 6px;

    pointer-events: none;
}

.orientation-prompt.visible {
    display: flex;
}

.orientation-prompt-inner {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;

    transform: rotate(-90deg);
    transform-origin: center;

    color: rgba(255, 179, 0, 0.95);
}

.orientation-icon {
    width: 52px;
    height: 52px;
}

.orientation-prompt-inner span {
    font-size: 13px;
    font-weight: 700;
    letter-spacing: 0.06em;
    white-space: nowrap;
    text-align: center;
}

/* =========================
   DOCUMENT TYPE CHOICE (2026-07-14)
   Первый экран, до старта камеры — см. комментарий в index.html про то,
   почему он вне .scanner-shell (не нужен поворот, камера ещё не запущена,
   экран просто на чёрном фоне body). Обычный, НЕ повёрнутый оверлей —
   читается как есть, без приёма rotate(-90deg), который используют
   элементы ВНУТРИ рамки камеры.
========================= */
.doc-type-choice {
    position: fixed;
    inset: 0;
    z-index: 20;

    display: flex;
    align-items: center;
    justify-content: center;

    background: #000;
    padding: 24px;
}

.doc-type-choice[hidden] {
    display: none;
}

.doc-type-choice-inner {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 14px;
    width: 100%;
    max-width: 340px;
}

.doc-type-choice-inner h1 {
    margin: 0 0 10px;
    color: rgba(255, 255, 255, 0.92);
    font-size: 18px;
    font-weight: 650;
    text-align: center;
    letter-spacing: -0.01em;
}

.doc-type-message-text {
    margin: 0 0 20px;
    color: rgba(255, 255, 255, 0.85);
    font-size: 15px;
    line-height: 1.5;
    text-align: center;
}

/* 2026-07-15 (третий заход): предыдущие две версии не сработали —
   вертикальная раскладка (иконка сверху/подпись снизу) с растянутой на
   всю ширину контейнера кнопкой заставляла "Загранпаспорт" (это ОДНО
   слово, без пробелов) переноситься ПРЯМО ПОСЕРЕДИНЕ слова — некрасиво
   и нечитаемо. Корень проблемы был не в переносе как таковом, а в том,
   что кнопка растягивалась на фиксированную ширину контейнера (align-
   items:stretch у родителя), а не подстраивалась под своё содержимое.

   Теперь — просто, без переносов: иконка и текст рядом в один ряд
   (flex-row), кнопка НЕ растягивается на всю ширину — align-self:center
   переопределяет stretch у родителя, ширина = сколько реально нужно
   содержимому (плюс padding), с потолком в 90% экрана на случай очень
   узкого телефона. "Загранпаспорт" при обычной ширине экрана спокойно
   помещается в одну строку без переноса вообще. */
.doc-type-btn {
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: center;
    gap: 10px;

    align-self: center;
    width: auto;
    max-width: 90%;

    padding: 14px 22px;
    border-radius: 10px;
    border: 1px solid rgba(255, 255, 255, 0.18);
    background: rgba(0, 255, 122, 0.12);
    color: #fff;
    cursor: pointer;
    white-space: nowrap;
    transition: background 0.15s ease, transform 0.1s ease;
}
.doc-type-btn:active {
    transform: scale(0.98);
}

.doc-type-btn-icon {
    font-size: 22px;
    line-height: 1;
    flex-shrink: 0;
}
.doc-type-btn-label {
    font-size: 15px;
    font-weight: 600;
    line-height: 1;
    white-space: nowrap;
}

/* Основной путь (загранпаспорт — то, что реально поддерживается) —
   заметнее; Kimlik — вторичная, приглушённая кнопка (не спрятана, просто
   визуально не соревнуется с основным путём, раз мы всё равно не можем
   его обработать сами). */
.doc-type-btn-secondary {
    background: rgba(255, 255, 255, 0.06);
}
.doc-type-btn-secondary .doc-type-btn-label {
    color: rgba(255, 255, 255, 0.75);
}

/* .doc-type-option — просто обёртка вокруг каждой .doc-type-btn (Passport/
   Kimlik) для центрирования. */
.doc-type-option {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
}

/* 2026-08-27 (доделано): "Or upload a photo instead" — PUBLIC_MODE-only
   (см. script.js, showUploadInsteadLinks). ОДНА ссылка на весь экран
   выбора документа (НЕ внутри .doc-type-option — не привязана ни к
   паспорту, ни к Kimlik), т.к. тип документа теперь определяется на
   бэкенде автоматически (см. mrz_auto_detect.py) — раньше было по одной
   ссылке под каждой кнопкой, тип задавался тем, какую из них нажали. */
.upload-instead-link {
    /* Та же причина, что у .doc-type-btn/.public-copy-btn выше (см.
       комментарий у глобального button{} в начале файла) — без явного
       переопределения ширины/высоты/flex это была бы круглая кнопка-
       затвор 76×76px с обрезанным текстом, а не текстовая ссылка. */
    display: block;
    width: auto;
    height: auto;
    flex: 0 0 auto;
    box-sizing: border-box;
    padding: 4px 8px;

    background: transparent;
    border: none;
    box-shadow: none;
    border-radius: 6px;

    color: rgba(255, 255, 255, 0.5);
    font-size: 13px;
    font-weight: 500;
    text-decoration: underline;
    text-underline-offset: 2px;
    cursor: pointer;
}
.upload-instead-link:active {
    color: rgba(255, 255, 255, 0.8);
    background: transparent;
}

.upload-formats-hint {
    margin: 4px 0 0;
    color: rgba(255, 255, 255, 0.4);
    font-size: 12px;
    line-height: 1.4;
    text-align: center;
}

.upload-status-text {
    margin: 4px 0 0;
    color: rgba(255, 255, 255, 0.85);
    font-size: 14px;
    line-height: 1.5;
    text-align: center;
}
.upload-status-text--error {
    color: rgba(255, 120, 120, 0.95);
}

/* 2026-08-27, прямой запрос из чата: раньше во время загрузки файла был
   только текст ("Uploading and recognizing your photo…") без вообще
   какого-то визуального индикатора прогресса. Две фазы:
   1) реальная загрузка байт — ширина .upload-progress-bar растёт 0%→100%
      по факту (XMLHttpRequest.upload.onprogress, см. script.js);
   2) после 100% — время распознавания на бэкенде не измеримо с
      фронтенда, полоса переключается в .upload-progress-bar--indeterminate
      (бегущий блик) вместо застывания на "100%, но ничего не происходит". */
.upload-progress-wrap {
    position: relative;
    width: 100%;
    max-width: 260px;
    height: 6px;
    margin: 10px auto 0;
    background: rgba(255, 255, 255, 0.15);
    border-radius: 3px;
    overflow: hidden;
}
.upload-progress-bar {
    position: absolute;
    top: 0;
    left: 0;
    height: 100%;
    width: 0%;
    background: var(--frame-color-ready);
    border-radius: 3px;
    transition: width 0.2s ease-out;
}
.upload-progress-bar--indeterminate {
    width: 35% !important;
    transition: none;
    animation: upload-progress-indeterminate-slide 1.1s ease-in-out infinite;
}
@keyframes upload-progress-indeterminate-slide {
    0% { left: -35%; }
    100% { left: 100%; }
}

/* Кнопка съёмки больше не нужна — см. комментарий в index.html. */
.capture-btn-hidden {
    display: none;
}

/* 2026-08-21: карточка результата для публичного бесплатного сканера
   (PUBLIC_MODE, /g/free-scanner) — тот же тёмный визуальный язык, что и
   у .doc-type-choice выше (fixed, во весь экран, поверх камеры), просто
   с прокруткой — полей может быть больше, чем влезает на маленький экран
   телефона одним разворотом. */
.public-result-panel {
    position: fixed;
    inset: 0;
    z-index: 25;

    display: flex;
    flex-direction: column;
    gap: 16px;

    background: #000;
    padding: 24px 20px calc(24px + env(safe-area-inset-bottom, 0px));
    overflow-y: auto;
}
.public-result-panel[hidden] {
    display: none;
}

.public-result-header h2 {
    margin: 0 0 6px;
    color: rgba(255, 255, 255, 0.92);
    font-size: 18px;
    font-weight: 650;
    letter-spacing: -0.01em;
}
.public-result-hint {
    margin: 0;
    color: rgba(255, 255, 255, 0.55);
    font-size: 13px;
    line-height: 1.45;
}

.public-result-fields {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.public-result-row {
    display: flex;
    align-items: center;
    gap: 10px;

    padding: 10px 12px;
    border-radius: 10px;
    border: 1px solid rgba(255, 255, 255, 0.1);
    background: rgba(255, 255, 255, 0.04);
}
.public-result-text {
    flex: 1;
    min-width: 0;
}
/* 2026-08-22: бейджи "верно/неверно" рядом с подписью поля — прямой запрос
   из чата, см. buildPublicResultBadge()/renderPublicResults() в script.js.
   label-row вместо голого .public-result-label — иначе бейдж (inline)
   лёг бы под текстом подписи, та display:block. */
.public-result-label-row {
    display: flex;
    align-items: center;
    gap: 6px;
}
.public-result-label {
    display: block;
    color: rgba(255, 255, 255, 0.5);
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.public-result-badge {
    display: inline-flex;
    align-items: center;
    flex-shrink: 0;
    padding: 1px 7px;
    border-radius: 999px;
    font-size: 10px;
    font-weight: 700;
    text-transform: none;
    letter-spacing: normal;
    white-space: nowrap;
}
.public-result-badge.badge-ok { background: rgba(0, 255, 122, 0.22); color: #6dffb0; }
.public-result-badge.badge-bad { background: rgba(255, 70, 70, 0.22); color: #ff8a8a; }
.public-result-badge.badge-warn { background: rgba(255, 200, 0, 0.22); color: #ffd866; }
.public-result-value {
    display: block;
    margin-top: 2px;
    color: rgba(255, 255, 255, 0.95);
    font-size: 15px;
    font-weight: 600;
    word-break: break-word;
}
.public-result-value--empty {
    color: rgba(255, 255, 255, 0.35);
    font-weight: 500;
    font-style: italic;
}

.public-copy-btn {
    /* width/height/border-radius/flex здесь ОБЯЗАТЕЛЬНЫ, не косметика:
       глобальный селектор `button` в этом файле (см. "BUTTON" секцию
       выше) — это стили круглой кнопки-затвора камеры (#captureBtn),
       76×76px, border-radius:50%, padding:0, flex:0 0 auto. Он не
       ограничен ID/классом, поэтому цепляет ЛЮБУЮ <button> на странице.
       Классовые правила (padding/border-radius/font-size ниже) бьют его
       по специфичности, но width/height/flex — нет, если их явно не
       переопределить: без этого кнопка "Copy" у каждого поля становится
       76×76px кругом вместо компактной кнопки рядом с текстом. Тот же
       паттерн уже используется в .doc-type-btn (см. width: auto там). */
    flex-shrink: 0;
    width: auto;
    height: auto;
    padding: 8px 14px;
    border-radius: 8px;
    border: 1px solid rgba(255, 255, 255, 0.18);
    background: rgba(255, 255, 255, 0.08);
    color: #fff;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    transition: background 0.15s ease, transform 0.1s ease;
}
.public-copy-btn:active {
    transform: scale(0.96);
}
.public-copy-btn--done {
    background: rgba(0, 255, 122, 0.22);
    border-color: rgba(0, 255, 122, 0.4);
}

.public-result-actions {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-top: 4px;
}
.public-copy-all-btn,
.public-scan-another-btn {
    /* width/height/flex переопределены здесь по той же причине, что и
       у .public-copy-btn выше — иначе это круглая кнопка-затвор камеры
       76×76px, а не прямоугольная кнопка на всю ширину. Явный
       прямоугольник (border-radius: 10px, не 50%, ширина на весь
       контейнер) — по прямой просьбе из чата ("текст не помещается...
       сделай их прямоугольными"). */
    display: block;
    width: 100%;
    height: auto;
    flex: 0 0 auto;
    box-sizing: border-box;
    padding: 14px 22px;
    border-radius: 10px;
    border: 1px solid rgba(255, 255, 255, 0.18);
    font-size: 15px;
    font-weight: 650;
    text-align: center;
    cursor: pointer;
    transition: background 0.15s ease, transform 0.1s ease;
}
.public-copy-all-btn:active,
.public-scan-another-btn:active {
    transform: scale(0.98);
}
.public-copy-all-btn {
    background: rgba(0, 255, 122, 0.16);
    color: #fff;
}
.public-copy-all-btn--done {
    background: rgba(0, 255, 122, 0.32);
}
.public-scan-another-btn {
    background: rgba(255, 255, 255, 0.06);
    color: rgba(255, 255, 255, 0.8);
}

.public-result-b2b {
    text-align: center;
    margin-top: 14px;
}
.public-result-b2b a {
    font-size: 13px;
    color: rgba(255, 255, 255, 0.45);
    text-decoration: none;
}
.public-result-b2b a:hover {
    color: rgba(255, 255, 255, 0.75);
    text-decoration: underline;
}

