/* touch-action:manipulation kills the NATIVE double-tap gesture (zoom is locked by the
   viewport, so it degenerated into a "centre on the tapped point" scroll nudge) and the
   350ms click delay. Our own click-based double-tap detector handles double taps instead.
   Pinch stays blocked: WKWebView honours user-scalable=no unless ignoresViewportScaleLimits. */
html{-webkit-text-size-adjust:100%;touch-action:manipulation;}
/* WebKit's OWN scroll anchoring is OFF: it doesn't hold the viewport reliably when content
   above it changes height (replaceFragment compensates manually instead). */
html,body,#content,#tail{overflow-anchor:none;}
/* NO horizontal scroll, ever. Books ship wide images/tables/<pre> and explicit widths that
   would let the page pan sideways; this is the hard backstop (the rules below make wide
   content actually fit instead of being clipped). */
html,body{overflow-x:hidden;max-width:100%;}
body{background:var(--bg)!important;color:var(--ink)!important;
     font-family:var(--reader-font)!important;font-size:var(--reader-size)!important;
     line-height:var(--reader-line)!important;margin:0;padding:132px 22px 130px;
     -webkit-font-smoothing:antialiased;}
/* Line spacing is a READER setting, not book typography. Setting it only on body is not enough:
   an EPUB's direct `p{line-height:150%}` wins over any inherited value, so the settings control
   appears broken and a Word Wise hint can collide with the line above. Force the chosen unitless
   ratio on every block that can carry book text. Inline runs do not need a reset: the carrier's
   line-box strut is the safety floor, while sup/sub/ruby/MathML keep their deliberate metrics.
   `_enforceReaderLine` in 20-fragments.js mirrors this list and is the second layer for inline
   `style="line-height:...!important"`, which a stylesheet cannot reliably beat. */
#content p,#content div,#content h1,#content h2,#content h3,#content h4,#content h5,#content h6,
#content blockquote,#content li,#content dt,#content dd,#content figcaption,#content caption,
#content td,#content th,#content pre,#content address,#content section,#content article,
#content aside,#content header,#content footer,#content main,#content nav,#content hgroup,#content center,
#content ul,#content ol,#content dl{line-height:var(--reader-line)!important;}
/* The isolated onboarding overlays progress + instruction above the web content. Keep its first
   line below that chrome without changing the proven reader geometry. Keep the value in
   sync with OnboardingExercisePage.webTopChromeHeight (the opaque curtain on the sentence
   screen that tear-shifted context lines slide under).
   User scrolling in the onboarding exercises is blocked by the non-passive touchmove preventDefault
   (see the touch listeners) — CSS touch-action does NOT gate the main-frame pan. */
body[data-interaction-mode^="onboarding"]{padding-top:227px;}
/* Sentence exercise: the paragraph is centered between the curtain and the CTA by
   _centerOnboardingSentence (these values are only the pre-layout default). The bottom
   padding is trimmed so the document is exactly as tall as the text — nothing to scroll
   into past it. На время раскрытого перевода _openTear добирает его ровно настолько,
   чтобы хватило на реконсил (scroll0+D) — иначе до уехавшего вверх контекста не долистать. */
body[data-interaction-mode="onboardingSentence"]{padding-top:260px;padding-bottom:24px;}
p,div,span,h1,h2,h3,h4,h5,h6,blockquote,li{color:var(--ink);}
/* Основной текст ВСЕХ книг — выравнивание по ширине (justify), как у Project Hail Mary,
   а не left из CSS книги. #content-специфичность (1,0,1) бьёт стиль книги; заголовки (h1-h6)
   и явная inline-центровка (style="text-align:center") не трогаются. hyphens сглаживает зазоры.
   text-wrap:stable — КРИТИЧНО: на новых iOS (26.x) WebKit раскладывает justify-абзацы
   look-ahead-алгоритмом по нескольким строкам, и раздвижка тапнутого слова (applySpacing)
   перекладывала строки ВЫШЕ точки тапа (слово с конца верхней строки уезжало вниз — «текст
   выше прыгает»; на старых iOS разбивка жадная и этого нет). stable = инкрементальная
   разбивка: изменение контента не пере-разбивает строки ДО места правки. Старые движки
   свойство просто игнорируют. */
#content p,#content li{text-align:justify;-webkit-hyphens:auto;hyphens:auto;
     text-wrap:stable;text-wrap-style:stable;}
/* Красная строка (отступ первой строки абзаца) для ВСЕХ книг — единообразно, как в бумажной
   вёрстке. #content-специфичность (1,0,1) перебивает p из CSS книги (0,0,1), даже если книга
   индент не задаёт или он не доезжает. БЕЗ отступа — только настоящий первый абзац после
   заголовка (типографская норма: красная строка отделяет абзацы, а не открывает их). ВАЖНО:
   НЕ занулять по p:first-child — читалка грузит книгу чанками-фрагментами (один фрагмент =
   свой <div>-обёртка), и почти каждый фрагмент начинается с <p>, который оказывается
   :first-child своей обёртки. Зануление по :first-child убило бы отступ почти везде. Перевод-
   блок предложения (.tear-inner) и служебные плашки (.plum-notice) — без отступа. */
