/* Токены (`--ep-*`) — в `estiprint-tokens.css`, этот лист от него зависит. */

/* Фокус с клавиатуры — одной рамкой на всю витрину, включая всплывающие окна.
   Своё правило было у четырёх элементов из четырёх десятков: у остальных
   обходом с клавиатуры распоряжалась тема, то есть по-разному на каждом сайте.
   `:focus-visible`, а не `:focus`: мышь рамку не зажигает. */
.estiprint-calculator-section :focus-visible,
.estiprint-popup :focus-visible,
.estiprint-scrim :focus-visible {
    outline: var(--ep-focus-ring);
    outline-offset: var(--ep-focus-offset);
}

/* Внутрь — у тех, кто прижат к краю своего контейнера: снаружи рамка легла бы
   на соседа или обрезалась бы прокруткой. */
.estiprint-cart-item:focus-visible,
.estiprint-lead-option:focus-visible {
    outline-offset: -2px;
}

/* Калькулятор меряет себя, а не окно.
   ---------------------------------------------------------------------------
   Он встраивается шорткодом в чужую тему, и ширины окна о нём не говорят
   ничего: в колонке блога при окне 1440 ему достаётся 600 пикселей, и раскладка
   «широкого экрана» разъезжается там, где по числам всё в порядке. Ширину
   контейнера тема тоже не обязана сообщать.

   Поэтому секция объявлена контейнером, и все брейкпоинты калькулятора —
   `@container`, а не `@media`. Числа остались прежние; изменилось то, что они
   меряют.

   Вне контейнера остаются двое: всплывающая форма и подсказка. Обе `position:
   fixed` поверх страницы, и точкой отсчёта у них остаётся окно — проверено
   замером, а не выведено из `contain: layout`. Их раскладка про окно и есть.

   `container-type: inline-size` даёт `contain: layout inline-size style`, но не
   `paint`: подсказка, вылезающая за край карточки, не обрезается. */
.estiprint-calculator-section {
    container-type: inline-size;
    container-name: estiprint;
    background: #fff;
    font-family: var(--ep-font-family);
    padding: 60px 0;
}

.estiprint-calculator-section * {
    box-sizing: border-box;
}

/* Ширину назначает себе калькулятор, а не тема.
   ---------------------------------------------------------------------------
   Блочные темы WordPress раскладывают контент правилом
   `.is-layout-constrained > :where(:not(.alignwide):not(.alignfull))` и режут
   всё внутри до `--wp--style--global--content-size`. На этом стенде это
   650 пикселей: калькулятор, нарисованный под 1170, всю дорогу жил в двух
   третях своей ширины и показывал узкую раскладку на широком экране.

   Имя класса повторено дважды — 0,2,0 против 0,1,0 у темы. Шире рамки это не
   сделает: `--ep-frame-max` ниже ставит собственный предел и центрирует, так
   что видимая ширина — это `min(1170, сколько дали)`, а не «во весь экран».

   Магазину, которому нужно обратно в колонку темы, довольно одной строки:
   `.estiprint-calculator-section { max-width: 100% }` весом 0,1,0 не победит,
   поэтому для этого есть `--ep-frame-max`. */
.estiprint-calculator-section.estiprint-calculator-section {
    max-width: none;
}

/* То же и для всплывающих поверх страницы.
   ---------------------------------------------------------------------------
   Форма заявки и окно согласия выводятся хуком `estiprint3d_after_calculator`,
   то есть лежат в том же `.entry-content`, — и то же правило темы давало им
   `max-width: 650px` и `margin: 24px auto`. Позиционирование при этом честно
   оконное: `position: fixed` работал, ширину и отступ задавала тема. На экране
   это выглядело как затемнение узкой полосой посреди страницы и форма,
   прижатая к колонке текста. */
.estiprint-popup.estiprint-popup,
.estiprint-scrim.estiprint-scrim {
    max-width: none;
    margin: 0;
}

/* Граница со стилями темы.
   ---------------------------------------------------------------------------
   Калькулятор стои́т внутри `.entry-content`, и тема бьёт по нему правилами
   вроде `.entry-content ul` или `.entry-content button` — это 0,1,1 против
   наших 0,1,0, то есть тема выигрывает. Здесь сбрасывается не всё подряд
   (`all: unset` снёс бы и наше), а именно то, чем темы обычно и бьют.

   Через `:where()`, чтобы сброс не поднимал специфичность: он остаётся 0,1,0,
   стои́т в начале файла, и любое наше правило ниже перебивает его порядком, а
   не весом. Списки, которым нужны маркеры, объявляют их у себя и потому
   выигрывают.

   Наследуемое задаётся на корне: до сих пор секция объявляла из него один
   `font-family`, а `color`, `line-height`, `letter-spacing` и `text-transform`
   приходили из темы — и «ЗАГОЛОВОК ВРАЗРЯДКУ» появлялся там, где мы про буквы
   ничего не говорили. */
.estiprint-calculator-section,
.estiprint-popup,
.estiprint-scrim {
    color: var(--ep-color-graphite);
    font-size: 16px;
    line-height: 1.5;
    font-weight: 400;
    letter-spacing: normal;
    word-spacing: normal;
    text-transform: none;
    /* `start`, а не `left`: у языков справа налево это разные стороны. */
    text-align: start;
}

.estiprint-calculator-section :where(p, ul, ol, li, dl, dd, figure, blockquote,
h1, h2, h3, h4, h5, h6),
.estiprint-popup :where(p, ul, ol, li, h1, h2, h3, h4, h5, h6),
.estiprint-scrim :where(p, ul, ol, li, h1, h2, h3, h4, h5, h6) {
    margin: 0;
    padding: 0;
    color: inherit;
    letter-spacing: inherit;
    text-transform: none;
}

.estiprint-calculator-section :where(ul, ol),
.estiprint-popup :where(ul, ol),
.estiprint-scrim :where(ul, ol) {
    list-style: none;
}

/* Поля и кнопки не наследуют шрифт сами — это правило браузера, а не темы, но
   чинится здесь же и один раз. */
.estiprint-calculator-section :where(button, input, select, textarea),
.estiprint-popup :where(button, input, select, textarea),
.estiprint-scrim :where(button, input, select, textarea) {
    font: inherit;
    color: inherit;
    letter-spacing: inherit;
    text-transform: none;
    box-shadow: none;
}

.estiprint-calculator-section :where(a),
.estiprint-popup :where(a),
.estiprint-scrim :where(a) {
    text-decoration: none;
    box-shadow: none;
}

.estiprint-calculator-section :where(img, svg, canvas),
.estiprint-popup :where(img, svg),
.estiprint-scrim :where(img, svg) {
    max-width: 100%;
    /* Тема, обтекающая картинки, унесла бы холст просмотрщика вбок. */
    float: none;
}

/* Страховка для браузеров без контейнерных запросов (Safari до 16, Firefox до
   110). Там ни один `@container` не сработает, и калькулятор остался бы в самой
   широкой раскладке на телефоне — то есть в двух колонках по 180 пикселей.
   Повторять здесь все пять блоков незачем: достаточно того, без чего страница
   нечитаема, а остальное переживёт лишний ряд. */
@supports not (container-type: inline-size) {
    @media (max-width: 900px) {
        .estiprint-calculator-grid {
            grid-template-columns: 1fr;
        }
    }

    @media (max-width: 576px) {
        .estiprint-calculator-summary,
        .estiprint-calculator-summary-right {
            grid-template-columns: 1fr;
        }

        .estiprint-lead {
            flex-direction: column;
        }
    }
}

.estiprint-calculator-frame {
    border-radius: var(--ep-radius-card);
    /* Предел ширины — токеном: магазин меняет его объявлением
       `--estiprint-max-width`, а не борьбой со специфичностью. */
    max-width: var(--ep-frame-max);
    margin: 0 auto;
    position: relative;
}

.estiprint-calculator-header {
    margin-bottom: 50px;
}

.estiprint-calculator-title {
    font-size: 60px;
    font-weight: 600;
    line-height: 1.133;
    letter-spacing: -1px;
    color: var(--ep-color-title);
    margin: 0 0 30px;
}

.estiprint-calculator-description {
    font-size: 20px;
    font-weight: 400;
    line-height: 1.2;
    letter-spacing: 0.4px;
    color: var(--ep-color-title);
    margin: 0;
}

.estiprint-calculator-content {
    display: flex;
    flex-direction: column;
    gap: 30px;
}

/* Пропорция макета, а не половина на половину: слева собирается заявка — список,
   который растёт до сорока строк, — справа одна выбранная модель. */
.estiprint-calculator-grid {
    display: grid;
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
    gap: 30px;
    align-items: start;
}

.estiprint-calculator-uploader {
    border: 1px dashed var(--ep-color-gray);
    border-radius: var(--ep-radius-card);
    padding: 24px 40px;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    transition: border-color .15s, background-color .15s;
    /* Курсор говорит то же, что и рамка: сюда можно. Зона принимает нажатие
       целиком, а не только подписью внутри неё. */
    cursor: pointer;
}

/* Файл над зоной. Артборд `Main`: `.drop.is-hot`. Раньше перетаскивание не
   отвечало ничем, и было непонятно, отпускать здесь или мимо. */