#content p{text-indent:1.15em;}
#content h1+p,#content h2+p,#content h3+p,#content h4+p,#content h5+p,#content h6+p,
#content hgroup+p,#content header+p,#content blockquote>p:first-child,
#content li>p:first-child,#content .plum-notice p{text-indent:0;}
/* СТИХИ. Раньше вёрстку строк отдавали книге: издательский CSS (Standard Ebooks) сам делал строку
   блоком с висячим отступом, прятал разделяющие <br> и раскладывал ступени классами i1/i2/i3, а мы
   лишь снимали прозаические правила. С 12.09.2026 стили издания не подключаются вовсе (единое
   оформление для всех книг), поэтому структуру строфы задаём ЗДЕСЬ — иначе стих сливается в
   сплошной абзац с красной строкой и выключкой по ширине.
   Селекторы — по семантике epub и по классу, в который её разворачивает импортёр
   (namespace-селектор в HTML не работает). Переносы по слогам в стихах не нужны. */
#content [epub\:type~="z3998:poem"] p,#content [epub\:type~="z3998:verse"] p,
#content [epub\:type~="z3998:song"] p,#content [epub\:type~="z3998:hymn"] p,
#content .epub-type-contains-word-z3998-poem p,
#content .epub-type-contains-word-z3998-verse p,
#content .epub-type-contains-word-z3998-song p,
#content .epub-type-contains-word-z3998-hymn p{
     text-align:left;text-indent:0;-webkit-hyphens:none;hyphens:none;}
/* Строфа: сам блок стиха — с воздухом и небольшим полем, без книжных отступов blockquote. */
#content [epub\:type~="z3998:poem"],#content [epub\:type~="z3998:verse"],
#content [epub\:type~="z3998:song"],#content [epub\:type~="z3998:hymn"],
#content .epub-type-contains-word-z3998-poem,#content .epub-type-contains-word-z3998-verse,
#content .epub-type-contains-word-z3998-song,#content .epub-type-contains-word-z3998-hymn{
     margin:1.4em 0;padding-left:1.2em;}
/* Строка стиха — ИНЛАЙН, разрыв строки — видимый <br> издателя. Блочной (display:block с
   висячим отступом переноса) строка быть НЕ может: перевод предложения оборачивает диапазон в
   инлайновый .tr-sent, блок внутри него выпадает из подсветки, а границу предложения посреди
   строки extractContents режет <span> на две части — блочный хвост уезжал на отдельную строку и
   после закрытия так и оставался (обёртка снимается, части не сшиваются). Инлайновая строка
   режется и живёт частями так же незаметно, как текст прозы.
   Цена: длинная строка, не влезшая по ширине, переносится в левый край, а не с втяжкой. */
/* Ступени втяжки строки (классы издателя i1/i2/i3 — общая конвенция Standard Ebooks). Селектор
   по потомку, а не по прямому ребёнку: внутри .tr-sent/.tear-hide строка — уже не ребёнок <p>. */
#content p span.i1{margin-left:1em;}
#content p span.i2{margin-left:2em;}
#content p span.i3{margin-left:3em;}
/* Хвост строки, разрезанной обёрткой (класс ставит _markCutTail в 90-sentence.js): ступень несёт
   голова, хвосту её повторять нельзя — иначе зазор в 1em посреди строки. */
#content p span.plum-tail{margin-left:0;}
/* Строфы внутри одного стиха разделяем воздухом (каждая — отдельный <p>). */
#content [epub\:type~="z3998:poem"] p+p,#content .epub-type-contains-word-z3998-poem p+p{
     margin-top:1em;}

/* ЗАГОЛОВОК ГЛАВЫ. У Standard Ebooks он собран в <hgroup>: номер главы — в <h2>, а НАЗВАНИЕ —
   в обычном <p epub:type="title">. Без стилей издания такой <p> неотличим от абзаца прозы
   (красная строка, выключка по ширине), поэтому задаём вид сами. */
#content hgroup{text-align:center;margin:2em 0 1.3em;}
#content hgroup h1,#content hgroup h2,#content hgroup h3,#content hgroup h4{
     font-size:1.05em!important;font-weight:600;opacity:.6;margin:0 0 .35em;text-align:center;
     -webkit-hyphens:none;hyphens:none;}
#content hgroup p,#content hgroup [epub\:type~="title"],
#content hgroup .epub-type-contains-word-title{
     font-size:1.35em;font-weight:600;line-height:1.25;margin:0;text-align:center;text-indent:0;
     -webkit-hyphens:none;hyphens:none;}
/* Force the reader's chosen typeface across ALL book text. Book CSS routinely sets
   font-family directly on <p>/inline spans, which beats body's inherited !important
   (inheritance is the lowest-priority cascade source). Without this, paragraphs render
   in the embedded book font while gloss spans differ → text "jumps" and highlighted
   words look unlike plain ones. Bold/italic (weight/style) and indents/dropcaps are
   untouched. Exclude our own translation UI so it keeps its serif/mono styling. */
#content :not(.tear-tr):not(.t-lbl){font-family:var(--reader-font)!important;}
/* Neutralise book quirks that set font-size directly on inline runs (e.g. HP's
   `span{font-size:150%}`). Our gloss markup wraps difficult words in <span class="gloss">,
   so such a rule blows them up 1.5× → highlighted words look bigger than plain text and
   the line "jumps". Reset every content <span> to its inherited size; headings/blocks
   (not spans) keep their intentional sizes. Exclude our own translation-UI spans. */