.estiprint-calculator-uploader.is-hot {
    border-color: var(--ep-color-accent);
    background: var(--ep-accent-soft, #fdeef1);
}

/* Пока файл летит, зона не приглашает начать заново. */
.estiprint-calculator-uploader.is-busy {
    cursor: progress;
}

/* Обход с клавиатуры. Поле файла спрятано нулевым размером и `clip`, и общая
   рамка витрины доставалась ему же — то есть прямоугольнику 0×0 под `clip`.
   Рамка рисовалась и была не видна ни на чём: дойдя табом до зоны загрузки,
   человек видел, что фокус пропал.

   Рамка переносится на саму зону — ту же, что светится под занесённым файлом:
   одно место отвечает и на перетаскивание, и на клавиатуру. `:has()`, потому
   что метка с полем внутри — потомок зоны, и снизу вверх иначе не дотянуться;
   браузер постарше правило пропустит и останется при нынешнем поведении.

   `:focus-visible`, а не `:focus`: нажатие мышью фокусирует то же поле, и на
   каждый выбор файла зона зажигалась бы рамкой обхода. */
.estiprint-calculator-uploader:has(.estiprint-calculator-uploader-input:focus-visible) {
    outline: var(--ep-focus-ring);
    outline-offset: var(--ep-focus-offset);
}

/* И не двумя рамками сразу: своя, невидимая, у поля остаётся от общего
   правила витрины. */
.estiprint-calculator-uploader-input:focus-visible {
    outline: none;
}

.estiprint-calculator-uploader-step {
    font-size: 18px;
    font-weight: 600;
    line-height: 26px;
    color: var(--ep-color-title);
    margin: 0;
    list-style: decimal;
    padding: 10px 10px 10px 20px;
}

.estiprint-calculator-uploader-label {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 30px;
    cursor: pointer;
    /* Было 58/50: столько пустоты держали иконка с одной строкой под ней.
       С заголовком и метками зона выросла бы на экран. */
    padding: 34px 0 32px;
}

.estiprint-calculator-uploader-icon-path {
    transition: fill 0.3s ease;
}

.estiprint-calculator-uploader-label:hover .estiprint-calculator-uploader-icon-path {
    fill: var(--ep-color-accent-hover);
    transition: fill 1s ease;
}

.estiprint-calculator-uploader-content {
    display: flex;
    flex-direction: column;
    align-items: center;
}

.estiprint-calculator-uploader-icon-wrap {
    width: 206px;
    height: 138px;
    perspective: 600px;
}

.estiprint-calculator-uploader-icon {
    display: block;
    width: 100%;
    height: 100%;
    will-change: transform;
    transition: transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

.estiprint-calculator-uploader-icon svg {
    width: 100%;
    height: 100%;
    display: block;
}

/* Заголовок зоны по макету `docs/design/Main.dc.html`: зона называет себя сама,
   вместо счёта из одного пункта над ней. */
.estiprint-calculator-uploader-title {
    margin: 24px 0 0;
    font-size: 17px;
    font-weight: 700;
    line-height: 1.3;
    color: var(--ep-color-title);
    text-align: center;
}

/* Формат и предел — метки, а не абзац: их читают взглядом, а не строкой. */
.estiprint-calculator-uploader-info {
    font-size: 16px;
    font-weight: 400;
    line-height: 1.5;
    color: var(--ep-color-graphite);
    text-align: center;
    margin: 12px 0 0;
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    justify-content: center;
    gap: 8px;
}

.estiprint-calculator-uploader-chip {
    padding: 3px 10px;
    border: 1px solid var(--ep-color-light-gray);
    border-radius: var(--ep-r-pill, 999px);
    font-size: 12px;
    font-weight: 500;
    line-height: 18px;
    color: var(--ep-color-graphite);
    white-space: nowrap;
}

.estiprint-calculator-uploader-link {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 12px 28px;
    border: 1px solid var(--ep-color-accent);
    border-radius: var(--ep-radius-btn-sm);
    font-size: 17px;
    font-weight: 600;
    line-height: 1.3;
    letter-spacing: 0.18px;
    color: var(--ep-color-accent);
    min-width: 240px;
    min-height: var(--ep-h-lg);
    margin-top: 28px;
    box-sizing: border-box;
    transition: color 0.3s ease, background 0.3s ease, border-color 0.3s ease, fill 0.3s ease;
}

.estiprint-calculator-uploader-link:hover {
    color: var(--ep-color-accent-hover);
    border-color: var(--ep-color-accent-hover);
    transition: color 0.3s ease, background 0.3s ease, border-color 0.3s ease, fill 0.3s ease;
}

.estiprint-calculator-uploader-input {
    position: absolute;
    width: 0;
    height: 0;
    opacity: 0;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
}

.estiprint-calculator-uploader-note {
    display: block;
    font-size: 14px;
    font-weight: 400;
    line-height: 24px;
    color: var(--ep-color-graphite);
    text-align: center;
    margin: 0;
}

.is-hidden {
    display: none !important;
}

/* Сцена — рамка постоянного размера, а не появляющийся холст. Три состояния
   (модель, пусто, замер) занимают одно и то же место, поэтому колонка не
   прыгает, когда одно сменяет другое. Артборд `Main` / `Empty` / `Analysing`. */
.estiprint-calculator-stage {
    position: relative;
    width: 100%;
    aspect-ratio: 1;
    max-height: 400px;
    border-radius: 12px;
    overflow: hidden;
    background: #f7f8f9;
    border: 1px solid var(--ep-color-light-gray);
}

.estiprint-calculator-stage-note {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 10px;
    margin: 0;
    padding: 24px;
    box-sizing: border-box;
    text-align: center;
    font-size: 13px;
    line-height: 1.5;
    color: var(--ep-color-placeholder);
}

.estiprint-calculator-stage-cube {
    flex: none;
    color: var(--ep-ink-3, #8c8c8c);
    opacity: .55;
}

.estiprint-calculator-viewer {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    gap: 0;
}

.estiprint-calculator-viewer-canvas {
    width: 100%;
    height: 100%;
    border-radius: 12px;
    overflow: hidden;
    background: #f0f0f0;
}

.estiprint-calculator-viewer-filebar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 14px 0 0;
}

.estiprint-calculator-viewer-fileinfo {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
}

.estiprint-calculator-viewer-fileicon {
    width: 24px;
    height: 24px;
    color: var(--ep-color-graphite);
    flex-shrink: 0;
}

.estiprint-calculator-viewer-fileicon svg {
    width: 100%;
    height: 100%;
    display: block;
}

.estiprint-calculator-viewer-filename {
    font-size: 16px;
    font-weight: 400;
    line-height: 32px;
    color: #000;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 280px;
}

.estiprint-calculator-viewer-remove {
    width: 40px;
    height: 40px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: none;
    background: none;
    color: var(--ep-color-graphite);
    cursor: pointer;
    border-radius: var(--ep-radius-btn-sm);
    flex-shrink: 0;
    transition: color 0.2s ease, background 0.2s ease;
}

.estiprint-calculator-viewer-remove:hover {
    color: var(--ep-accent);
    background: var(--ep-hover-danger);
}

.estiprint-calculator-viewer-remove svg {
    display: block;
}

.estiprint-calculator-options {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.estiprint-calculator-option {
    border: 1px solid var(--ep-color-gray);
    border-radius: var(--ep-radius-card);
    padding: 24px 40px 34px;
    display: flex;
    flex-direction: column;
    gap: 10px;
    position: relative;
}

/* Подсказка-иллюстрация пустого шага снята вместе с пустым шагом: выбор есть
   всегда. Правило остаётся для тем, переопределивших `options.php` раньше, —
   в их разметке рука ещё стои́т, и без него она бы появилась. */
.estiprint-calculator-option-hint {
    display: none;
}

@keyframes ep-spin {
    to {
        transform: rotate(360deg);
        transform-origin: center;
    }
}

/* Заголовок шага: подпись и ⓘ рядом.
   ---------------------------------------------------------------------------
   `justify-content: space-between` разносил их по краям колонки, и значок
   оказывался в сорока сантиметрах от слова, которое объясняет, — над правым
   краем списка, то есть читался значком самого списка, а не подписи. Отступ в
   девять пикселей достался от времён, когда заголовок был кнопкой,
   раскрывающей шаг; шаги не раскрываются с 1.7.0, и подпись из-за него стояла
   не по левому краю корыта. */
.estiprint-calculator-option-header {
    display: flex;
    align-items: center;
    gap: 7px;
    margin: 0 0 10px;
}

/* ============================================================
   Шаги не раскрываются

   Материалы и цвета видны сразу. Ради этого и мерились клики
   (замечание №16): четыре на модель превращаются в два, только
   если шаг не надо сперва открыть. Плашка выбранного и стрелка
   раскрытия убраны из разметки — выбранное видно в самом списке.
   ============================================================ */

.estiprint-calculator-option-body {
    display: block;
}

.estiprint-sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    border: 0;
}

.estiprint-calculator-section select {
    font-family: var(--ep-font-family);
    font-size: 16px;
    line-height: 1.35;
}

/* Нижняя полоса по макету `docs/design/Main.dc.html`: срок слева, из чего
   сложился итог — справа, и кнопка отправки там же. Липкая: на заявке из сорока
   деталей итог иначе остаётся под списком, и до него надо доскроллить, чтобы
   узнать, во что обошлась правка одной строки. */
.estiprint-calculator-summary {
    position: sticky;
    bottom: 0;
    z-index: 5;
    background: var(--ep-color-light-gray);
    border-radius: var(--ep-radius-card);
    padding: 22px 32px;
    display: grid;
    /* Две колонки: слева слова магазина и разбор итога, справа то, чем цену
       меняют, и сама цена. Третьей не осталось — её жильцы разъехались по
       краям. Обе умеют ужиматься: `max-content`, который стоял здесь раньше,
       не сжимается никогда, и между 576 и 900 полоса вылезала из карточки. */
    grid-template-columns: minmax(220px, 1fr) minmax(0, auto);
    gap: 16px 40px;
    /* По верху, а не по центру: «Estimated Cost» — заголовок полосы, и висеть
       посередине её высоты он не должен. Кнопки к низу прижимает сама правая
       колонка. */
    align-items: start;
}

/* Полоса срока и разбивка итога — заполняются из `renderCart()`, поэтому
   пустыми не должны занимать места. */
.estiprint-summary-lead:empty,
.estiprint-summary-breakdown:empty {
    display: none;
}

.estiprint-summary-breakdown .estiprint-cart-total {
    margin: 0;
    padding: 0;
    border-top: 0;
}

/* В полосе срока подпись стои́т слева от цены, а не над ней: рядом с итогом
   колонка карточек спорила бы с ним ростом. */
.estiprint-summary-lead .estiprint-lead {
    margin: 0;
}

.estiprint-calculator-summary-left {
    display: flex;
    flex-direction: column;
    gap: 6px;
    min-width: 0;
}

/* Разбор итога идёт под словами магазина и отделён от них воздухом: это уже не
   объяснение, а числа. */
.estiprint-summary-breakdown:not(:empty) {
    margin-top: 10px;
}

.estiprint-calculator-summary-title {
    font-size: 17px;
    font-weight: 700;
    line-height: 1.3;
    color: #000;
    margin: 0;
}

.estiprint-calculator-summary-hint,
.estiprint-calculator-summary-note {
    font-size: 13px;
    font-weight: 400;
    line-height: 1.45;
    color: var(--ep-color-graphite);
    margin: 0;
    max-width: 42ch;
}

/* Оговорка о минимуме — сноской под ценой: условие того числа, что стои́т над
   ней. Меру берёт по цене, а не по колонке текста, и садится к ней вплотную —
   между числом и его условием воздуха быть не должно, иначе это уже не сноска,
   а следующий абзац. */
.estiprint-calculator-summary-right > .estiprint-calculator-summary-note {
    max-width: none;
    margin-top: -6px;
    font-size: 12px;
    line-height: 1.4;
    color: var(--ep-color-placeholder);
}

/* Решения и само число — справа: срок, услуги заявки, итог, сноска, кнопки. */
.estiprint-calculator-summary-right {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 12px;
    min-width: 0;
}

.estiprint-summary-actions {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
}

/* Кнопку сохранения рисует аддон, и рост у неё свой. В одной колонке с главной
   это читается лесенкой, поэтому здесь она приводится к той же мере. */
.estiprint-summary-actions .epsq-summary-save__btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    min-height: var(--ep-h-md);
    box-sizing: border-box;
    border-radius: var(--ep-radius-btn-sm);
}

/* Полоса причины — поперёк всей нижней полосы, над всем остальным
   (артборд `NeedsAction`). */
.estiprint-calculator-summary > .estiprint-calculator-notice {
    grid-column: 1 / -1;
    margin: 0;
}

.estiprint-calculator-summary-price-wrap {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px 14px;
}

.estiprint-calculator-summary-price {
    font-size: 24px;
    font-weight: 600;
    line-height: 30px;
    color: var(--ep-color-accent);
    margin: 0;
}

.estiprint-currency-switcher {
    display: flex;
    align-items: center;
    gap: 6px;
}

/* Переключатель валют рисует аддон; здесь он приводится к той же мере, что
   соседние мелкие кнопки. */
.estiprint-currency-switcher button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: var(--ep-h-sm);
    border-radius: var(--ep-radius-btn-sm);
}

.estiprint-currency-switcher:empty {
    display: none;
}

.estiprint-currency-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: var(--ep-h-sm);
    padding: 4px 12px;
    border: 1px solid var(--ep-color-gray);
    border-radius: var(--ep-radius-btn-sm);
    background: transparent;
    font-family: var(--ep-font-family);
    font-size: 13px;
    font-weight: 500;
    line-height: 18px;
    color: var(--ep-color-graphite);
    cursor: pointer;
    transition: color 0.2s ease, background 0.2s ease, border-color 0.2s ease;
}

.estiprint-currency-btn:hover {
    border-color: var(--ep-hover-ink);
    color: var(--ep-hover-ink);
}

.estiprint-currency-btn.is-active {
    background: var(--ep-selected-ink);
    border-color: var(--ep-selected-ink);
    color: #fff;
    pointer-events: none;
}

/* Главное действие полосы. `margin-right:auto` прижимал кнопку влево, когда она
   стояла в столбце сама по себе; в колонке кнопок это разъезжало её с соседней
   «Save quote» на разную ширину. */
/* Главное действие — одно правило на обе кнопки: «посчитать» в нижней полосе и
   «отправить» в форме заявки. Это одно и то же действие в двух шагах одного
   сценария, и мера у них была разная — 52 пикселя роста против 64, 17 против
   18 в шрифте, скругление поля против скругления кнопки. */
.estiprint-calculator-summary-submit,
.estiprint-popup-form .wpcf7-submit {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: var(--ep-h-lg);
    padding: 12px 28px;
    border: none;
    border-radius: var(--ep-radius-btn-sm);
    background: var(--ep-color-disabled);
    font-family: var(--ep-font-family);
    font-size: 17px;
    font-weight: 600;
    line-height: 1.3;
    letter-spacing: 0.18px;
    color: #fff;
    cursor: not-allowed;
    box-sizing: border-box;
    transition: color 0.3s ease, background 0.3s ease, border-color 0.3s ease, fill 0.3s ease;
}

.estiprint-calculator-summary-submit:not(:disabled),
.estiprint-popup-form .wpcf7-submit:not(:disabled) {
    background: var(--ep-color-accent);
    cursor: pointer;
    transition: color 0.3s ease, background 0.3s ease, border-color 0.3s ease, fill 0.3s ease;
}

.estiprint-calculator-summary-submit:not(:disabled):hover,
.estiprint-popup-form .wpcf7-submit:not(:disabled):hover {
    background: var(--ep-color-accent-hover);
    transition: color 0.3s ease, background 0.3s ease, border-color 0.3s ease, fill 0.3s ease;
}

button.estiprint-calculator-summary-submit:disabled,
.estiprint-popup-form .wpcf7-submit:disabled {
    background: var(--ep-color-disabled);
    border: none;
    cursor: not-allowed;
}

.estiprint-popup {
    position: fixed;
    inset: 0;
    z-index: var(--ep-z-overlay);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20px;
    box-sizing: border-box;
    visibility: hidden;
    opacity: 0;
    transition: visibility 0.3s ease, opacity 0.3s ease;
}

.estiprint-popup.is-open {
    visibility: visible;
    opacity: 1;
}

.estiprint-popup-backdrop {
    position: absolute;
    inset: 0;
    background: var(--ep-scrim);
}

/* Окно поверх страницы — с той же тенью, что у окна согласия: без неё белый
   диалог на светлой странице держался одним затемнением. */
.estiprint-popup-dialog {
    position: relative;
    background: #fff;
    box-shadow: var(--ep-shadow-dialog);
    border-radius: var(--ep-radius-card);
    padding: 70px;
    max-height: calc(100vh - 40px);
    overflow-y: auto;
    box-sizing: border-box;
}

.estiprint-popup-dialog--form {
    max-width: 850px;
    width: 100%;
}

.estiprint-popup-dialog--success {
    max-width: 547px;
    width: 100%;
}

.estiprint-popup-close {
    position: absolute;
    right: 24px;
    top: 24px;
    width: 40px;
    height: 40px;
    padding: 0;
    border: none;
    background: none;
    cursor: pointer;
    color: var(--ep-color-graphite);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1;
    transition: color 0.3s ease, background 0.3s ease, border-color 0.3s ease, fill 0.3s ease;
}

/* Крестик закрытия — снять окно, а не удалить: наведение говорит то же, что у
   остальных снимающих значков. Обводка идёт за цветом кнопки, поэтому отдельного
   правила на `svg > path` больше нет — раньше оно красило знак в чернила поверх
   акцента, то есть спорило с собственным наведением. */
.estiprint-popup-close:hover {
    color: var(--ep-accent);
    background: var(--ep-hover-danger);
}

.estiprint-popup-body {
    display: flex;
    gap: 50px;
    align-items: flex-start;
}

.estiprint-popup-info {
    display: flex;
    flex-direction: column;
    gap: 20px;
    justify-content: center;
    flex-shrink: 0;
    max-width: 300px;
}

.estiprint-popup-title {
    font-family: var(--ep-font-family);
    font-size: 32px;
    font-weight: 600;
    line-height: 38px;
    color: var(--ep-color-title);
    margin: 0;
}

.estiprint-popup-subtitle {
    font-family: var(--ep-font-family);
    font-size: 16px;
    font-weight: 400;
    line-height: 24px;
    color: var(--ep-color-graphite);
    margin: 0;
}

.estiprint-popup-icon {
    width: 170px;
    height: 170px;
    flex-shrink: 0;
}

.estiprint-popup-icon svg {
    display: block;
    width: 100%;
    height: 100%;
}

.estiprint-popup-form {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-width: 0;
}

/* Плашка «формы нет» — тем же языком, что сообщения об ошибке отправки и
   оговорка под итогом: бледный акцент и полоса слева. Рамка по кругу была
   четвёртым видом тревожной плашки на витрине. */
.estiprint-popup-cf7-unavailable {
    padding: 16px 20px;
    border-radius: var(--ep-radius-field, 8px);
    background: var(--ep-accent-soft);
    border-left: 3px solid var(--ep-color-accent);
}