#content span:not(.tear-gap):not(.tear-inner):not(.t-lbl):not(.tear-tr){font-size:1em!important;}
/* Font-size: --reader-size is the SINGLE source of truth for body text, so the size slider
   works on EVERY book. Book CSS routinely pins font-size directly on text elements (classes
   like `.con{font-size:medium}`, Calibre `.calibreN`, and CSS keyword sizes small/medium/large
   that don't even scale) — a direct rule beats body's INHERITED size (inheritance is the
   weakest cascade source), so on such books the slider did nothing. Force the reader size onto
   the block-level text carriers (#content-specificity + !important beats book rules; books here
   don't use !important anyway), and give headings an em-scale so they stay proportional AND
   track the slider. Inline runs already reset to 1em above; footnote refs (a.noteref), sub/sup,
   MathML internals and our translation UI keep their own relative sizes (not matched / excluded). */
#content p,#content li,#content dd,#content dt,#content blockquote,#content figcaption,
#content td,#content th,#content div:not([class*="tear"]):not(.plum-notice){font-size:var(--reader-size)!important;}
#content h1{font-size:1.7em!important;}#content h2{font-size:1.45em!important;}
#content h3{font-size:1.25em!important;}#content h4{font-size:1.12em!important;}
#content h5{font-size:1em!important;}#content h6{font-size:.92em!important;}
img,svg,image{max-width:100%!important;height:auto!important;}
/* Тёмная тема: светлая подложка под иллюстрации (--img-bg, см. Theme.readerVars) — монохромный
   штрих на прозрачном PNG иначе сливается с тёмной страницей. У непрозрачных фото не видна. */
img,svg{background:var(--img-bg,transparent);}
/* overflow-x:hidden заодно включает обрезку по вертикали (overflow-y считается auto): облако пометки над
   первой строкой книги, выходящее выше #content, срезалось сверху. clip режет только по горизонтали
   (hidden — запасной для WebView без clip); flow-root держит margin'ы детей внутри, как держал hidden. */
#content{overflow-x:hidden;overflow-x:clip;display:flow-root;}
/* Ссылок в книге НЕ поддерживаем (решение владельца 12.09.2026): оглавление, список
   иллюстраций, сноски и внешние адреса читаются как обычный текст. Цвет не наследуется от
   --accent, иначе разметка глосс рвала фразу на оранжевые и чёрные куски: span.gloss внутри
   ссылки берёт цвет текста, а промежутки между ними оставались ссылочными. */
a{color:inherit;text-decoration:none;}
/* Wide content fits the column instead of scrolling sideways: tables lay out fixed-width
   with wrapping cells, code/pre wrap, long words/URLs break. */
table{max-width:100%!important;width:100%;table-layout:fixed;}
td,th{word-break:break-word;overflow-wrap:anywhere;}
pre,code,kbd,samp{white-space:pre-wrap!important;word-break:break-word;overflow-wrap:anywhere;}
#content p,#content div,#content figure,#content blockquote{max-width:100%;overflow-wrap:break-word;}
/* Formulas (MathML): cap to the column; an over-wide formula is clipped, not scrolled. */
math{font-size:1em;max-width:100%;}
math[display="block"]{display:block;margin:12px 0;}
/* Маркер сноски остаётся надстрочным, но не выглядит нажимаемым: сноски не открываются. */
a.noteref{color:inherit;text-decoration:none;
     vertical-align:super;font-size:.72em;padding:0 1px;}
/* Стих без семантики (класс ставит _markPlainVerse в 20-fragments.js): строки разделены <br>,
   поэтому обёрток вокруг них нет и висячий отступ переносу не сделать — снимаем хотя бы
   прозаические правила, из-за которых стих выглядел абзацем: выключку по ширине, красную строку
   и переносы по слогам. Левое поле отделяет строфу от прозы. */
#content p.plum-verse{text-align:left;text-indent:0;-webkit-hyphens:none;hyphens:none;
     margin:1.4em 0;padding-left:1.2em;}

/* Word Wise glosses */
/* БЕЗ white-space:nowrap на глоссах по умолчанию: неразрывность у невскрытых слов меняла
   переносы строк в момент врезки разметки (replaceFragment / applyRemark) — текст прыгал
   ВЫШЕ точки тапа от самой доразметки. Склейку (pill и подсказка не должны рваться между
   строк) даёт applySpacing точечно — inline nowrap при показе подсказки, и только когда
   первая строка глоссы при этом остаётся на месте. */
.gloss.clickable{cursor:pointer;-webkit-tap-highlight-color:transparent;}
/* Подчёркивание сложных слов — опционально (настройка underlineGlosses → body.gloss-underline). */
body.gloss-underline .gloss.clickable:not(.revealed){border-bottom:1px dotted var(--accentDot);}
/* Trim only the painted text box, not the book's roomy line-height. Cap-height removes the
   excess above the word; keep 6px above capitals and 1px around the other edges.
   Поля пилюли и зазор до подсказки — ПЕРЕМЕННЫЕ: у платформ разные наборы шрифтов, и минимальный
   междустрочный интервал, при котором подсказка ещё не задевает строку выше, считается из них.
   Дефолты здесь = историческим значениям iOS, поэтому платформа, которая переменных не шлёт,
   получает ровно прежнюю вёрстку; Android задаёт свои (в долях кегля) через themeVars. */