.estiprint-popup-cf7-unavailable__text {
    margin: 0;
    font-size: 15px;
    line-height: 1.5;
    color: var(--ep-color-graphite, #4c5359);
}

.estiprint-popup-cf7-unavailable__actions {
    margin: 12px 0 0;
}

.estiprint-popup-cf7-unavailable__admin-link {
    font-weight: 600;
    color: var(--ep-color-accent, #dd0b2f);
    text-decoration: underline;
}

.estiprint-popup-cf7-unavailable__admin-link:hover {
    color: var(--ep-accent-hover);
}

.estiprint-popup-form .wpcf7-form>p,
.estiprint-popup-form .wpcf7-form>div {
    margin: 0;
}

/* Подпись, которую рисует Contact Form 7, гасится нулевым кеглем: своя подпись
   у поля уже есть, а две над одним полем — это две. */
.estiprint-popup-form .wpcf7-form>p>label {
    font-size: 0;
    line-height: 0;
}

/* Ноль наследуется вниз, и вместе с подписью схлопывалось всё, что лежит внутри
   той же метки. `cf7-intl-tel-input` кладёт туда список стран, и его имена
   превращались в пустые строки — «Afghanistan (+93)» шириной 278 пикселей и
   высотой ноль. Кегль возвращается на содержимом метки: одно правило на всё
   внутри, а не перечисление чужих классов, которые сменятся в следующей версии
   того плагина. */
.estiprint-popup-form .wpcf7-form>p>label>* {
    font-size: 16px;
    line-height: 1.35;
}

.estiprint-popup-form .wpcf7-form>p>label>br {
    display: none;
}

.estiprint-floating-field {
    position: relative;
    display: block;
    padding-bottom: 20px;
}

.estiprint-floating-label {
    position: absolute;
    left: 0;
    top: 0;
    font-family: var(--ep-font-family);
    font-size: 16px;
    line-height: 1.35;
    letter-spacing: 0.32px;
    color: var(--ep-color-graphite);
    pointer-events: none;
    transform-origin: left top;
    transition: transform 0.2s ease, color 0.2s ease;
    white-space: nowrap;
}

/* Подпись телефона начинается после того, что `cf7-intl-tel-input` держит слева
   внутри поля, — отступ ставит `initFloatingLabels()`, спросив его у самого
   поля. Числом он здесь стоять не может: флаг занимает 52 пикселя, флаг с кодом
   страны — 85, и зависит это от настроек чужого плагина. Уехав вверх, подпись
   возвращается к левому краю: над строкой ни флага, ни кода нет. */
.estiprint-floating-field--tel.is-active .estiprint-floating-label {
    left: 0;
}

.estiprint-floating-field.is-active .estiprint-floating-label {
    transform: translateY(-100%) scale(0.75);
    color: var(--ep-color-graphite);
}

/* Поля формы отвечают одинаково — чем бы они ни были.
   ---------------------------------------------------------------------------
   Форму рисует Contact Form 7, то есть её владелец: к имени, почте и телефону в
   любой день добавятся дата, выпадающий список или загрузка чертежа. Линию под
   полем получали пять типов из полутора десятков, остальные приходили в том
   виде, в каком их рисует тема, — то есть по-разному на каждом сайте.

   Правила названы по `.wpcf7-form-control` — классу, который CF7 ставит своим
   полям. Без него они дотягивались до чужих: поиску по странам внутри списка
   `cf7-intl-tel-input` доставался наш `padding: 0 0 6px` вместо его отступа под
   иконку, и набранный текст вставал под лупу.

   Здесь перечислено всё, что CF7 умеет вставить, и состояний у них четыре:
   покой, наведение, фокус, ошибка. Цвета — витрины: серая линия в покое, графит
   под курсором, чернила в фокусе, акцент на ошибке. Фокус утолщает линию тенью,
   а не границей: граница сдвинула бы текст на пиксель. */
.estiprint-popup-form input.wpcf7-form-control[type="text"],
.estiprint-popup-form input.wpcf7-form-control[type="email"],
.estiprint-popup-form input.wpcf7-form-control[type="url"],
.estiprint-popup-form input.wpcf7-form-control[type="tel"],
.estiprint-popup-form input.wpcf7-form-control[type="number"],
.estiprint-popup-form input.wpcf7-form-control[type="date"],
.estiprint-popup-form input.wpcf7-form-control[type="time"],
.estiprint-popup-form input.wpcf7-form-control[type="datetime-local"],
.estiprint-popup-form input.wpcf7-form-control[type="month"],
.estiprint-popup-form input.wpcf7-form-control[type="week"],
.estiprint-popup-form input.wpcf7-form-control[type="password"],
.estiprint-popup-form input.wpcf7-form-control[type="search"],
.estiprint-popup-form input.wpcf7-form-control[type="file"],
.estiprint-popup-form textarea.wpcf7-form-control,
.estiprint-popup-form select.wpcf7-form-control {
    width: 100%;
    max-width: 320px;
    font-family: var(--ep-font-family);
    font-size: 16px;
    line-height: 1.35;
    letter-spacing: 0.32px;
    color: var(--ep-color-title);
    border: none;
    border-bottom: 1px solid var(--ep-color-gray);
    border-radius: 0;
    padding: 0 0 6px;
    outline: none;
    background: transparent;
    box-sizing: border-box;
    transition: border-color .15s, box-shadow .15s;
}

.estiprint-popup-form input.wpcf7-form-control[type="text"]:hover:not(:disabled),
.estiprint-popup-form input.wpcf7-form-control[type="email"]:hover:not(:disabled),
.estiprint-popup-form input.wpcf7-form-control[type="url"]:hover:not(:disabled),
.estiprint-popup-form input.wpcf7-form-control[type="tel"]:hover:not(:disabled),
.estiprint-popup-form input.wpcf7-form-control[type="number"]:hover:not(:disabled),
.estiprint-popup-form input.wpcf7-form-control[type="date"]:hover:not(:disabled),
.estiprint-popup-form input.wpcf7-form-control[type="time"]:hover:not(:disabled),
.estiprint-popup-form input.wpcf7-form-control[type="datetime-local"]:hover:not(:disabled),
.estiprint-popup-form input.wpcf7-form-control[type="month"]:hover:not(:disabled),
.estiprint-popup-form input.wpcf7-form-control[type="week"]:hover:not(:disabled),
.estiprint-popup-form input.wpcf7-form-control[type="password"]:hover:not(:disabled),
.estiprint-popup-form input.wpcf7-form-control[type="search"]:hover:not(:disabled),
.estiprint-popup-form input.wpcf7-form-control[type="file"]:hover:not(:disabled),
.estiprint-popup-form textarea.wpcf7-form-control:hover:not(:disabled),
.estiprint-popup-form select.wpcf7-form-control:hover:not(:disabled) {
    border-bottom-color: var(--ep-color-graphite);
}

/* Поле в работе метится подложкой, а не линией: линия у него и так есть, и
   смена её цвета — это разговор шёпотом там, где нужно сказать «вы здесь».
   Подложка и тень — те же, что у выбранной ячейки настроек: «это сейчас
   моё» — один ответ на всю витрину. */
.estiprint-popup-form .wpcf7-form-control:not([type="checkbox"]):not([type="radio"]):not(.wpcf7-submit):focus {
    background: var(--ep-selected-surface);
    box-shadow: var(--ep-selected-shadow);
}

/* У поля свой знак фокуса — линия в два пикселя чернил, — и общая рамка витрины
   на нём была бы второй меткой того же самого: текстовое поле по спецификации
   считается «видимо сфокусированным» и от мыши тоже, так что рамка появлялась
   бы при каждом щелчке. Переключатели рамку сохраняют: линии у них нет. */
.estiprint-popup-form .wpcf7-form-control:not([type="checkbox"]):not([type="radio"]):not(.wpcf7-submit):focus-visible {
    outline: none;
}

/* Недоступное поле остаётся читаемым, но говорит, что его не трогают:
   пунктирная линия и курсор. */
.estiprint-popup-form .wpcf7-form-control:disabled:not(.wpcf7-submit) {
    color: var(--ep-color-placeholder);
    border-bottom-style: dashed;
    cursor: not-allowed;
}

/* Выпадающий список: своя стрелка вместо системной — иначе на каждой платформе
   он выглядит чужим среди полей с линией. Нарисована, а не набрана символом:
   глиф наследует шрифт и уезжает вместе с ним. */
.estiprint-popup-form select.wpcf7-form-control {
    appearance: none;
    -webkit-appearance: none;
    padding-right: 22px;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%234C5359' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0 top 50%;
    background-size: 16px 16px;
}

/* Кнопка выбора файла — тем же языком, что второстепенные кнопки витрины. */
.estiprint-popup-form input.wpcf7-form-control[type="file"] {
    padding-top: 4px;
}

.estiprint-popup-form input.wpcf7-form-control[type="file"]::file-selector-button {
    margin-right: 10px;
    padding: 6px 14px;
    border: 1px solid var(--ep-color-gray);
    border-radius: var(--ep-radius-btn-sm);
    background: #fff;
    font-family: var(--ep-font-family);
    font-size: 13px;
    font-weight: 600;
    color: var(--ep-color-title);
    cursor: pointer;
    transition: border-color .15s, background .15s;
}

.estiprint-popup-form input.wpcf7-form-control[type="file"]:hover::file-selector-button {
    border-color: var(--ep-hover-ink);
    background: var(--ep-hover-surface);
}

.estiprint-popup-form input.wpcf7-form-control[type="tel"].iti-initialized {
    color: var(--ep-color-title);
}

/* Подсказка внутри поля гасится: её текст уже стоит подписью над полем. Только
   у своих: у чужих полей — вроде поиска по странам — она единственная. */
.estiprint-popup-form .wpcf7-form-control::placeholder {
    color: transparent;
}

/* Кроме телефона в работе: там подсказка — это маска по выбранной стране, и
   показывать её есть куда, потому что подпись в этот момент уже наверху. */
.estiprint-floating-field--tel.is-active .wpcf7-form-control::placeholder {
    color: var(--ep-color-placeholder);
}

/* Кнопка страны занимает всю высоту поля, включая его нижний отступ под линию,
   и центрируется по ней — поэтому «+380» стояло на три пикселя ниже набранного
   номера. Тот же отступ на кнопке приводит обе строки к одной середине. */
.estiprint-popup-form .iti__selected-country {
    padding-bottom: 6px;
}

/* Кнопка выбора страны — часть поля телефона, а не кнопка рядом с ним: в фокусе
   она метится тем же, чем поле, — подложкой и тенью. Общая рамка витрины рядом
   с подложкой соседнего поля читалась бы вторым, отдельным элементом.

   Класс чужой, и это тот случай, когда иначе нельзя: свою кнопку внутрь нашего
   поля кладёт `cf7-intl-tel-input`, и назвать её можно только его именем. */
.estiprint-popup-form .iti__selected-country:focus-visible {
    outline: none;
    background: var(--ep-selected-surface);
    box-shadow: var(--ep-selected-shadow);
}

/* Ошибка — на любом типе поля: линия акцентом. Подложка в работе от этого не
   меняется: она говорит, где курсор, а линия — что с полем не так, и это два
   разных сообщения. */
.estiprint-popup-form .wpcf7-not-valid {
    border-bottom-color: var(--ep-color-accent) !important;
}

.estiprint-floating-field.is-active .estiprint-floating-label.has-error {
    color: var(--ep-color-accent);
}

.estiprint-popup-form .wpcf7-not-valid-tip {
    position: absolute;
    left: 0;
    bottom: 0;
    font-family: var(--ep-font-family);
    font-size: 12px;
    line-height: 16px;
    color: var(--ep-color-accent);
    white-space: nowrap;
}

.estiprint-popup-form .wpcf7-response-output {
    display: none !important;
}

.estiprint-popup-message {
    font-family: var(--ep-font-family);
    font-size: 14px;
    line-height: 20px;
    padding: 12px 16px;
    border-radius: var(--ep-radius-btn-sm);
    border-left: 3px solid transparent;
    display: none;
}

.estiprint-popup-message.is-visible {
    display: block;
}

.estiprint-popup-message.is-error {
    background: var(--ep-accent-soft);
    border-left-color: var(--ep-color-accent);
    color: var(--ep-color-accent);
}

.estiprint-popup-message.is-spam {
    background: rgba(255, 152, 0, 0.08);
    border-left-color: #ff9800;
    color: #e65100;
}

.estiprint-popup-message.is-fail {
    background: var(--ep-accent-soft);
    border-left-color: var(--ep-color-accent);
    color: var(--ep-color-accent);
}

.estiprint-popup-form .wpcf7-acceptance {
    display: block;
}

.estiprint-popup-form .wpcf7-list-item {
    margin: 0;
}

.estiprint-popup-form .wpcf7-list-item label {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    font-family: var(--ep-font-family);
    font-size: 16px;
    font-weight: 400;
    line-height: 24px;
    color: var(--ep-color-title);
    cursor: pointer;
}

/* Переключатели — согласие, галочки и радио — одним видом: квадрат и круг
   отличаются только скруглением. Раньше был описан один флажок согласия, а
   «чекбокс» и «радио», которые CF7 вставляет теми же двумя кликами, приходили
   системными — рядом с полями в линию они выглядели из другой формы. */
.estiprint-popup-form input[type="checkbox"],
.estiprint-popup-form input[type="radio"] {
    appearance: none;
    -webkit-appearance: none;
    width: 18px;
    height: 18px;
    min-width: 18px;
    border: 2px solid var(--ep-color-title);
    border-radius: 3px;
    cursor: pointer;
    position: relative;
    margin: 2.5px 0 0;
    background: transparent;
    transition: background 0.2s ease, border-color 0.2s ease;
    flex-shrink: 0;
}

.estiprint-popup-form input[type="radio"] {
    border-radius: 50%;
}

.estiprint-popup-form input[type="checkbox"]:hover:not(:disabled),
.estiprint-popup-form input[type="radio"]:hover:not(:disabled) {
    background: var(--ep-hover-surface);
}

.estiprint-popup-form input[type="checkbox"]:checked,
.estiprint-popup-form input[type="radio"]:checked {
    background: var(--ep-color-accent);
    border-color: var(--ep-color-accent);
}

/* Галочка внутри квадрата и точка внутри круга — та же метка, разной формы. */
.estiprint-popup-form input[type="checkbox"]:checked::after {
    content: '';
    position: absolute;
    left: 4px;
    top: 1px;
    width: 4px;
    height: 9px;
    border: solid #fff;
    border-width: 0 2px 2px 0;
    transform: rotate(45deg);
}

.estiprint-popup-form input[type="radio"]:checked::after {
    content: '';
    position: absolute;
    inset: 3px;
    border-radius: 50%;
    background: #fff;
}

.estiprint-popup-form input[type="checkbox"]:disabled,
.estiprint-popup-form input[type="radio"]:disabled {
    border-color: var(--ep-color-disabled);
    background: var(--ep-color-light-gray);
    cursor: not-allowed;
}

/* Ошибка на группе переключателей: CF7 метит ею обёртку, а не сам флажок. */
.estiprint-popup-form .wpcf7-not-valid input[type="checkbox"],
.estiprint-popup-form .wpcf7-not-valid input[type="radio"],
.estiprint-popup-form input[type="checkbox"].wpcf7-not-valid,
.estiprint-popup-form input[type="radio"].wpcf7-not-valid {
    border-color: var(--ep-color-accent);
}

/* Обёртка держит кружок ожидания поверх кнопки, поэтому меряется по ней:
   `inline-flex` сжимал её по тексту, и кнопка «шириной с поля» получала 177
   пикселей вместо 320. */
.estiprint-popup-submit-wrap {
    position: relative;
    display: flex;
    width: 100%;
    max-width: 320px;
    margin-top: 30px;
}

/* Кнопка формы шириной с поля над ней: это столбец одной ширины, и кнопка в
   нём — его последняя строка. Всё остальное — рост, шрифт, скругление, цвета
   состояний — общим правилом выше. */
.estiprint-popup-form .wpcf7-submit {
    width: 100%;
    max-width: 320px;
    margin-top: 6px;
}

.estiprint-popup-form .wpcf7-spinner {
    position: absolute !important;
    inset: 0;
    display: flex;
    justify-content: center;
    align-items: center;
    width: 100% !important;
    height: 100% !important;
    margin: 0 !important;
    padding: 0;
    background: var(--ep-color-accent);
    border-radius: var(--ep-radius-btn-sm);
    visibility: hidden;
    opacity: 0;
    transition: opacity 0.25s ease, visibility 0.25s ease;
}

.estiprint-popup-form .wpcf7-spinner::before {
    content: '';
    position: static;
    width: 24px;
    height: 24px;
    border: 3px solid rgba(255, 255, 255, 0.3);
    border-top-color: #fff;
    border-radius: 50%;
    animation: ep-spin 0.6s linear infinite;
    background: none !important;
    transform-origin: center;
}

.estiprint-popup-form .wpcf7-spinner.active {
    visibility: visible;
    opacity: 1;
}

.estiprint-popup-success-content {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 20px;
    text-align: center;
}

/* ============================================================
   Infill preset selector
   ============================================================ */
.estiprint-calculator-infill-wrap {
    padding: 16px 20px;
    border-radius: var(--ep-radius-field);
    background: var(--ep-color-light-gray, #f5f5f5);
    margin-bottom: 4px;
}

/* Подпись и ⓘ рядом, как в заголовке любого другого шага. `space-between`
   стоял здесь ради значка с процентом справа; значка больше нет, и без правки
   он растащил бы подпись и подсказку по краям. */
.estiprint-infill-header {
    display: flex;
    align-items: center;
    gap: 7px;
    margin-bottom: 12px;
}

.estiprint-infill-label {
    font-size: 14px;
    font-weight: 600;
    line-height: 20px;
    color: #000;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

#ep-infill-presets {
    display: flex;
    flex-wrap: wrap;
}

.estiprint-infill-btn {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    flex: 1 1 0;
    min-width: 56px;
    padding: 8px 6px;
    cursor: pointer;
    text-align: center;
}

.estiprint-infill-btn b {
    font-size: 15px;
    font-weight: 700;
    line-height: 20px;
    color: #000;
}

.estiprint-infill-btn span {
    font-size: 11px;
    font-weight: 400;
    line-height: 14px;
    color: #666;
}

/* Выбранная плотность — общим правилом выше. */

.estiprint-infill-btn.is-active b {
    color: var(--ep-ink);
}

@media (max-width: 768px) {
    .estiprint-popup-body {
        flex-direction: column;
        gap: 30px;
    }

    .estiprint-popup-info {
        max-width: 100%;
    }

    .estiprint-popup-dialog {
        padding: 50px 24px 40px;
    }

    .estiprint-popup-close {
        right: 16px;
        top: 16px;
    }

    .estiprint-popup-title {
        font-size: 24px;
        line-height: 30px;
    }

    /* На узкой колонке поле занимает её целиком — своё поле, не чужое: у
       поиска по странам своя ширина внутри списка. */
    .estiprint-popup-form .wpcf7-form-control:not(.wpcf7-submit) {
        max-width: 100%;
        width: 100%;
    }

    .estiprint-popup-submit-wrap,
    .estiprint-popup-form .wpcf7-submit {
        width: 100%;
    }
}
/* ==========================================================================
   Server-side model check (volume + soft integrity warnings)
   ========================================================================== */
/* Строка проверки модели по макету `docs/design/Main.dc.html`: пилюля и
   предложение рядом с ней. Рамка с цветным фоном ушла — цвет несёт пилюля, а
   коробка вокруг одной строки делала из проверки уведомление. */
.estiprint-model-warnings {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 9px;
    margin: 0 0 12px;
    padding: 12px 0;
    font-family: var(--ep-font-family);
    font-size: 13px;
    line-height: 1.5;
    color: var(--ep-ink-2, var(--ep-color-graphite));
}

.estiprint-model-warnings-text {
    min-width: 0;
}

.estiprint-model-warnings-list {
    flex: 1 1 100%;
    margin: 0;
    padding-left: 18px;
    list-style: disc;
}

.estiprint-model-warnings-item {
    margin: 2px 0;
}

/* ==========================================================================
   Multi-model cart
   ========================================================================== */
.estiprint-cart {
    margin-top: 8px;
    padding: 16px 20px;
    border: 1px solid var(--ep-color-gray);
    border-radius: var(--ep-radius-card);
    background: #fff;
    font-family: var(--ep-font-family);
}

.estiprint-cart-title {
    font-size: 15px;
    font-weight: 700;
    color: var(--ep-color-title);
    margin-bottom: 10px;
}

.estiprint-cart-list {
    list-style: none;
    margin: 0;
    padding: 0;
}

/* Строка модели по макету `docs/design/Main.dc.html`: три яруса в одной строке —
   имя и цена, габариты и цена за штуку, чипы конфигурации и действия. Так
   заказчик видит, что выбрано, не открывая модель: у сорока деталей открывать
   каждую и есть та работа, которую раскладка снимает. */
.estiprint-cart-item {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto auto;
    grid-template-areas:
        "pick state state   state"
        "pick name  qty     price"
        "pick dims  dims    perpart"
        "pick chips chips   acts";
    align-items: center;
    column-gap: 12px;
    row-gap: 1px;
    padding: 8px 12px;
    border-radius: var(--ep-r-sm);
    line-height: 1.25;
    border-bottom: 1px solid var(--ep-line);
    font-size: 14px;
    color: var(--ep-ink-2);
}

/* Строка сама открывает свою модель, поэтому выглядит нажимаемой. Кнопка
   «Open» в углу просила прицелиться в одиннадцать пикселей текста; места
   внутри строки, у которых своё дело, курсор себе оставляют сами. */
.estiprint-cart-item {
    cursor: pointer;
}

.estiprint-cart-item.is-active {
    cursor: default;
}

.estiprint-cart-item:not(.is-active):hover {
    background: var(--ep-hover-surface);
}

/* Строка попадает в обход с клавиатуры, и видно, где он сейчас. */
/* Выбранную строку метит полоса слева на нейтральном фоне. Красная подложка
   под выбором читалась бы ошибкой — акцент витрины это цвет действия. */
/* Открытую строку метит подложка и метка «editing» в углу. Полосу слева она
   больше не забирает: полоса говорит о модели, а не о том, какая строка сейчас
   на холсте. */
.estiprint-cart-item.is-active {
    background: var(--ep-surface-alt);
}

.estiprint-cart-item-pick {
    grid-area: pick;
    align-self: start;
    display: flex;
    align-items: center;
    padding-top: 2px;
    cursor: pointer;
}

/* Межстрочие 1.2, а не 1.25 всей строки: три яруса на сорок моделей — это
   высота экрана на каждые восемь. */
/* Пилюля состояния — своим ярусом над именем: она про модель целиком, и её
   читают раньше, чем материал и цену. В ряду чипов конфигурации она стояла
   четвёртой среди «MJF · PA 12 · Raw Grey» и там читалась ещё одним свойством
   печати, а не состоянием детали. Рядом — ⓘ с тем, что нашла проверка. */
.estiprint-cart-item-state {
    grid-area: state;
    display: flex;
    align-items: center;
    gap: 2px;
    min-width: 0;
    margin-bottom: 2px;
}

/* Своим ярусом пилюля начинает строку, и отступ слева ей больше не нужен —
   он остаётся у той, что стоит в правой панели рядом с текстом. */
.estiprint-cart-item-state .estiprint-cart-state {
    margin-left: 0;
}

/* Метка открытой строки — у правого края того же яруса. */
.estiprint-cart-item-state .estiprint-cart-item-editing {
    margin-left: auto;
}

.estiprint-cart-item-name {
    grid-area: name;
    overflow: hidden;
    font-size: 14px;
    font-weight: 600;
    line-height: 1.2;
    color: var(--ep-ink);
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Габариты живут в строке с именем — как у обоих референсов, а не в отдельной
   панели проверки, куда за ними надо идти. */
.estiprint-cart-item-dims {
    grid-area: dims;
    display: block;
    font-size: 11px;
    line-height: 1.2;
    color: var(--ep-ink-3);
}

.estiprint-cart-item-meta {
    grid-area: chips;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    min-width: 0;
    gap: 3px 4px;
    margin-top: 2px;
    font-size: 13px;
    color: var(--ep-ink-3);
}

/* Действия — третьим ярусом справа, одним рядом: количество, цены, дубль,
   открыть, удалить. Ссылками, а не кнопками: их пять, и пять кнопок в ряд
   весят больше самой строки. */
.estiprint-cart-item-acts {
    grid-area: acts;
    flex: none;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 10px;
    margin-top: 2px;
}

/* Рост у всех один: «prices», «⧉», «Open» и «×» стояли на 14, 14, 26 и 24
   пикселях, и ряд читался неровным. Флексом, а не строкой: голая кнопка сидит
   на базовой линии своей строки даже в сетке с центровкой. */
.estiprint-cart-prices {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: var(--ep-h-row);
    padding: 0;
    border: none;
    border-radius: var(--ep-radius-btn-sm);
    background: none;
    font-family: var(--ep-font);
    font-size: 11px;
    line-height: 1.2;
    color: var(--ep-ink-3);
    text-decoration: underline;
    text-underline-offset: 2px;
    cursor: pointer;
}

.estiprint-cart-prices.is-open {
    color: var(--ep-color-graphite);
}

.estiprint-cart-prices:hover {
    color: var(--ep-accent);
}

.estiprint-cart-item-editing {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: var(--ep-h-row);
    padding: 0;
    border: none;
    border-radius: var(--ep-radius-btn-sm);
    background: none;
    font-family: var(--ep-font);
    font-size: 11px;
    font-style: italic;
    line-height: 1.2;
    color: var(--ep-accent);
    text-decoration: none;
    white-space: nowrap;
}

.estiprint-cart-duplicate {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--ep-h-row);
    min-width: var(--ep-h-row);
    min-height: var(--ep-h-row);
    padding: 0 3px;
    border: none;
    border-radius: var(--ep-radius-btn-sm);
    background: none;
    font-size: 14px;
    line-height: 1;
    color: var(--ep-ink-3);
    cursor: pointer;
}

.estiprint-cart-duplicate:hover {
    color: var(--ep-hover-ink);
    background: var(--ep-hover-surface);
}

.estiprint-cart-remove {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--ep-h-row);
    min-width: var(--ep-h-row);
    height: var(--ep-h-row);
    min-height: var(--ep-h-row);
    padding: 0 3px;
    border: none;
    border-radius: 50%;
    background: none;
    font-size: 14px;
    line-height: 1;
    color: var(--ep-ink-3);
    cursor: pointer;
    transition: color .15s, background .15s;
}

/* Удаление — единственное необратимое действие ряда, и метится оно как
   опасное: акцент на своей бледной подложке. */
.estiprint-cart-remove:hover {
    color: var(--ep-accent);
    background: var(--ep-hover-danger);
}

/* Обводкой по `currentColor`, как в админке: иконка идёт за цветом кнопки, в
   том числе при наведении. Размер — 15 против админских 18: там строка таблицы
   высотой 30 пикселей, здесь ряд действий в 26. */
.estiprint-cart-remove svg {
    width: 15px;
    height: 15px;
    stroke: currentColor;
    fill: none;
}

/* Степпер: нажать «+» быстрее, чем выделить число и набрать новое, а на сорока
   деталях это разница в сотню действий. Поле остаётся — для «поставить 25». */
/* Степпер — одна деталь, а не три: у поля свои боковые границы, и зазор в
   шесть пикселей оставлял их висеть в воздухе по обе стороны от числа. Кнопки
   стоят вплотную к этим границам, и рамка вокруг читается одной кнопкой
   количества. */
.estiprint-cart-item-qty {
    grid-area: qty;
    justify-self: end;
    display: flex;
    align-items: center;
    margin-right: 12px;
    border: 1px solid var(--ep-line-strong);
    border-radius: var(--ep-r-sm);
    overflow: hidden;
    background: #fff;
    font-size: 13px;
    color: var(--ep-color-placeholder);
}

/* Двадцать два пикселя, а не двадцать шесть: степпер и действия задавали высоту
   двух ярусов из трёх, и это десять пикселей со строки — целый экран на сорока
   моделях. Ниже опускаться некуда: цель нажатия и так на нижней границе. */
.estiprint-cart-qty-step {
    width: 22px;
    height: 22px;
    border: none;
    background: none;
    color: var(--ep-ink-2);
    font-size: 14px;
    line-height: 1;
    cursor: pointer;
    font-family: var(--ep-font);
}

/* Нейтральное действие: подложка и чернила. Акцент здесь читался бы
   предупреждением — «плюс» ничего не портит. */
.estiprint-cart-qty-step:hover:not(:disabled) {
    background: var(--ep-hover-surface);
    color: var(--ep-hover-ink);
}

.estiprint-cart-qty-step:disabled {
    color: var(--ep-color-disabled);
    cursor: default;
}

.estiprint-cart-item-qty-input {
    width: 32px;
    height: 22px;
    padding: 0;
    border: none;
    border-left: 1px solid var(--ep-line);
    border-right: 1px solid var(--ep-line);
    border-radius: 0;
    background: #fff;
    text-align: center;
    font-family: var(--ep-font);
    font-size: 13px;
    color: var(--ep-ink);
    -moz-appearance: textfield;
    appearance: textfield;
}

.estiprint-cart-item-qty-input::-webkit-outer-spin-button,
.estiprint-cart-item-qty-input::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}

.estiprint-cart-item-price {
    grid-area: price;
    font-size: 15px;
    font-weight: 600;
    color: var(--ep-ink);
    text-align: right;
    white-space: nowrap;
}

/* Цена за штуку — то, что заказчик платит за деталь. Показывается только когда
   тираж больше одного: при единице она равна сумме и ничего не добавляет. */
.estiprint-cart-item-perpart {
    grid-area: perpart;
    display: block;
    font-size: 11px;
    font-weight: 400;
    color: var(--ep-ink-3);
    text-align: right;
    white-space: nowrap;
}

/* Итог по макету: каждая строка отдельно, подпись слева, число справа. Раньше
   строки были инлайновыми и слипались в «Нова пошта in 3 days120 ₴VAT 20%480 ₴
   Total for 3 models: 2883 ₴» — прочитать это можно было только зная, что там
   написано. */
/* Разбор итога — таблицей, языком админских списков.
   ---------------------------------------------------------------------------
   Строки без разлиновки читались абзацем, и глаз шёл по ним слева направо, а
   не сверху вниз по числам. Здесь у каждой строки своя чётность и своя черта,
   а числа стоят одной колонкой, как в `.ep-table` настроек. */
.estiprint-cart-total {
    display: flex;
    flex-direction: column;
    gap: 0;
    margin-top: 14px;
    border: 1px solid var(--ep-line);
    border-radius: var(--ep-radius-btn-sm);
    overflow: hidden;
    /* Белая подложка: сама полоса серая, и без неё чётная строка выходила
       светлее нечётной — зебра наизнанку. */
    background: #fff;
    font-size: 13px;
    color: var(--ep-ink-2);
}

.estiprint-cart-total-line {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 16px;
    padding: 7px 12px;
}

/* Чётная строка подсвечена, как в таблицах настроек: на пяти строках подряд
   это единственное, что не даёт съехать взглядом на соседнюю сумму. */
.estiprint-cart-total-line:nth-child(even) {
    background: var(--ep-surface-alt);
}

.estiprint-cart-total-line + .estiprint-cart-total-line {
    border-top: 1px solid var(--ep-line);
}

/* Числа — одной колонкой и одной шириной цифр: их сравнивают между собой, а
   пропорциональные знаки эту колонку разваливают. */
.estiprint-cart-total-line > em {
    flex: none;
    font-style: normal;
    font-variant-numeric: tabular-nums;
    color: var(--ep-ink);
}

.estiprint-cart-total-line > span:first-child {
    color: var(--ep-ink-3);
}

/* Добор до минимума — не строка расходов, а объяснение: без неё цена растёт
   молча, и это читается ошибкой расчёта (ADR 0015). */
.estiprint-cart-total-line--topup > span:first-child {
    color: var(--ep-accent);
}

/* Налог, уже сидящий в цене: строка разбора, но не слагаемое итога — сумма
   приглушена так же, как подпись. */
.estiprint-cart-total-line--note > em {
    color: var(--ep-ink-3);
}

/* Правила про `.estiprint-cart-total-sum` и `<b>` внутри разбора убраны: сам
   итог печатается один раз, крупно, в правой колонке полосы, а в разборе его
   больше нет. */


/* --------------------------------------------------------------------------
 * Cart lines: created on upload, so a line can exist before it is configured.
 * ------------------------------------------------------------------------ */

/* Выбранная строка описана выше, вместе с остальной раскладкой строки. Здесь
   было второе такое правило — оно же сдвигало текст выбранной строки на 4px
   относительно соседей, и при переключении моделей список дёргался. */

/* Rare now, and loud when it happens: a line reaches this state only if the
   catalogue offered nothing to default to, which is a studio misconfiguration
   rather than something the customer forgot. Submission is still blocked, so
   the line holding it up must be findable. */
.estiprint-cart-item.is-incomplete .estiprint-cart-item-name {
    color: var(--ep-color-placeholder);
}

.estiprint-cart-item-todo {
    color: #96601a;
    font-weight: 600;
}

/* The configuration of a line, readable without opening the model. This is the
   guard that replaced the ban on sending an unconfigured line: the material is
   always set now, so what protects the customer is seeing which one. */
.estiprint-cart-chip {
    display: inline-block;
    padding: 2px 7px;
    margin-right: 4px;
    border: 1px solid var(--ep-line);
    border-radius: 4px;
    background: #fff;
    font-size: 11px;
    line-height: 1.45;
    color: var(--ep-ink-2);
    white-space: nowrap;
}

/* Dashed: a catalogue default nobody chose. Quiet enough not to read as an
   error, visible enough to be noticed before the request goes. */
.estiprint-cart-chip.is-default {
    border-style: dashed;
    color: var(--ep-color-placeholder);
}

/* Says which line is not ready, next to the button it disables. Wears the same
   treatment as the popup's error messages — tinted ground, accent left edge,
   accent text — because it reports the same kind of thing: something must be
   dealt with before this can be sent. */
/* Причина, по которой кнопка не нажимается. Артборд `NeedsAction`: полоса на
   всю ширину над полосой итога, а не подпись сбоку от цены — сбоку её принимают
   за пояснение к цене и не читают. */
.estiprint-calculator-notice {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 0;
    padding: 10px 16px;
    font-size: 13px;
    font-weight: 600;
    line-height: 1.4;
    color: var(--ep-color-accent);
    background: var(--ep-accent-soft);
    border-left: 3px solid var(--ep-color-accent);
    border-radius: var(--ep-radius-field, 8px);
}

.estiprint-calculator-notice::before {
    content: '';
    flex: none;
    width: 24px;
    height: 24px;
    background-color: currentColor;
    -webkit-mask: var(--ep-warning-icon) center / contain no-repeat;
    mask: var(--ep-warning-icon) center / contain no-repeat;
}


/* Групповые операции над строками заявки (#13). Панель появляется со второй
   модели: на одной она была бы шумом. */
.estiprint-cart-tools {
    display: flex;
    align-items: center;
    gap: 12px;
    margin: 4px 0 8px;
    font-size: 13px;
}

/* Флажок «Select all» стоит в одном ряду с кнопками и должен стоять по центру,
   а не на базовой линии своей строки. */
.estiprint-cart-tools-all {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    min-height: var(--ep-h-sm);
    cursor: pointer;
    color: var(--ep-color-graphite);
}

.estiprint-cart-tools button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: var(--ep-h-sm);
    padding: 4px 12px;
    border: 1px solid var(--ep-color-light-gray);
    border-radius: var(--ep-radius-btn-sm);
    background: #fff;
    font: inherit;
    color: var(--ep-color-graphite);
    cursor: pointer;
}

/* Флажок «Select all» стои́т в одном ряду с ними и должен стоять по центру, а не
   на базовой линии своей строки. */
.estiprint-cart-tools button:hover:not(:disabled) {
    border-color: var(--ep-color-graphite);
}

.estiprint-cart-tools button:disabled {
    opacity: .45;
    cursor: default;
}

/* Таблица цен по тиражам под строкой заявки (#5, этап 1). Свёрнута по
   умолчанию: сорок развёрнутых таблиц — стена цифр, а не помощь. */
.estiprint-cart-prices-panel {
    grid-column: 1 / -1;
    margin: 4px 0 8px;
    padding: 8px 10px;
    border-radius: var(--ep-radius-btn-sm);
    background: var(--ep-surface-alt);
}

.estiprint-price-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 12px;
}

.estiprint-price-table th {
    padding: 2px 8px 4px 0;
    font-weight: 500;
    color: var(--ep-ink-3);
    text-align: left;
}

.estiprint-price-table td {
    padding: 3px 8px 3px 0;
    color: var(--ep-color-graphite);
}

/* Тираж, который выбран сейчас, — точка отсчёта для взгляда. */
.estiprint-price-table tr.is-current td {
    font-weight: 700;
}

.estiprint-price-table-note {
    margin: 6px 0 0;
    font-size: 11px;
    color: var(--ep-ink-3);
}