.gloss.revealed{position:relative;background:var(--pill);border-radius:3px;
     padding:var(--pill-pad-top,6px) 1px var(--pill-pad-bottom,1px);
     /* Низ подсветки: text-box-trim СРЕЗАЕТ нижний padding (померено), поэтому хвосты 'g' и 'y'
        оставались снаружи. Добираем бордюром того же цвета — у инлайна он не входит в высоту
        строки, то есть красит ниже базовой линии, ничего не сдвигая. Дефолт 0 = прежний вид. */
     border-bottom:var(--pill-under,0) solid var(--pill);
     text-box-trim:trim-both;text-box-edge:cap text;}
/* Склеенная (nowrap) глосса — АТОМАРНЫЙ бокс, а не инлайн. Причина: у инлайна с боковыми
   margins, который перенос уносит в начало следующей строки, WebKit (iOS 26) оставляет
   невидимый фрагмент бокса ещё и НИЖЕ — и абсолютная подсказка ::before считает свою привязку
   по нему, то есть встаёт ПОД словом вместо «над». getClientRects() этот фрагмент не
   показывает (один прямоугольник), поймать его из JS нечем; чинилось только повторной
   записью стиля — то есть у пользователя жило до тапа по соседнему слову. У атомарного бокса
   фрагмент ровно один — съезжать не от чего.
   Условие — САМ признак склейки (инлайновый white-space:nowrap от applySpacing), а не
   отдельный класс: nowrap ставится, снимается, переносится в клон при подмене фрагмента и
   восстанавливается после доразметки в четырёх разных местах, и класс-дублёр рано или поздно
   разъехался бы с ним. Фразе шире строки склейку откатывают (white-space:normal) — ей
   ломаться между строк по-прежнему можно.
   text-indent:0 — обязателен: свойство НАСЛЕДУЕТСЯ, а атомарный бокс раскладывает содержимое
   как блок, то есть отрабатывает красную строку абзаца (#content p{text-indent:1.15em}) ещё
   раз, внутри себя. Слово уезжало вправо, пилюля вырастала слева на этот отступ, и подсказка
   вставала по центру ПИЛЮЛИ, а не слова. У инлайна свойство ни на что не влияло, поэтому
   всплыло только со сменой типа бокса (::before ловит то же самое — см. ниже). */
.gloss.revealed[style*="nowrap"]{display:inline-block;text-indent:0;}
@supports not (text-box-trim:trim-both){
  .gloss.revealed{line-height:1;padding:var(--pill-pad-top-legacy,0) 1px var(--pill-pad-bottom-legacy,0);}
}
/* Подсказка привязана к СТАТИЧЕСКОЙ позиции (left:auto), а не к left:50%. У глоссы-фразы,
   которую перенос разорвал на две строки, «50%» считается браузером от объединяющего блока
   обоих кусков — подсказка уезжала далеко влево от слова. Статическая позиция = начало
   первого куска всегда; на его центр её ставит --tip-cx (полширины куска) из applySpacing.
   --tip-dx — сдвиг у края колонки, чтобы подсказку не обрезало. */
/* --tip-dx: сдвиг у края КОЛОНКИ (#content с overflow-x:hidden), чтобы подсказку не обрезало */
.gloss.revealed::before{content:attr(data-tr);position:absolute;left:auto;
     bottom:calc(100% + var(--hint-gap,1px));
     transform:translateX(calc(-50% + var(--tip-cx, 0px) + var(--tip-dx, 0px)));
     /* width:max-content — бокс всегда по тексту. Без него shrink-to-fit ужимает его
        остатком места справа (подсказка стоит в статической позиции), текст вылезает за
        бокс, и translateX(-50%) центрирует не то. */
     white-space:nowrap;width:max-content;font-size:var(--hint-size,.64em);font-style:italic;
     line-height:1;color:var(--hint);font-family:Georgia,serif;pointer-events:none;
     /* text-indent is INHERITED: a book's `p{text-indent:1em}` (e.g. Harry Potter) leaks into
        this absolutely-positioned (→ block) hint, pushing its text right inside the box while
        translateX(-50%) centres the box → the translation visibly drifts right. Reset it. */
     text-indent:0;}
/* Склеенная глосса — атомарный бокс (см. выше), и никакого «первого куска» у неё нет: её центр
   считается прямо, процентом от собственной ширины. Компенсация --tip-cx (полширины куска от
   статической позиции) здесь не просто лишняя, а вредна — у атомарного бокса статическая
   позиция отсчитывается иначе, и подсказка уезжала вбок ровно на ширину раздвижки. --tip-dx
   (сдвиг от края колонки) остаётся: он про колонку, а не про способ привязки. */
.gloss.revealed[style*="nowrap"]::before{left:50%;
     transform:translateX(calc(-50% + var(--tip-dx, 0px)));}
/* Фраза не влезает в строку целиком (её рвёт даже с nowrap) — перевод идёт ПО ЕЁ СТРОКАМ:
   свой кусок перевода над каждым куском фразы. Одним ::before это не сделать, поэтому куски
   рисуют абсолютные `.gtip` — ПУСТЫЕ элементы (текст живёт в content:attr): в абзаце нет
   ни одного лишнего текстового узла, обходы текста (границы предложений, доразметка,
   копирование) их не видят. Живут внутри самого абзаца (он получает position:relative) —
   значит едут вместе с ним при любых трансформах (разрыв под перевод предложения). */
.gloss.revealed.tip-multi::before{content:none;}
.gtip{position:absolute;transform:translate(-50%,-100%);white-space:nowrap;width:max-content;
     font-size:var(--hint-size,.64em);font-style:italic;line-height:1;color:var(--hint);font-family:Georgia,serif;
     pointer-events:none;text-indent:0;}
.gtip::before{content:attr(data-tip);}
/* Пометка служебного слова (книги pv2) — облако-диалог над словом после тапа (70-remark.js _showNoteAt). Как .gtip:
   пустой элемент, текст в content:attr — в тексте блока его нет; absolute внутри блока, не в потоке. Облако стоит
   выше зоны подсказок строки, хвостик длиной --tail-h тянется вниз до слова (--tail-dx — поправка у края колонки). */
/* text-indent/text-align-last/интервалы — НАСЛЕДУЮТСЯ от абзаца: красная строка (#content p{text-indent:1.15em})
   давала пустоту ~30px перед текстом облака, выравнивание последней строки страницы — растяжку. Сброс. */
.ntip{position:absolute;transform:translate(-50%,-100%);z-index:5;pointer-events:none;width:max-content;
      max-width:min(18em,78vw);white-space:normal;text-align:center;text-align-last:auto;text-indent:0;
      letter-spacing:normal;word-spacing:normal;-webkit-hyphens:none;hyphens:none;font-style:normal;font-weight:normal;
      font-size:.72em;line-height:1.3;padding:6px 12px;border-radius:14px;background:var(--ink);color:var(--bg);
      box-shadow:0 2px 10px rgba(0,0,0,.18);transition:opacity .2s;}
.ntip::before{content:attr(data-tip);}
/* Хвостик реплики — полоса под облаком шириной с облако, форма — clip-path из --tail-clip (70-remark.js
   _noteTail): кончик над серединой слова, наклон — от положения слова, стык с облаком скруглён, кончик тоже. */
.ntip::after{content:'';position:absolute;left:0;top:100%;width:var(--tail-w,0px);height:calc(var(--tail-h,8px) + 2px);
      background:var(--ink);-webkit-clip-path:var(--tail-clip,none);clip-path:var(--tail-clip,none);}
.ntip.out{opacity:0;}
/* Часы pv2 (перевод слова ещё готовится): после фразы — песочные часы, переворачиваются каждые 1,5 с. Пустой <b>,
   значок — в ::before, в тексте блока его нет. «Уменьшение движения» — часы стоят. */