/* ---------------------------------------------------------------------------
   Раскладка 1.4.0: слева собирается заявка, справа настраивается выбранная
   модель. До этого слева был просмотрщик, а список заявки лежал под сеткой,
   из-за чего деталь, которую настраиваешь, жила не в списке, а над ним.
   --------------------------------------------------------------------------- */

/* Зона загрузки больше не единственное, что есть в колонке, — она уступает
   место списку. Огромная иконка была уместна на пустом экране и неуместна
   над сорока строками. */
.estiprint-request-col .estiprint-calculator-uploader-label {
    gap: 14px;
    padding: 22px 0 20px;
}

.estiprint-request-col .estiprint-calculator-uploader-icon-wrap {
    width: 96px;
    height: 64px;
}

/* Список заявки идёт сразу под зоной: она — то, из чего он растёт. */
.estiprint-request-col .estiprint-cart {
    margin-top: 14px;
}

/* Правая колонка — про одну модель: просмотрщик, её размеры, её настройки. */
.estiprint-config-col .estiprint-calculator-viewer-col-inner {
    margin-bottom: 16px;
}


/* "Upload another model": frees the canvas for the next file. Visually the same
   affordance the old add-to-cart button had, since it sits in the same place. */
.estiprint-calculator-next-model {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    min-height: var(--ep-h-md);
    margin-top: 4px;
    padding: 10px 18px;
    text-align: center;
    font-family: var(--ep-font-family);
    font-size: 14px;
    font-weight: 600;
    color: var(--ep-color-accent);
    background: transparent;
    border: 1px solid var(--ep-color-accent);
    border-radius: var(--ep-radius-btn-sm);
    cursor: pointer;
    transition: color .15s, background .15s, border-color .15s;
}

.estiprint-calculator-next-model:hover:not(:disabled) {
    color: #fff;
    background: var(--ep-color-accent);
}

.estiprint-calculator-next-model:disabled {
    opacity: .5;
    cursor: not-allowed;
}


/* Stands in for the canvas when a saved line is reopened after a reload: the
   file is on the server, the browser has nothing to draw. Same footprint as the
   canvas so the layout does not jump. */
/* Размер и фон теперь у сцены; здесь остаётся только то, чем эта записка
   отличается от соседних, — пунктир вокруг. */
.estiprint-calculator-viewer-nopreview {
    font-size: 14px;
    background: #f7f8f9;
    border: 1px dashed var(--ep-color-light-gray);
    border-radius: 12px;
}

/* ============================================================
   Подсказки — значок ⓘ и то, что он открывает

   Описание элемента каталога и объяснение шага живут здесь, а не
   абзацем в строке: список из шести материалов с абзацем под каждым
   перестаёт быть выбором и становится документацией. Значок держит
   экран сканируемым, а ответ — на расстоянии одного нажатия.
   ============================================================ */

.estiprint-hint {
    position: relative;
    display: inline-flex;
    vertical-align: middle;
    margin-left: 6px;
}

.estiprint-hint-btn {
    width: 18px;
    height: 18px;
    padding: 0;
    border: 1px solid currentColor;
    border-radius: 50%;
    background: transparent;
    color: #6b6b6b;
    font: 600 11px/16px inherit;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
}

.estiprint-hint-btn:hover,
.estiprint-hint-btn[aria-expanded="true"] {
    color: var(--ep-hover-ink);
    background: var(--ep-hover-surface);
}

/* Перечисление внутри подсказки: пункты того, что проверка нашла у модели. */
.estiprint-hint-list {
    margin: 0;
    padding-left: 16px;
    list-style: disc;
}

.estiprint-hint-list li {
    margin: 3px 0;
}

/* Окно подсказки. Место ему считает `hintPlacement()` и ставит числами: CSS не
   знает ни где край экрана, ни сколько места осталось снизу, а подсказка стоит
   и в углу плитки, и в нижней полосе, и внутри всплывающего окна. Отсюда
   `position: fixed` без координат — они приходят из JS, — и предельная высота
   оттуда же, с прокруткой внутри: обрезанное окно с прокруткой показывает весь
   текст, а вылезшее за край — только начало. */
.estiprint-hint-pop {
    position: fixed;
    z-index: var(--ep-z-pop);
    width: max-content;
    max-width: min(320px, calc(100vw - 24px));
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: 10px 12px;
    border: 1px solid #d9d9de;
    border-radius: var(--ep-radius-btn-sm);
    background: #fff;
    box-shadow: var(--ep-shadow-pop);
    font-size: 13px;
    font-weight: 400;
    line-height: 1.5;
    color: #171717;
    text-align: left;
    white-space: normal;
    cursor: auto;
}

/* Картинка в подсказке — во всю её ширину, над текстом: подписи к изображению
   здесь нет, объяснение и есть текст под ним. Высота ограничена, чтобы
   вертикальный снимок не превратил окно в ленту: остальное доберёт прокрутка. */
.estiprint-hint-image {
    display: block;
    width: calc(100% + 24px);
    max-height: 180px;
    margin: -10px -12px 8px;
    /* Целиком, а не обрезком: картинку в подсказке ставят, чтобы показать её
       всю, — схему, образец поверхности, пример детали. */
    object-fit: contain;
    border-radius: var(--ep-radius-btn-sm) var(--ep-radius-btn-sm) 0 0;
    background: var(--ep-surface-alt);
}

/* Правило про узкий экран отсюда ушло: оно растягивало подсказку во всю ширину
   от края до края, потому что CSS не знал, помещается ли она рядом со значком.
   Теперь это знает `hintPlacement()`, и на телефоне окно такое же, как на
   мониторе, — просто прижатое к тому краю, где есть место. */

/* ============================================================
   Материал, которого нет ни на одной подходящей машине

   Гасим, а не прячем: исчезнувший материал читается как «у них его
   нет», погашенный — как «не для этой детали». Причина открывается
   тем же значком.
   ============================================================ */

/* Пунктиром на сером, как в макете: погашенная плитка отличается от обычной не
   только бледностью — иначе на телефоне это читается просто плохой печатью.
   С весом плитки, иначе её `cursor: pointer` перебивал запрет. */
#ep-material-options .estiprint-material-item.is-unavailable {
    opacity: .5;
    border-style: dashed;
    border-color: var(--ep-line-strong);
    background: var(--ep-surface-alt);
    cursor: not-allowed;
}

.estiprint-material-item.is-unavailable .estiprint-hint-btn {
    cursor: pointer;
    opacity: 1;
}

/* ============================================================
   Полоса выбора срока

   Устройство Weerg: колонка на срок, в ней дата и цена, выбранная
   обведена. Цена падает вправо — чем дольше ждать, тем дешевле.
   Один срок — не выбор, а обещание, и полоса схлопывается в строку.
   ============================================================ */

/* Полоса срока — переключатель, а не подпись.
   ---------------------------------------------------------------------------
   Раньше три ячейки в тонкой рамке читались таблицей: сроки выглядели
   справкой о ценах, а не выбором, который делают. Здесь у неё своя подложка,
   ячейки приподняты над ней, и выбранная — карточка с акцентной обводкой. */
.estiprint-lead {
    display: flex;
    gap: 4px;
    margin: 0 0 12px;
    padding: 4px;
    border: 1px solid var(--ep-line);
    border-radius: var(--ep-radius-field);
    /* Корыто белое по той же причине, что и подложка таблицы: полоса серая, и
       на ней невыбранные ячейки должны быть светлее, а не темнее. */
    background: #fff;
    overflow: visible;
}

.estiprint-lead-option {
    flex: 1;
    min-width: 0;
    padding: 9px 12px;
    /* Рамка в покое — та же, что у ячеек настроек: «как у сроков» должно
       оставаться правдой и в обратную сторону. */
    border: 1.5px solid var(--ep-line);
    border-radius: var(--ep-radius-btn-sm);
    background: transparent;
    font: inherit;
    text-align: center;
    cursor: pointer;
    color: inherit;
    transition: background .15s, border-color .15s, color .15s;
}

/* Наведение говорит, что по ячейке можно нажать, — до того как по ней нажали. */
.estiprint-lead-option:hover:not(.is-selected) {
    background: var(--ep-hover-surface);
    border-color: var(--ep-hover-line);
}

.estiprint-lead-option.is-selected {
    border-color: var(--ep-selected-line);
    background: var(--ep-selected-surface);
    box-shadow: var(--ep-selected-shadow);
}

/* Выбранный срок называет себя ценой, а не серостью соседей: у остальных цена
   приглушена, у него — полного веса и цвета чернил. */
.estiprint-lead-option.is-selected .estiprint-lead-price {
    color: var(--ep-ink);
}

.estiprint-lead-option.is-selected .estiprint-lead-when {
    color: var(--ep-selected-line);
    font-weight: 600;
}

/* Подпись переносится, а не обрезается: «Standard · Sep 14» в узкой колонке
   превращалось в «Standard · Se…» — пропадала дата, ради которой на срок и
   смотрят, а многоточие делало вид, что там ничего важного. */
/* Подпись переносится, а не обрезается, и держит два ряда всегда: иначе
   «Standard · Sep 14» уезжало на вторую строку, «Fast · Sep 11» оставалось на
   одной, и три цены под ними стояли на разной высоте. */
.estiprint-lead-when {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 2.5em;
    font-size: 11px;
    line-height: 1.25;
    color: #8c8c8c;
    white-space: normal;
    overflow-wrap: anywhere;
}

.estiprint-lead-price {
    display: block;
    margin-top: 2px;
    font-size: 16px;
    font-weight: 700;
    letter-spacing: -.01em;
    color: var(--ep-ink-2);
    /* Цифры одной ширины: три цены стоят столбиками и сравниваются взглядом,
       а пропорциональные «1» и «8» разъезжают их между собой. */
    font-variant-numeric: tabular-nums;
}

.estiprint-lead--single {
    display: block;
    padding: 9px 12px;
    font-size: 13px;
    color: #5a5a5a;
    text-align: center;
}

/* ============================================================
   Итог группового применения

   Об успехе не сообщаем — он виден в списке. Сообщаем о том, что
   невидимо: строка, которую пропустили, выглядит просто
   ненастроенной, и отличить «не применилось» от «забыл» нельзя.
   ============================================================ */

.estiprint-cart-notice {
    margin: 0 0 12px;
    padding: 9px 12px;
    border-radius: var(--ep-radius-btn-sm);
    background: #f3f4fb;
    font-size: 13px;
    line-height: 1.5;
    color: #171717;
}

.estiprint-cart-notice--warn {
    background: #fdeef1;
    border: 1px solid #f3c4cd;
}

/* ============================================================
   Раскладка шагов: плитки и свотчи вместо списка

   На макетах технология — ряд пилюль, материалы — сетка плиток,
   цвета — ряд образцов. Смысл не в красоте: пока выбор был
   вертикальным списком внутри раскрывающегося шага, каждая
   настройка стоила лишнего клика и прокрутки, и это ровно то,
   что мерилось в замечании №16.
   ============================================================ */

.estiprint-calculator-option-list {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

/* Отделка разбита на группы по макету `docs/design/Finishing.dc.html`: то, чем
   деталь выходит из машины, и то, что делают с ней после. */
.estiprint-color-group {
    display: flex;
    flex-direction: column;
    gap: 7px;
}

/* Подпись группы — тихой строкой, а не заголовком: заголовок у блока уже
   есть, «FINISHING» над ним. */
.estiprint-color-group-label {
    display: block;
    font-size: 11px;
    color: var(--ep-ink-3);
}

.estiprint-color-swatches {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

/* Счётчик количества в панели — тот же степпер, что в строке заявки, только
   крупнее: здесь его правят вместе с остальными настройками открытой модели,
   а не мимоходом в списке. Артборд `Main`, блок `Quantity`. */
.estiprint-calculator-option-quantity {
    display: inline-flex;
    align-items: center;
    border: 1px solid var(--ep-line-strong);
    border-radius: var(--ep-radius-btn-sm);
    overflow: hidden;
    background: #fff;
}

.estiprint-calculator-option-quantity button {
    width: var(--ep-h-sm);
    height: var(--ep-h-sm);
    border: none;
    background: none;
    color: var(--ep-ink-2);
    font-family: var(--ep-font);
    font-size: 16px;
    line-height: 1;
    cursor: pointer;
    transition: background .15s, color .15s;
}

.estiprint-calculator-option-quantity button:hover {
    background: var(--ep-hover-surface);
    color: var(--ep-hover-ink);
}

.estiprint-calculator-option-quantity input {
    width: 56px;
    height: var(--ep-h-sm);
    padding: 0;
    border: none;
    border-left: 1px solid var(--ep-line);
    border-right: 1px solid var(--ep-line);
    border-radius: 0;
    text-align: center;
    font-family: var(--ep-font);
    font-size: 15px;
    font-weight: 600;
    color: var(--ep-ink);
    -moz-appearance: textfield;
    appearance: textfield;
}

.estiprint-calculator-option-quantity input::-webkit-outer-spin-button,
.estiprint-calculator-option-quantity input::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}

/* Услуги модели — по макету `docs/design/Main.dc.html`, блок `Services`:
   каждая своей карточкой — название, под ним зачем это, цена справа и
   переключатель. Строки с чертой между ними читались продолжением списка
   материалов, а не отдельным решением о деньгах. */
.estiprint-service-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.estiprint-service {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 9px 12px;
    font-size: 13px;
    line-height: 1.4;
}

.estiprint-service-text {
    display: flex;
    flex-direction: column;
    gap: 2px;
    flex: 1;
    min-width: 0;
}

.estiprint-service-name {
    font-weight: 400;
    color: var(--ep-ink);
}

/* Зачем это нужно — под названием и мельче: услуга, названная одним словом,
   покупается наугад. */
.estiprint-service-why {
    font-size: 11px;
    color: var(--ep-ink-3);
}

.estiprint-service-price {
    flex: none;
    margin-left: auto;
    font-size: 12px;
    color: var(--ep-ink-2);
    white-space: nowrap;
}

/* Переключатель мелкий и тёмный, как в макете: включённая услуга — решение,
   а не тревога, и красить его в цвет главной кнопки незачем. */
.estiprint-service-toggle {
    position: relative;
    flex: none;
    width: 34px;
    height: 19px;
    padding: 0;
    border: none;
    border-radius: 999px;
    background: var(--ep-color-disabled);
    cursor: pointer;
    transition: background .18s;
}

.estiprint-service-toggle::before {
    content: '';
    position: absolute;
    top: 2px;
    left: 2px;
    width: 15px;
    height: 15px;
    border-radius: 50%;
    background: #fff;
    transition: transform .18s;
}

.estiprint-service-toggle[aria-checked="true"] {
    background: var(--ep-ink);
}

.estiprint-service-toggle[aria-checked="true"]::before {
    transform: translateX(15px);
}

/* Обязательную видно, но не выключить: иначе непонятно, за что надбавка. */
.estiprint-service-toggle:disabled {
    cursor: not-allowed;
    opacity: .6;
}

/* Услуги всей заявки — в нижней полосе, рядом с итогом, который они меняют. */
.estiprint-request-services {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.estiprint-request-services:empty {
    display: none;
}

.estiprint-request-service {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 13px;
    color: var(--ep-ink-2);
    cursor: pointer;
}

.estiprint-request-service-name {
    flex: 1;
    min-width: 0;
}

.estiprint-request-service-price {
    flex: none;
    font-weight: 600;
    color: var(--ep-ink);
}


/* Состояние строки и согласие с мягкими предупреждениями (замечание №16).
   Weerg делает так же: чип на строке, липкая полоса, выключенная кнопка. */
.estiprint-cart-state {
    display: inline-block;
    margin-left: 8px;
    padding: 2px 9px;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.02em;
    vertical-align: middle;
    white-space: nowrap;
}
.estiprint-cart-state--ready { background: var(--ep-state-ready-bg); color: var(--ep-state-ready); }
.estiprint-cart-state--todo { background: var(--ep-state-todo-bg); color: var(--ep-state-todo); }
.estiprint-cart-state--warn { background: var(--ep-state-warn-bg); color: var(--ep-state-warn); }

/* В строке заявки пилюля мельче: три яруса на сорок моделей — это высота экрана
   на каждые восемь. */
.estiprint-cart-item .estiprint-cart-state {
    padding: 2px 8px;
    font-size: 10.5px;
    line-height: 1.4;
}

/* Полоса слева — в цвет пилюли этой строки: зелёная у готовой, жёлтая у той,
   что просит внимания, серая у ненастроенной и у той, которую сейчас меряют.
   Прежде полоса говорила о другом — красная у открытой строки и жёлтая у
   помеченной, — то есть отвечала то на «какая строка на холсте», то на «что с
   моделью», и какой ответ сейчас, по цвету было не понять. Первое теперь
   говорят подложка и метка «editing».

   Берётся подложка пилюли, а не её чернила: полоса читается как продолжение
   пилюли, а не как второй, более громкий знак рядом с ней. */
.estiprint-cart-item {
    box-shadow: inset 3px 0 0 var(--ep-state-line, transparent);
}

.estiprint-cart-item--ready { --ep-state-line: var(--ep-state-ready-bg); }
.estiprint-cart-item--todo { --ep-state-line: var(--ep-state-todo-bg); }
.estiprint-cart-item--warn { --ep-state-line: var(--ep-state-warn-bg); }
.estiprint-cart-item--busy { --ep-state-line: var(--ep-state-busy-bg); }

/* Затемнение и окно поверх страницы — артборды `Consent` и `SubmitForm`.

   Затемнение живёт вне `.estiprint-calculator-section`, а правило про `[hidden]`
   там и заканчивается: `display:flex` ниже перебивало атрибут, и закрытое окно
   оставалось на экране белым прямоугольником. */
.estiprint-scrim[hidden] {
    display: none;
}

.estiprint-scrim {
    position: fixed;
    inset: 0;
    z-index: var(--ep-z-overlay);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    background: var(--ep-scrim);
}

.estiprint-modal {
    width: min(560px, 100%);
    max-height: calc(100vh - 48px);
    overflow: auto;
    padding: 24px 26px;
    border-radius: var(--ep-radius-card, 12px);
    background: #fff;
    font-size: 14px;
    line-height: 1.55;
    color: var(--ep-ink-2, var(--ep-color-graphite));
    box-shadow: var(--ep-shadow-dialog);
}

.estiprint-modal-foot {
    display: flex;
    justify-content: flex-end;
    flex-wrap: wrap;
    gap: 12px;
    margin-top: 20px;
}

.estiprint-consent-title {
    margin: 0 0 10px;
    font-size: 18px;
    font-weight: 700;
    color: var(--ep-ink, var(--ep-color-title));
}

.estiprint-consent-list { margin: 0 0 10px; padding-left: 18px; }
.estiprint-consent-list li { margin-bottom: 4px; }
.estiprint-consent-note { margin: 0; color: var(--ep-ink-3, #8c8c8c); font-size: 13px; }

/* Размер задан, а не унаследован: через `font: inherit` он приходил от
   `.estiprint-modal`, а в чужой теме пришёл бы от неё. */
.estiprint-consent-back,
.estiprint-consent-accept {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: var(--ep-h-md);
    border-radius: var(--ep-radius-btn-sm);
    padding: 10px 20px;
    font-family: var(--ep-font-family);
    font-size: 14px;
    line-height: 20px;
    font-weight: 600;
    cursor: pointer;
}

.estiprint-consent-back {
    border: 1px solid var(--ep-color-gray, #d9d9de);
    background: #fff;
    color: var(--ep-ink, var(--ep-color-title));
}

.estiprint-consent-back:hover { border-color: var(--ep-hover-ink); }

.estiprint-consent-accept {
    border: 1px solid var(--ep-color-accent);
    background: var(--ep-color-accent);
    color: #fff;
}

.estiprint-consent-accept:hover {
    background: var(--ep-color-accent-hover);
    border-color: var(--ep-color-accent-hover);
}

/* ── Списки настроек говорят языком полосы сроков ────────────────────────────
   Процесс, материал, отделка, заполнение и услуги настраивались каждый своим
   видом: сегменты с тёмной заливкой, плитки в белых рамках, пилюли, кнопки с
   двухпиксельной рамкой и карточки услуг. Пять списков в одной панели, и все
   пять по-разному отвечали на «что выбрано» и «по чему можно нажать».

   Язык взят у полосы сроков, потому что она уже отвечает на оба вопроса и
   нарисована в артборде: корыто в светлой рамке, внутри ячейки без собственных
   границ, выбранная помечена обводкой акцента и бледной подложкой.

   Здесь только шкура: раскладку каждый список оставляет свою — у материалов
   сетка, у отделки перенос по ширине, у услуг столбец.
   ------------------------------------------------------------------------- */
#ep-tech-options,
#ep-material-options,
#ep-infill-presets,
#ep-color-options .estiprint-color-swatches,
.estiprint-service-list {
    gap: 4px;
    padding: 4px;
    border: 1px solid var(--ep-line);
    border-radius: var(--ep-radius-field);
    /* Корыто белое: ячейки на нём должны быть светлее под курсором, а не
       темнее. */
    background: #fff;
    /* Ширина по содержимому, как у сегментов процесса: растянутое на всю
       колонку корыто обещает ячейки, которых в нём нет, — четыре образца
       отделки и полметра пустой рамки справа.

       `fit-content`, а не `max-content` с пределом в 100%: второе сначала
       требует ширины «всё в одну строку», и колонка, которая меряет себя по
       содержимому, честно под это расширялась — на телефоне правая колонка
       разъезжалась до 971 пикселя. `fit-content` берёт меньшее из двух —
       содержимое и доступное место, — поэтому перенос остаётся на месте. */
    width: fit-content;
}

/* Рамка у ячейки есть всегда: невыбранная плитка без неё — текст, висящий в
   корыте, и список читается как перечисление слов, а не как выбор из
   нескольких. Меняется не наличие рамки, а её цвет: светлая в покое, заметная
   под курсором, акцентная у выбранной. */
/* Заполнение — исключение из ширины по содержимому: пять плотностей это шкала
   от 5% до 100%, и шкалой она читается, только когда занимает всю ширину.
   Ячейки на ней делят место поровну (`flex: 1 1 0` у самой кнопки), поэтому
   шаги шкалы равны, как им и положено. Правило стоит после общего: специфичность
   у них одинаковая, и решает порядок. */
#ep-infill-presets {
    width: auto;
}

#ep-tech-options .estiprint-tech-item,
#ep-material-options .estiprint-material-item,
#ep-color-options .estiprint-color-item,
.estiprint-infill-btn,
.estiprint-service {
    border: 1.5px solid var(--ep-line);
    border-radius: var(--ep-radius-btn-sm);
    background: transparent;
    transition: background .15s, border-color .15s, color .15s;
}

#ep-tech-options .estiprint-tech-item:hover:not(.is-selected),
#ep-material-options .estiprint-material-item:hover:not(.is-selected):not(.is-unavailable),
#ep-color-options .estiprint-color-item:hover:not(.is-selected),
.estiprint-infill-btn:hover:not(.is-active) {
    background: var(--ep-hover-surface);
    border-color: var(--ep-hover-line);
}

#ep-tech-options .estiprint-tech-item.is-selected,
#ep-material-options .estiprint-material-item.is-selected,
#ep-color-options .estiprint-color-item.is-selected,
.estiprint-infill-btn.is-active,
.estiprint-service.is-on {
    border-color: var(--ep-selected-line);
    background: var(--ep-selected-surface);
    box-shadow: var(--ep-selected-shadow);
}

/* ── Правая колонка по макету ────────────────────────────────────────────────
   `docs/design/Main.dc.html`: полоса файла, подпись раздела, технология
   сегментами, материалы плитками с ценой за грамм, отделка свотчами. Было —
   нумерованные карточки «2. Choose technology», «3. Choose material», язык
   мастера, который заполняют сверху вниз. Модель приходит настроенной, и это
   правка выбранного, а не заполнение пустого (BRIEF.md).
   ------------------------------------------------------------------------- */

.estiprint-calculator-filebar {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 16px;
    margin: 14px 0 6px;
    font-size: 14px;
    color: var(--ep-ink);
}

.estiprint-calculator-filebar.is-hidden { display: none; }

.estiprint-calculator-filebar-name {
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.estiprint-calculator-filebar-measure {
    flex: none;
    font-size: 12px;
    color: var(--ep-ink-3);
    white-space: nowrap;
}

.estiprint-calculator-filebar-sep { margin: 0 3px; }
.estiprint-calculator-filebar-unit { margin-left: 3px; }

/* Подпись раздела: мелкая, прописная, тихая. Раздел настройки, а не шаг. */
.estiprint-calculator-option-label {
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ep-ink-2);
}

/* Свёрнутые настройки: полоса с подписью раздела, тем, что выбрано, и стрелкой.
   Подпись — тем же языком, что подписи шагов внутри (`option-label`), потому
   что говорит она о том же. Строка выбора тише подписи: её читают глазами
   мимоходом, а не ищут. */
.estiprint-calculator-options-toggle {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 12px 0;
    border: none;
    border-bottom: 1px solid var(--ep-line);
    background: none;
    font-family: var(--ep-font-family);
    text-align: left;
    cursor: pointer;
}

.estiprint-calculator-options-title {
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ep-ink-2);
    white-space: nowrap;
}

.estiprint-calculator-options-summary {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 13px;
    color: var(--ep-ink-3);
}

.estiprint-calculator-options-chevron {
    flex: none;
    color: var(--ep-ink-2);
    transition: transform 0.2s ease;
}

.estiprint-calculator-options-toggle.is-open .estiprint-calculator-options-chevron {
    transform: rotate(180deg);
}

.estiprint-calculator-options-toggle:hover .estiprint-calculator-options-title,
.estiprint-calculator-options-toggle:hover .estiprint-calculator-options-chevron {
    color: var(--ep-hover-ink);
}

/* Атрибут, а не класс: тело сворачивается `hidden`, и правило `display` у
   соседей не должно его перебивать. */
.estiprint-calculator-options-body[hidden] {
    display: none;
}

/* Первый шаг внутри раскрытых настроек отбит от полосы, а не прижат к ней. */
.estiprint-calculator-options-body {
    padding-top: 18px;
}

.estiprint-calculator-options .estiprint-calculator-option {
    border: none;
    background: none;
    box-shadow: none;
    padding: 0;
    margin: 0 0 22px;
}

/* Технология — сегментами: их две-три, и они взаимоисключающие. Всё остальное
   — корыто, рамку, радиус и ширину по содержимому — задаёт общий язык списков
   выше. */
#ep-tech-options {
    display: flex;
}

/* Рамку, подложку, радиус и переход ячейке задаёт общий язык списков выше:
   здесь остаётся только то, чем сегмент процесса отличается от остальных
   ячеек, — его раскладка и вес подписи. */
#ep-tech-options .estiprint-tech-item {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 7px 16px;
    font-size: 13px;
    font-weight: 600;
    color: var(--ep-ink-2);
    cursor: pointer;
}

/* Выбранный сегмент метится как выбранная ячейка где угодно в настройках —
   обводкой и подложкой (правило выше). Здесь только подпись: она набирает
   чернила, потому что на бледной подложке приглушённый серый теряется. */
#ep-tech-options .estiprint-tech-item.is-selected {
    color: var(--ep-ink);
}