.ntip.soon b{display:inline-block;margin-left:.35em;font-weight:normal;animation:hgFlip 3s ease-in-out infinite;}
.ntip.soon b::before{content:'⏳';}
@keyframes hgFlip{0%,40%{transform:rotate(0deg);}50%,90%{transform:rotate(180deg);}100%{transform:rotate(360deg);}}
@media (prefers-reduced-motion: reduce){.ntip.soon b{animation:none;}}
.tr-sent.active .gtip{display:none;}
/* Hide gloss underlines + word-hints inside the active translated sentence. */
.tr-sent.active .gloss.clickable{border-bottom-color:transparent;}
/* Keep sentence punctuation beside its word when an onboarding gloss expands. */
.onboarding-word-tail{white-space:nowrap;}
.tr-sent.active .gloss.revealed,.tr-sent .gloss.revealed.swept{background:transparent;}
.tr-sent.active .gloss.revealed::before,.tr-sent .gloss.revealed.swept::before,
.tr-sent.active .gloss.revealed::after,.tr-sent .gloss.revealed.swept::after{content:none;}
.plum-notice{margin:32px 0;text-align:center;color:var(--muted);font-family:Georgia,serif;}
.plum-notice .t{font-size:1.05em;color:var(--ink);}
.plum-notice a{color:var(--accent);font-weight:600;text-decoration:none;}
/* Премиум-CTA (плашка-стена «дальше по подписке» / регистрация) — кнопка, не просто текст. */
.plum-cta{display:inline-block;margin-top:6px;padding:13px 28px;border-radius:14px;
     background:linear-gradient(135deg,#3d1a40,#5e2350,#7a2f4a);color:#fff!important;
     font-family:-apple-system,system-ui,sans-serif;font-weight:700;font-size:.95em;
     text-decoration:none;box-shadow:0 4px 16px rgba(61,26,64,.32);-webkit-tap-highlight-color:transparent;}
/* Loader: spinner with «Загрузка» below it; in-flow (never overlaps text). */
.plum-loader{display:flex;flex-direction:column;align-items:center;gap:12px;
     margin:28px 0;color:var(--muted);font-family:Georgia,serif;}
.plum-spinner{width:26px;height:26px;border-radius:50%;
     border:2.5px solid var(--accentDot);border-top-color:var(--accent);
     animation:plumspin .8s linear infinite;}
@keyframes plumspin{to{transform:rotate(360deg);}}
/* First fragment not loaded yet: center the tail loader on the whole screen. */
#content:empty + #tail .plum-loader{min-height:70vh;justify-content:center;}
/* No native selection / magnifier / callout on long-press — we use it for
   sentence translation, not copy. Gloss/footnote taps still work (click). */
/* Cap the reading column so lines stay readable on wide screens; centered. ~34em of the reader
   font scales the cap with font size. No effect on phone in portrait (content is narrower). */
#content,#tail{max-width:34em;margin-left:auto;margin-right:auto;}
/* Планшет (body.tablet ставит натив: iPad / Android sw≥600dp): поля зависят только от ширины окна,
   не от шрифта — с колонкой в em крупный шрифт съедал поля, мелкий их раздувал. Как Apple Books
   (замер по его скриншоту iPad 13" в App Store: поле ≈95pt из 1032, колонка ≈842pt): поле 9% ширины,
   колонка не шире 842px — иначе в альбомной строка разъезжается до ~125 знаков. Шрифт меняет только
   переносы строк. */
body.tablet{padding-left:max(9vw, calc((100vw - 842px) / 2));padding-right:max(9vw, calc((100vw - 842px) / 2));}
body.tablet #content,body.tablet #tail{max-width:none;}
#content{-webkit-user-select:none;user-select:none;-webkit-touch-callout:none;}
.sent{-webkit-tap-highlight-color:transparent;overflow-anchor:none;}
/* Every sentence of the exercise accepts a hold, so the paragraph enters the screen CLEAN:
   no tint, no pulse. The .onboarding-sentence-target wrapper stays as the anchor the layout
   and the tests key off — it just paints nothing. */
body[data-interaction-mode="onboardingSentence"] .onboarding-sentence-target{
     border-radius:5px;-webkit-box-decoration-break:clone;box-decoration-break:clone;}
/* ---- Sentence translation (hold-to-fill + tear-open subscript, per design) ---- */
/* Held sentence (wrapped on touch): an amber fill grows left→right as you hold (a live
   progress indicator); pops on completion; settles to a soft tint while shown. */
@property --p { syntax:'<number>'; inherits:false; initial-value:0; }
.tr-sent{border-radius:5px;padding:0;margin:0;-webkit-tap-highlight-color:transparent;--p:0;
     background-image:linear-gradient(90deg, color-mix(in srgb, var(--accent) 45%, transparent), color-mix(in srgb, var(--accent) 45%, transparent));
     background-repeat:no-repeat;background-position:left center;background-size:calc(var(--p) * 100%) 100%;
     transition:--p .3s cubic-bezier(.3,.7,.4,1), box-shadow .25s ease;}
.tr-sent.holding{transition:box-shadow .25s ease;}   /* live progress → no --p transition while held */
.tr-sent.pop{animation:lpPop .55s cubic-bezier(.2,.7,.3,1);}
@keyframes lpPop{0%{box-shadow:0 0 0 0 color-mix(in srgb, var(--accent) 55%, transparent);}
     55%{box-shadow:0 0 0 7px color-mix(in srgb, var(--accent) 0%, transparent);}
     100%{box-shadow:0 0 0 0 transparent;}}
.tr-sent.active{--p:1;background-image:linear-gradient(90deg, color-mix(in srgb, var(--accent) 32%, transparent), color-mix(in srgb, var(--accent) 32%, transparent));}
/* «Палец» демо-подсказки (92-hint-demo.js): полупрозрачный круг ложится на предложение и держится,
   пока идёт заливка; вне #content — трансформы tear его не двигают. */
#demo-finger{position:absolute;z-index:6;width:38px;height:38px;margin:-19px 0 0 -19px;border-radius:50%;pointer-events:none;
     background:color-mix(in srgb, var(--ink) 20%, transparent);
     box-shadow:0 0 0 2px color-mix(in srgb, var(--ink) 30%, transparent), 0 4px 14px rgba(0,0,0,.18);
     opacity:0;transform:scale(1.6);transition:opacity .28s ease, transform .28s ease;}
#demo-finger.down{opacity:1;transform:scale(1);}
#demo-finger.up{opacity:0;transform:scale(1.5);}
/* Демо «нажми на слово» (wordHintDemo): тап, а не удержание — кружок появляется сразу в полный размер
   (без «вдавливания») и тут же расходится волной и гаснет. */
#demo-finger.tap.down{opacity:1;transform:scale(1);transition:none;}
#demo-finger.tap.up{opacity:0;transform:scale(1.8);transition:opacity .3s ease-out, transform .3s ease-out;}
/* Слово в демо «нажми на слово» раскрывается без раздвижки и без боковых полей плашки — ширина
   слова не меняется, выравнивание строки не перескакивает. */
.gloss.revealed.hint-still{padding-left:0;padding-right:0;transition:background-color .35s ease;}
.gloss.revealed.hint-still::before{transition:opacity .35s ease;}
/* Конец круга демо: все раскрытые слова гаснут вместе, плавно. */
.gloss.revealed.hint-still.hint-fading{background-color:transparent;}
.gloss.revealed.hint-still.hint-fading::before{opacity:0;}
/* The "tear" gap opening ABOVE the sentence; translation = italic subscript with a left
   accent bar + a small "RU" label. */
/* block (NOT inline-block) for EVERY alignment: a block's height is its flow contribution
   1:1, so the sentence drops linearly with height → the slope-1 scroll lockstep pins it
   exactly. inline-block sat on a line with a ~1-line strut (flow = max(strut,height), non-
   linear → jitter on left-aligned books) and, at width:100%, stretched the line above on
   justify. block-split does NOT reflow left-aligned text above; on justify it only un-
   stretches the single line directly above (standard last-line behaviour). */