/* Материалы — плитками: у каждой цена за грамм, по которой их и выбирают. */
/* Плитки переносом, а не сеткой. Сетка из `auto-fill` и `1fr` растягивается на
   всю колонку по определению: доли делят то, что им дали, — и корыто под тремя
   материалами занимало столько же места, сколько под девятью. Переносом плитка
   берёт ширину по названию, но не уже ста тридцати пикселей, а корыто
   останавливается по последней из них. */
#ep-material-options {
    display: flex;
    flex-wrap: wrap;
}

/* Плитка растёт до конца своей строки: иначе перенос оставлял справа рваный
   край — корыто до правого края колонки, а плитки заканчивались где придётся.
   Когда все помещаются в строку, растягивать нечего: корыто само сжимается по
   ним. */
#ep-material-options .estiprint-material-item {
    flex: 1 1 auto;
    min-width: 130px;
    max-width: 100%;
}

#ep-material-options .estiprint-material-item {
    display: block;
    position: relative;
    /* Справа — место под значок ⓘ: без него длинное название подтекало бы под
       него, а «(Polyamide)» переносилось бы по чужой причине. */
    padding: 11px 34px 11px 13px;
    font-size: 13px;
    color: var(--ep-ink-2);
    cursor: pointer;
}

/* Выбранная плитка — общим правилом выше; здесь остаётся только вес подписи. */
#ep-material-options .estiprint-material-item.is-selected .estiprint-material-item-name {
    color: var(--ep-ink);
}

#ep-material-options .estiprint-material-item-name {
    display: block;
    font-size: 13px;
    font-weight: 600;
    color: var(--ep-ink);
}

/* Значок ⓘ — в правом верхнем углу плитки, а не строкой под ценой: строкой он
   добавлял плитке ярус и уводил взгляд с названия и цены, по которым материал
   и выбирают. */
#ep-material-options .estiprint-material-item .estiprint-hint {
    position: absolute;
    top: 9px;
    right: 9px;
    margin-left: 0;
}

/* Прижатие подсказки крайней правой плитки отсюда тоже ушло: край считается
   один раз для всех, по экрану, а не по колонке. */

/* Цена за грамм — то, по чему материалы сравнивают, но не заголовок плитки. */
.estiprint-material-item-price {
    display: block;
    margin-top: 2px;
    font-size: 11px;
    font-weight: 400;
    color: var(--ep-ink-3);
}

/* Отделка — «пилюлями», как в макете (`docs/design/Main.dc.html`, блок
   Finishing): кружок цвета, название и надбавка одной строкой. Группы идут
   столбцом, образцы в группе — рядом. Плитка шириной 118 пикселей с кружком
   над названием занимала под два образца ряд, где их помещается пять. */
#ep-color-options {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

#ep-color-options .estiprint-color-item {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 5px 12px 5px 6px;
    /* Единственное, что образец оставляет себе от прежнего вида, — форму:
       пилюля с кружком цвета нарисована так в артборде `Finishing`, и к
       состояниям она отношения не имеет. */
    border-radius: 999px;
    font-size: 12px;
    line-height: 1.3;
    color: var(--ep-ink);
    cursor: pointer;
}

/* Выбранный образец — общим правилом выше. */

/* ⓘ с описанием отделки — последним в пилюле образца. Свой отступ слева у него
   уже есть, а правый отступ пилюли посчитан под текст, не под значок. */
#ep-color-options .estiprint-color-item .estiprint-hint {
    margin-left: 1px;
    margin-right: -4px;
}

#ep-color-options .estiprint-color-item-swatch {
    display: block;
    flex: none;
    width: 16px;
    height: 16px;
    border: 1px solid rgba(0, 0, 0, .12);
    border-radius: 50%;
}

/* Название и надбавка — одной строкой: «Raw Grey included». */
.estiprint-color-item-info {
    display: flex;
    align-items: baseline;
    gap: 4px;
}

#ep-color-options .estiprint-color-item-name {
    font-weight: 400;
    color: var(--ep-ink);
}

/* Надбавка на самом образце: иначе её узнают, перебирая цвета и следя за
   итогом. */
.estiprint-color-item-price {
    font-style: normal;
    font-size: 11px;
    color: var(--ep-ink-3);
}

#ep-color-options .estiprint-color-item.is-selected .estiprint-color-item-price {
    color: var(--ep-ink-2);
}

/* ── Узкий экран ─────────────────────────────────────────────────────────────
   Артборд `Narrow`: колонки складываются, строка заявки теряет третий ярус в
   ряд — чипы и действия встают друг под друга, потому что рядом им уже не
   помещаться. Плотность на телефоне держит не ширина, а то, что настройка
   по-прежнему видна, не открывая модель.

   Все контейнерные правила калькулятора собраны здесь, от широкого к узкому.
   Разбросанные по файлу, они перебивались правилами ниже себя: сетка
   материалов в две колонки на 720 пикселях и поля карточки шага на 480 не
   срабатывали ни разу.
   ------------------------------------------------------------------------- */
@container estiprint (max-width: 1280px) {
    .estiprint-calculator-frame {
        padding-left: 24px;
        padding-right: 24px;
    }

    .estiprint-calculator-grid {
        grid-template-columns: 1fr 1fr;
    }
}

@container estiprint (max-width: 900px) {
    .estiprint-calculator-frame {
        padding: 40px 20px 40px;
    }

    .estiprint-calculator-title {
        font-size: 36px;
        line-height: 1.2;
    }

    .estiprint-calculator-description {
        font-size: 16px;
        line-height: 1.44;
    }

    .estiprint-calculator-grid {
        grid-template-columns: 1fr;
    }

    /* Двух колонок по 350 пикселей хватает и здесь: слева слова и разбор,
       справа решения и число. Ломать полосу в один столбец раньше времени
       незачем — она прилипшая, и каждый лишний ряд отнимается у списка. */
    .estiprint-calculator-summary {
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
        gap: 16px 24px;
    }

    .estiprint-cart-item {
        grid-template-columns: auto minmax(0, 1fr) auto;
        grid-template-areas:
            "pick name  price"
            "pick dims  perpart"
            "pick chips chips"
            "pick qty   acts";
        row-gap: 6px;
    }

    .estiprint-cart-item-qty {
        justify-self: start;
        margin-right: 0;
    }

    /* На узкой колонке плитке хватает ста десяти: имя переносится, а третья
       плитка не уезжает на свою строку из-за двадцати пикселей. */
    #ep-material-options .estiprint-material-item {
        min-width: 110px;
    }
}

@container estiprint (max-width: 640px) {
    /* Шкала заполнения остаётся шкалой: пять шагов в один ряд. Своя нижняя
       ширина в 56 пикселей на узкой колонке роняла пятый шаг на вторую строку,
       и «100% Solid» растягивался там во всю ширину — из шкалы получался
       список с одним огромным пунктом. */
    .estiprint-infill-btn {
        min-width: 0;
    }

    .estiprint-cart-tools {
        flex-wrap: wrap;
    }
}

@container estiprint (max-width: 576px) {
    /* Одна колонка: пара «слова и число» здесь уже не пара, а два столбца по
       270 пикселей. */
    .estiprint-calculator-summary {
        grid-template-columns: minmax(0, 1fr);
    }

    .estiprint-calculator-summary-left {
        grid-column: 1;
    }

    .estiprint-calculator-summary-submit {
        margin-left: auto;
    }

    .estiprint-calculator-filebar {
        flex-direction: column;
        align-items: flex-start;
        gap: 2px;
    }

    /* Сегменты технологий переносятся, а не выезжают за край. */
    #ep-tech-options {
        display: flex;
        flex-wrap: wrap;
    }

    /* Нижняя полоса на телефоне — одной колонкой. В два столбца разбивка
       получала треть ширины, и «Нова пошта in 3 days» разваливалась на четыре
       строки рядом с кнопкой во всю высоту. */
    .estiprint-calculator-summary-right {
        grid-column: 1;
        grid-template-columns: minmax(0, 1fr);
    }

    .estiprint-summary-actions {
        grid-column: 1;
        grid-row: auto;
    }

    /* Сроки — друг под другом. В три колонки на телефоне на карточку остаётся
       восемьдесят пикселей, и «Standard · Sep 15» обрезается до
       «Standard · Se…»: пропадает ровно та дата, ради которой на срок и
       смотрят. */
    .estiprint-lead {
        flex-direction: column;
    }

    /* Карточкой и здесь: рамка у каждой своя, разделять их чертой не нужно —
       корыто и зазор между ними делают это сами. */
    .estiprint-lead-option {
        display: flex;
        align-items: baseline;
        justify-content: space-between;
        gap: 12px;
        text-align: left;
    }

    .estiprint-lead-price {
        margin-top: 0;
    }

    /* В строку подпись помещается целиком — держать под неё два ряда незачем. */
    .estiprint-lead-when {
        justify-content: flex-start;
        min-height: 0;
    }
}

@container estiprint (max-width: 480px) {
    .estiprint-calculator-uploader {
        padding: 20px 10px;
    }

    .estiprint-calculator-summary {
        padding: 20px 10px;
    }
}


/* ── `hidden` должен скрывать ───────────────────────────────────────────────
   Атрибут задаёт `display:none` с нулевой специфичностью, и любое правило по
   классу его перебивает. Полоса подсказки заявки так и висела: `hidden=true`,
   высота 44px, красная плашка с восклицательным знаком и без текста. Ловушка
   не разовая — она повторится с каждым новым блоком, который прячут атрибутом.
   ------------------------------------------------------------------------- */
.estiprint-calculator-section [hidden] {
    display: none !important;
}

/* Подсказка под заголовком не исчезает. Она пряталась с появлением первой
   модели, потому что умолчание текста — «Чтобы посчитать, загрузите модель» —
   с этого мгновения перестаёт быть правдой. Но текст правится в настройках, а
   строка, пропадающая по ходу работы, читается сбоем: разбираться, куда делась
   надпись, приходится каждому, а неудачное умолчание правит магазин один раз.
   Класс `has-models` остаётся: его ставит `renderCart()`, и он ещё пригодится
   тому, что действительно зависит от наличия моделей. */

/* ── Пока что-то происходит ──────────────────────────────────────────────────
   Артборд `Analysing`: строка говорит «measuring…» вместо габаритов, пилюля —
   «ANALYSING…», просмотрщик — «Measuring the model…». Операция, которая длится
   и молчит, читается как зависшая; на сорока моделях это сорок поводов
   перезагрузить страницу.
   ------------------------------------------------------------------------- */
.estiprint-cart-state--busy { background: var(--ep-state-busy-bg); color: var(--ep-state-busy); }

.estiprint-cart-item-dims.is-busy {
    font-style: italic;
    color: var(--ep-ink-3);
}

/* Пульс, а не вращение: строка меряется секунду-две, и крутящийся элемент в
   ней спорил бы за внимание с ценой соседней строки. */
.estiprint-cart-state--busy,
.estiprint-cart-item-dims.is-busy {
    animation: estiprint-pulse 1.4s ease-in-out infinite;
}

@keyframes estiprint-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.45; }
}

@media (prefers-reduced-motion: reduce) {
    .estiprint-cart-state--busy,
    .estiprint-cart-item-dims.is-busy {
        animation: none;
    }
}

/* Просмотрщик, пока сервер меряет: место модели занимает то, что о ней сейчас
   известно, — что её меряют. */
.estiprint-viewer-busy {
    animation: estiprint-pulse 1.4s ease-in-out infinite;
}

@media (prefers-reduced-motion: reduce) {
    .estiprint-viewer-busy { animation: none; }
}

.estiprint-calculator-filebar-remove {
    flex: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--ep-h-row);
    height: var(--ep-h-row);
    padding: 0;
    border: none;
    border-radius: 50%;
    background: none;
    color: var(--ep-ink-3);
    cursor: pointer;
    line-height: 0;
    transition: color .15s, background .15s;
}

/* Иконка та же, что в строке заявки и в админке: обводка идёт за цветом
   кнопки, размер — по её росту. */
.estiprint-calculator-filebar-remove svg {
    width: 16px;
    height: 16px;
    stroke: currentColor;
    fill: none;
}

/* Снять модель — то же удаление, что и в строке заявки, и метится так же. */
.estiprint-calculator-filebar-remove:hover {
    color: var(--ep-accent);
    background: var(--ep-hover-danger);
}