.tear-gap{display:block;}   /* бокс-модель (flow-root/height:auto) ставит инлайн _openTear — draw-once */
.tear-inner{display:block;margin:7px 0 10px;padding:2px 0 3px 14px;border-left:2px solid color-mix(in srgb, var(--accent) 55%, transparent);
     line-height:1.3;   /* set on the BLOCK — its strut (inherited reader line-height) drives wrapped-line spacing, not .tear-tr's inline line-height */
     /* Anchor the translation block to the reader's base size. The block is excluded from the
        #content span{font-size:1em} reset, so without this it INHERITS the book's font-size
        (e.g. HP's span{font-size:150%}) and .tear-tr's .9em multiplies it → giant translation.
        A direct rule beats inheritance, so the size is the reader's regardless of book CSS. */
     font-size:var(--reader-size);
     text-indent:0;   /* don't inherit the book's p{text-indent} into the sentence-translation block */
     /* opacity по умолчанию 0; _openTear ставит 1 сразу. Раскрытие делает съезжающий непрозрачный
        низ (belowWrap), а НЕ анимация прозрачности/высоты — draw-once, без покадрового рефлоу. */
     opacity:0;}
.tear-inner .t-lbl{display:inline-block;font-family:ui-monospace,"SF Mono",Menlo,monospace;font-size:9.5px;letter-spacing:.08em;text-transform:uppercase;color:color-mix(in srgb, var(--accent) 90%, var(--ink));margin-right:7px;opacity:.85;vertical-align:1px;}
.tear-tr{font-family:Georgia,"Iowan Old Style",serif;font-style:italic;font-size:.9em;color:color-mix(in srgb, var(--ink) 64%, transparent);}
/* Кнопка-закладка у перевода: тап-зона справа от текста; svg-иконка явных размеров (резеты
   #content span{font-size:1em} не влияют). Заливка по состоянию bm-on (Swift — истина). */
.tear-bm{display:inline-block;vertical-align:1px;margin-left:8px;padding:1px 3px;cursor:pointer;
     pointer-events:auto;-webkit-tap-highlight-color:transparent;}
.tear-bm svg{width:14px;height:14px;pointer-events:none;fill:none;
     stroke:var(--muted);stroke-width:2;vertical-align:middle;
     /* НЕ иллюстрация: гасим подложку --img-bg (img,svg выше) — в тёмной теме она светлая и
        иконка сидела на белом прямоугольнике. */
     background:transparent;}
.tear-bm.bm-on svg{fill:var(--accent);stroke:var(--accent);}
/* Короткая подсветка блока, к которому перешли по закладке (~1.6с). */
.bm-flash{border-radius:4px;animation:bmFlash 1.6s ease-out;}
@keyframes bmFlash{0%,18%{background-color:color-mix(in srgb, var(--accent) 22%, transparent);}
     100%{background-color:transparent;}}
/* Появилась разметка → размеченные слова кратко вспыхивают ПИЛЮЛЕЙ (фон, как у раскрытой глоссы). */
.frag.gloss-flash .gloss.clickable{border-radius:3px;animation:glossFlash 1.6s ease-out;}
/* Оглавление, свёрстанное в тексте (20-fragments.js `_isTocFragment`): ссылки в читалке мёртвые, своё
   оглавление есть в панели — схлопываем. НЕ display:none: фрагмент остаётся в потоке с нулевой высотой,
   и восстановление позиции (`_restoreScroll` по offsetTop) приземляется на место оглавления, а не в
   начало документа; overflow:hidden держит внутренние margin'ы, visibility прячет то, что могло бы
   выглянуть. */
.frag.plum-toc{height:0;overflow:hidden;visibility:hidden;margin:0;padding:0;}
/* Пустой пункт списка (20-fragments.js _markBlankItems): маркер без строки — не показываем. */
li.plum-blank{display:none!important;}
@keyframes glossFlash{0%,20%{background-color:var(--pill);}100%{background-color:transparent;}}
/* Invisible exact-width copy of the sentence's line-lead — holds the first word at its
   original x so nothing re-wraps. Real text (visibility:hidden) → keeps layout width. */
.lead-indent{visibility:hidden;-webkit-user-select:none;user-select:none;pointer-events:none;}
/* Double-tapped plain word waiting for its remark (lazy re-markup of one word). */
.remark-wait{background:var(--pill);border-radius:3px;animation:remarkPulse 1.1s ease-in-out infinite;}
@keyframes remarkPulse{50%{opacity:.45;}}
/* (i)-кнопка словарной карточки (режим «карточка по 2-му тапу» ВЫКЛ): маленький кружок
   В СТРОКЕ ПОДСКАЗКИ-ПЕРЕВОДА над словом (сразу после текста перевода, в межстрочном
   зазоре — сбоку от слова она мешала чтению). НЕ ПЕРЕКРЫВАЕТ ТЕКУЩУЮ СТРОКУ: и низ
   видимого кружка, и низ хит-зоны прижаты к ВЕРХУ строки слова (элемент 36×27, кружок
   18px у нижнего края) — тапы по словам строки не перехватываются. Исчезает при скролле /
   смене слова / скрытии перевода. Вне #content — резеты книжного CSS её не задевают.
   Координаты — СТРАНИЧНЫЕ (absolute на body, тело не позиционировано → отсчёт от начала
   документа): двигает композитор вместе с текстом при скролле. От перекладки строк ПОСЛЕ
   установки (iOS 26 look-ahead) кнопку доводит короткий follow-цикл в JS (~0.7с каждый
   кадр по фактическому rect слова). Вариант «кнопка внутри глоссы» отброшен: элемент в
   потоке текста ломал жесты скролла и наследовал книжный контекст. */
#gloss-info{position:absolute;z-index:60;width:36px;height:27px;display:none;
     overflow-anchor:none;   /* не давать WebKit выбирать кнопку якорем скролла: её подвижки не должны «возвращать» страницу */
     -webkit-tap-highlight-color:transparent;cursor:pointer;}
#gloss-info .c{position:absolute;left:9px;bottom:0;width:18px;height:18px;border-radius:50%;
     background:var(--accent);color:#fff;font:italic 600 11px/17px Georgia,serif;text-align:center;
     box-shadow:0 1px 5px rgba(0,0,0,.25);transform:scale(.8);opacity:0;
     transition:transform .16s ease, opacity .16s ease;}
#gloss-info .c::before{content:'i';}   /* буква CSS-контентом: не текстовый узел внутри глоссы */
#gloss-info.on .c{transform:scale(1);opacity:1;}

/* ---- Постраничный режим (38-pages.js) ----
   Область текста — между полями панелей (--pg-top/--pg-bot задаёт Swift): текста под панелью нет.
   pg-on — показ: в потоке только части текущей страницы (.pg-cur во фрагментах .pg-vis), остальное
   display:none — без раскладки. pg-tail — последняя страница: «Конец», стена, ошибка или загрузка.
   pg-meas — замер раздела: его части целиком в одной раскладке, раскрытые глоссы сжаты до чистой
   вёрстки (разбиение не зависит от того, что раскрыто); pg-clean — то же сжатие при проверке
   страниц показом. */
body.paged{padding-top:var(--pg-top,132px)!important;padding-bottom:var(--pg-bot,130px)!important;}
/* overflow-x:hidden у #content заодно включает обрезку по вертикали (overflow-y считается auto), и
   подсказка над словом первой строки, которая выходит выше начала страницы, срезалась. clip режет
   только по горизонтали. */
body.paged #content{overflow-x:clip;overflow-y:visible;}
body.pg-on #content .frag:not(.pg-vis),
body.pg-on #content .pg:not(.pg-cur),
body.pg-on #tail{display:none!important;}
body.pg-on.pg-tail #tail{display:flex!important;flex-direction:column;justify-content:center;
     min-height:var(--pg-h,60vh);}
body.pg-on.pg-tail #tail .plum-loader{min-height:0;}
body.pg-meas #content .frag:not(.pg-ch),
body.pg-meas #content .pg:not(.pg-ch),
body.pg-meas #tail{display:none!important;}
body.pg-meas .gloss.revealed,body.pg-clean .gloss.revealed,.pg-cl .gloss.revealed{margin-left:0!important;margin-right:0!important;white-space:normal!important;
     display:inline!important;padding-left:0!important;padding-right:0!important;}
/* Картинка не режется и не выше страницы. У картинки в figure — без зазора под базовой линией
   (иначе иллюстрация высотой в страницу вылезает за неё на этот зазор). */
body.pg-on #content img,body.pg-meas #content img,
body.pg-on #content svg,body.pg-meas #content svg{max-height:var(--pg-h,100vh)!important;object-fit:contain;}
body.pg-on #content figure img,body.pg-meas #content figure img{vertical-align:top;}
/* Разрезанный блок: продолжение — без красной строки и верхних отступов (и без маркера у пункта
   списка), голова — без нижних; последняя строка головы абзаца — по ширине, как в середине абзаца.
   Верхний отступ не трогаем у абзаца под переводом предложения ([data-tear-orig]): его поднимает
   инлайновый отрицательный margin-top из _openTear, и !important здесь его бы перебил. */
[data-pg-cont]{padding-top:0!important;border-top-width:0!important;text-indent:0!important;}
[data-pg-cont]:not([data-tear-orig]){margin-top:0!important;}
li[data-pg-cont]{list-style-type:none;}
[data-pg-head]{margin-bottom:0!important;padding-bottom:0!important;border-bottom-width:0!important;}
[data-pg-head="j"]{text-align-last:justify;}
/* Страница начинается первой строкой и кончается последней. */
.pg-mt0:not([data-tear-orig]){margin-top:0!important;}
.pg-mb0{margin-bottom:0!important;}
/* Пустые абзацы после последней строки страницы (38-pages.js _pgHideTrail). */
.pg-trail{display:none!important;}

/* Стили серверной разметки header.v2-title/header.v2-chapter: такой html остался у части
   существующих книг в БД (импорт 08.2026) — правила обслуживают их вид, сам движок v2 удалён. */
.frag header.v2-title{text-align:center;margin:16vh 0 10vh;}
/* #content в селекторах — иначе базовое `#content p{text-align:justify}` (специфичность с id)
   перебивает и автор прижимается влево */
#content header.v2-title h1{font-size:1.55em;line-height:1.25;margin:0 0 .55em;text-align:center;}
#content header.v2-title p{opacity:.55;font-size:.95em;margin:0;text-align:center;text-indent:0;}
#content header.v2-chapter p{font-size:1.25em;font-weight:600;text-align:center;
     text-indent:0;margin:0;-webkit-hyphens:none;hyphens:none;}
