﻿/* Владелец (14.09.2026): "верхнее меню закрепи на месте при прокрутке -
   оно всегда должно быть на месте". */
.vp-admin-topbar { position: sticky; top: 0; z-index: 50; }
/* Tezisy-9 п.8: the ORIGINAL visitprussia.com's own main typeface is
   Bitter (a real slab serif, paired with Lato for its sans body text) -
   confirmed by fetching the old site's own compiled CSS directly
   (visitprussia.com was unreachable from this session's own browser/
   fetch tools all session, but reachable fine from the dev server's own
   outbound connection - fetched from there instead). "Примерили" it here
   as the owner asked, self-hosted from Google Fonts' real SIL-OFL-
   licensed release (fetched via the same dev-server route, not scraped
   from the old site's own hosted copy) - same self-hosting convention as
   Leaflet/brand icons/flags elsewhere in this project, nothing loads
   from an external font CDN at runtime. Replaces "Georgia" as the site's
   serif everywhere Georgia was already the deliberate choice (headings,
   card titles, the logo) - Georgia/Times New Roman kept as the fallback
   chain for the rare case the font fails to load, so nothing regresses.
   Only 2 static weights fetched (400/700, matching what this site's own
   CSS actually asks for - nothing here uses a heavier/lighter weight)
   even though the underlying files are a single variable-font release
   per subset (confirmed live: Google's own CSS points BOTH weight:400
   and weight:700 at the identical file per subset - the browser selects
   the right instance from within it, a normal/intentional pattern for a
   variable-font-backed Google Fonts family, not a mistake). */
@font-face{font-family:'Bitter';font-style:italic;font-weight:400;font-display:swap;src:url('/assets/vendor/fonts/bitter/bitter-italic-cyrillic-ext.woff2') format('woff2');unicode-range:U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;}
@font-face{font-family:'Bitter';font-style:italic;font-weight:400;font-display:swap;src:url('/assets/vendor/fonts/bitter/bitter-italic-cyrillic.woff2') format('woff2');unicode-range:U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;}
@font-face{font-family:'Bitter';font-style:italic;font-weight:400;font-display:swap;src:url('/assets/vendor/fonts/bitter/bitter-italic-vietnamese.woff2') format('woff2');unicode-range:U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;}
@font-face{font-family:'Bitter';font-style:italic;font-weight:400;font-display:swap;src:url('/assets/vendor/fonts/bitter/bitter-italic-latin-ext.woff2') format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;}
@font-face{font-family:'Bitter';font-style:italic;font-weight:400;font-display:swap;src:url('/assets/vendor/fonts/bitter/bitter-italic-latin.woff2') format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;}
@font-face{font-family:'Bitter';font-style:italic;font-weight:700;font-display:swap;src:url('/assets/vendor/fonts/bitter/bitter-italic-cyrillic-ext.woff2') format('woff2');unicode-range:U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;}
@font-face{font-family:'Bitter';font-style:italic;font-weight:700;font-display:swap;src:url('/assets/vendor/fonts/bitter/bitter-italic-cyrillic.woff2') format('woff2');unicode-range:U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;}
@font-face{font-family:'Bitter';font-style:italic;font-weight:700;font-display:swap;src:url('/assets/vendor/fonts/bitter/bitter-italic-vietnamese.woff2') format('woff2');unicode-range:U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;}
@font-face{font-family:'Bitter';font-style:italic;font-weight:700;font-display:swap;src:url('/assets/vendor/fonts/bitter/bitter-italic-latin-ext.woff2') format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;}
@font-face{font-family:'Bitter';font-style:italic;font-weight:700;font-display:swap;src:url('/assets/vendor/fonts/bitter/bitter-italic-latin.woff2') format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;}
@font-face{font-family:'Bitter';font-style:normal;font-weight:400;font-display:swap;src:url('/assets/vendor/fonts/bitter/bitter-normal-cyrillic-ext.woff2') format('woff2');unicode-range:U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;}
@font-face{font-family:'Bitter';font-style:normal;font-weight:400;font-display:swap;src:url('/assets/vendor/fonts/bitter/bitter-normal-cyrillic.woff2') format('woff2');unicode-range:U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;}
@font-face{font-family:'Bitter';font-style:normal;font-weight:400;font-display:swap;src:url('/assets/vendor/fonts/bitter/bitter-normal-vietnamese.woff2') format('woff2');unicode-range:U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;}
@font-face{font-family:'Bitter';font-style:normal;font-weight:400;font-display:swap;src:url('/assets/vendor/fonts/bitter/bitter-normal-latin-ext.woff2') format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;}
@font-face{font-family:'Bitter';font-style:normal;font-weight:400;font-display:swap;src:url('/assets/vendor/fonts/bitter/bitter-normal-latin.woff2') format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;}
@font-face{font-family:'Bitter';font-style:normal;font-weight:700;font-display:swap;src:url('/assets/vendor/fonts/bitter/bitter-normal-cyrillic-ext.woff2') format('woff2');unicode-range:U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;}
@font-face{font-family:'Bitter';font-style:normal;font-weight:700;font-display:swap;src:url('/assets/vendor/fonts/bitter/bitter-normal-cyrillic.woff2') format('woff2');unicode-range:U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;}
@font-face{font-family:'Bitter';font-style:normal;font-weight:700;font-display:swap;src:url('/assets/vendor/fonts/bitter/bitter-normal-vietnamese.woff2') format('woff2');unicode-range:U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;}
@font-face{font-family:'Bitter';font-style:normal;font-weight:700;font-display:swap;src:url('/assets/vendor/fonts/bitter/bitter-normal-latin-ext.woff2') format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;}
@font-face{font-family:'Bitter';font-style:normal;font-weight:700;font-display:swap;src:url('/assets/vendor/fonts/bitter/bitter-normal-latin.woff2') format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;}


/* Tezisy 1.9-2 п.1: the header nav's own typeface on the ORIGINAL site is
   'LatoBold' (`.menu{font-family:'LatoBold',...}`, confirmed the same way
   as Bitter above - old site's own compiled CSS, fetched via the dev
   server's outbound route). Self-hosted the exact webfont file the old
   site itself serves (not a Google Fonts re-fetch this time - the old
   site doesn't use Google's hosted Lato, it ships its own local woff/
   woff2 build) so the nav matches pixel-for-pixel, full Cyrillic coverage
   included (confirmed: the old nav's own labels are Cyrillic). */
@font-face{font-family:'LatoBold';font-style:normal;font-weight:700;font-display:swap;src:url('/assets/vendor/fonts/lato/lato-bold.woff2') format('woff2'),url('/assets/vendor/fonts/lato/lato-bold.woff') format('woff');}

/* Владелец 18.09.2026: PT Sans - шрифт по умолчанию для врезок (Bold
   для заголовка, Regular Italic для текста) - самостоятельно
   захостен тем же способом, что Bitter/Lato, без внешнего Google Fonts. */
@font-face{font-family:'PT Sans';font-style:italic;font-weight:400;font-display:swap;src:url('/assets/vendor/fonts/ptsans/ptsans-italic-cyrillic-ext.woff2') format('woff2');unicode-range:U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;}
@font-face{font-family:'PT Sans';font-style:italic;font-weight:400;font-display:swap;src:url('/assets/vendor/fonts/ptsans/ptsans-italic-cyrillic.woff2') format('woff2');unicode-range:U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;}
@font-face{font-family:'PT Sans';font-style:italic;font-weight:400;font-display:swap;src:url('/assets/vendor/fonts/ptsans/ptsans-italic-latin-ext.woff2') format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;}
@font-face{font-family:'PT Sans';font-style:italic;font-weight:400;font-display:swap;src:url('/assets/vendor/fonts/ptsans/ptsans-italic-latin.woff2') format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;}
@font-face{font-family:'PT Sans';font-style:normal;font-weight:400;font-display:swap;src:url('/assets/vendor/fonts/ptsans/ptsans-normal-cyrillic-ext.woff2') format('woff2');unicode-range:U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;}
@font-face{font-family:'PT Sans';font-style:normal;font-weight:400;font-display:swap;src:url('/assets/vendor/fonts/ptsans/ptsans-normal-cyrillic.woff2') format('woff2');unicode-range:U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;}
@font-face{font-family:'PT Sans';font-style:normal;font-weight:400;font-display:swap;src:url('/assets/vendor/fonts/ptsans/ptsans-normal-latin-ext.woff2') format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;}
@font-face{font-family:'PT Sans';font-style:normal;font-weight:400;font-display:swap;src:url('/assets/vendor/fonts/ptsans/ptsans-normal-latin.woff2') format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;}
@font-face{font-family:'PT Sans';font-style:normal;font-weight:700;font-display:swap;src:url('/assets/vendor/fonts/ptsans/ptsans-bold-cyrillic-ext.woff2') format('woff2');unicode-range:U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;}
@font-face{font-family:'PT Sans';font-style:normal;font-weight:700;font-display:swap;src:url('/assets/vendor/fonts/ptsans/ptsans-bold-cyrillic.woff2') format('woff2');unicode-range:U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;}
@font-face{font-family:'PT Sans';font-style:normal;font-weight:700;font-display:swap;src:url('/assets/vendor/fonts/ptsans/ptsans-bold-latin-ext.woff2') format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;}
@font-face{font-family:'PT Sans';font-style:normal;font-weight:700;font-display:swap;src:url('/assets/vendor/fonts/ptsans/ptsans-bold-latin.woff2') format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;}

* { box-sizing: border-box; }
/* Sticky footer (brief п.13): on a short page the footer still sits at
   the bottom of the viewport; on a page taller than the viewport it's
   normal document flow (appears only once scrolled to), never fixed.
   Plain flexbox column - no JS needed. */
html { height: 100%; }
body {
  margin: 0;
  min-height: 100%;
  display: flex;
  flex-direction: column;
  font-family: 'Bitter', Georgia, "Times New Roman", serif;
  color: #1a1a1a;
  /* Tezisy-2 п.1: the previous eyeballed darken+blue-tint overshot on
     both tone and color - owner color-picked the original site directly
     and gave the exact measured value, RGB(229,233,234). Using that
     measured value verbatim instead of computing our own blend. */
  background: #e5e9ea;
  line-height: 1.6;
  /* Belt-and-braces for the .vp-load-more full-bleed break-out below
     (calc(50% - 50vw) can overshoot by a scrollbar's width on some
     platforms) - never let that create a horizontal scrollbar. */
  overflow-x: hidden;
}
.vp-main { flex: 1 0 auto; }
.vp-site-footer { flex-shrink: 0; }
a { color: #7a2e2e; }
/* Дополнительный цвет (owner's "п.11") - the original site's own base
   link color (visitprussia.com's compiled CSS: literally `a{color:#2eaadc}`,
   used site-wide there) reused here as the universal HOVER accent for
   teaser titles, header/footer nav items, and body hyperlinks - never
   the resting color, which stays the site's own #7a2e2e brand maroon. */
a:hover { color: #2eaadc; }
.vp-site-header {
  /* Tezisy-3 п.4: 10% lighter again (blend 10% white into #ccc/204,204,204):
     204*0.9 + 255*0.1 = 183.6 + 25.5 = 209.1 -> 209 = #d1d1d1 */
  border-bottom: 1px solid #d1d1d1;
  background: #fff;
}
.vp-site-header-inner {
  max-width: 1100px;
  margin: 0 auto;
  /* Tezisy-2 п.3: 5mm of free space above the header content (real
     physical unit, not a px guess - browsers resolve mm at a fixed
     96px/25.4mm, i.e. ~18.9px here). */
  padding: 5mm 24px 14px;
  position: relative;
}
/* Владелец 07.10.2026: "что-то есть выше логотипа, язык/Aa должны идти
   в один уровень с ВЕРХОМ логотипа" - раньше это была отдельная строка
   flex-колонки НАД .vp-header-main (10px вертикального зазора + своя
   высота ~30px, итого ~40px чистого воздуха, которого нет в макете).
   Замер макета (gridline-кроп, PowerShell+System.Drawing): рамки RU/Aa
   по вертикали перекрывают верхнюю строку логотипа ("KALININGRAD"), а
   не висят отдельным рядом над всем лого-блоком. Поэтому теперь
   position:absolute внутри .vp-site-header-inner (которому нужен
   position:relative выше) - left/right:24px повторяют те же 24px
   padding родителя (offset абсолютного потомка считается от padding-
   box родителя, который УЖЕ включает этот padding - те же left:311px у
   .vp-lang-dropdown и margin-left:auto у .vp-aa-toggle ниже продолжают
   работать без изменений, т.к. ширина строки та же, что и раньше).
   .vp-header-main остаётся единственным потоковым ребёнком - его
   верхний край теперь = padding-top родителя (5mm), лого поднялось на
   те самые ~40px, что и сократило общую высоту шапки под макет.
   Мобильная версия (@media max-width:600px) намеренно возвращает
   position:static - там это по-прежнему отдельная строка над лого,
   макет был только десктопный. */
.vp-header-utility {
  /* Владелец 07.10.2026: "кнопки Выбор языка и Размер шрифта надо
     сверху выровнять по верхнему краю лого" - точное правило вместо
     подобранного на глаз top:33px. .vp-header-main (и с ним лого) не
     имеет собственного margin-top - его видимый верх начинается ровно
     там, где заканчивается padding-top родителя (.vp-site-header-inner,
     5mm). У абсолютно спозиционированного потомка top тоже считается от
     того же padding-top родителя (см. комментарий выше про padding-box)
     - значит то же самое 5mm здесь даёт ТОЧНОЕ совпадение верхних
     краёв, а не близкое приближение.
     Владелец 07.10.2026 (следом): "кнопки надо синхронно опустить на
     пару пикселов" - оба (язык и Aa) - дети этого контейнера, поэтому
     один сдвиг держит их синхронными сам по себе; +2px поверх точного
     5mm, не взамен него. */
  position: absolute;
  top: calc(5mm + 2px);
  left: 24px;
  right: 24px;
  display: flex;
  align-items: center;
  gap: 14px;
  font-family: system-ui, sans-serif;
  font-size: 13px;
  /* Владелец 07.10.2026: "в десктопной версии ВСЯ площадь логотипа
     должна быть кликабельной ссылкой на Главную - сейчас курсор
     становится стрелкой на слове KALININGRAD". Этот div растянут на
     всю ширину контента (left/right:24px, см. выше) ради ТОЧНОГО
     совпадения с верхом лого - но раз он position:absolute, он рисуется
     ВЫШЕ обычного .vp-logo--new по умолчанию (абсолютно
     спозиционированные элементы всегда поверх статичных, независимо от
     порядка в DOM), и его собственная пустая область (между/вокруг
     RU-флага и Aa, где ничего не нарисовано) всё равно перехватывает
     курсор/клики - ровно там, где под ней начинается верхняя строка
     "KALININGRAD". pointer-events:none на самом div пропускает клики
     насквозь; auto на двух настоящих кнопках ниже возвращает им
     кликабельность (pointer-events наследуется, иначе они тоже
     перестали бы работать). */
  pointer-events: none;
}
.vp-header-utility > * { pointer-events: auto; }
/* Tezisy 2026-09-03 п.2: language switcher moved from the right edge to
   the left, next to the logo, staying in this same utility row (same
   height as before) - aligned so its own left edge lines up with the
   first letter of the main nav's first item below. That nav sits at
   .vp-logo's width (56px) + .vp-nav's own margin-left (47.25px) + its
   own position:relative left offset (calc(0.8cm + 15px)) from
   .vp-header-main's left edge - both rows share the same left edge
   inside .vp-site-header-inner, so the same offset applies here. */
/* Завершая сессию 4.9.26 п.1: +45px, making room for a wider/horizontal
   logo redesign coming later - moved in lockstep with .vp-nav's own
   +45px below (same "left" visual-offset pattern, kept as its own
   addend rather than folded into the base calc so this specific delta
   stays easy to find/adjust again later).
   Тезисы 6.9.26 п.1: +20px more, same lockstep pattern with .vp-nav
   below.
   Десктопные тезисы 8.9.26 п.3: +6px more, again in lockstep - this one
   is not an eyeballed nudge but an exact alignment: measured live at
   1440px, the nav/lang pair sat at x=400 and the footer's first link
   column at x=406, so 6px lands them on one vertical line.
   Владелец 07.10.2026: "выбор языка явно не по макету расположен" -
   вся формула выше (56px логотипа-якоря + накопленные сдвиги) была
   завязана на СТАРЫЙ логотип (см. тот же разбор у .vp-nav ниже) и
   перестала иметь смысл с новым - lang-dropdown должен просто совпадать
   по X с .vp-nav (та же цель "выровнять с первой колонкой футера", её
   .vp-nav теперь держит сам, без старого сдвига). Значение замерено
   живьём на реальном рендере ПОСЛЕ починки .vp-nav, а не пересчитано
   заново с нуля - проще и надёжнее, чем перестраивать всю цепочку
   calc(). */
/* Владелец 17.10.2026: переключатель языка встал на прежнее место «Аа» -
   к правому краю ряда (auto margin, как было у «Аа»), а «Аа» ушла в
   плавающую кнопку внизу справа (.vp-aa-float). */
.vp-lang-dropdown { margin-left: 311px; }
/* Владелец 17.10.2026 (следом): язык вернулся на родное место у логотипа
   (311px - прежнее значение), в правом углу - виджет «Дата и погода». Если
   главное меню перенеслось в 3+ строки (класс .vp-nav-wrapped ставит скрипт
   в layout.php), виджет прячется, а язык уходит на его место вправо. */
.vp-dw {
  margin-left: auto;
  display: flex; align-items: center; gap: 14px;
  font-family: Arial, Helvetica, sans-serif; font-size: 13px; color: #444;
  white-space: nowrap;
}
.vp-dw-wx-wrap:empty { display: none; }
.vp-dw-wx { display: inline-flex; align-items: center; gap: 6px; color: inherit; text-decoration: none; }
.vp-dw-wx:hover { color: #7a2e2e; }
.vp-dw-icon { width: 22px; height: 22px; flex-shrink: 0; }
.vp-dw-t { font-weight: 700; }
.vp-dw-extra { color: #777; }
/* Владелец 17.10.2026: «17 октября 2026, Сб / ⛅ +14°» - косая черта только
   когда погода есть. */
.vp-dw { gap: 8px; }
.vp-dw-sep { color: #999; }
.vp-dw-sep:has(+ .vp-dw-wx-wrap:empty) { display: none; }
/* Владелец 18.10.2026: текст обеих частей виджета - на одной «полке» (базовой
   линии) с буквами RU в кнопке языка. Внутри виджета всё по базовой линии,
   значок погоды опущен на 6px (центр - на середине заглавных букв); весь
   виджет поднят на 3.01px - замер по текстовому узлу (Range + fontBoundingBoxAscent):
   базовая линия RU 40.83px, виджета 43.84px. (18.10 днём было -7.93px по ошибочному
   замеру: вспомогательный span внутри flex-summary выравнивался отдельно от текста.) */
.vp-dw, .vp-dw-wx { align-items: baseline; }
.vp-dw-icon { position: relative; top: 6px; }
@media (min-width: 601px) {
  .vp-dw { position: relative; top: -3.01px; }
}
@media (min-width: 601px) {
  .vp-nav-wrapped .vp-dw { display: none; }
  .vp-nav-wrapped .vp-lang-dropdown { margin-left: auto; }
  .vp-nav-wrapped .vp-lang-menu { left: auto; right: 0; }
}
/* Владелец 17.10.2026: телефон, повёрнутый горизонтально (низкий экран,
   сенсор) - меню там в 3 строки, виджет показывается целиком ОТДЕЛЬНОЙ
   строкой над логотипом: шапка опускается на 28px, строка с языком - вместе
   с ней, виджет встаёт в освободившуюся полосу по левому краю лого. */
@media (orientation: landscape) and (max-height: 500px) and (hover: none) and (pointer: coarse) and (min-width: 601px) {
  .vp-site-header-inner { padding-top: calc(5mm + 28px) !important; }
  .vp-header-utility { top: calc(5mm + 2px + 28px); }
  .vp-site-header .vp-dw,
  .vp-site-header.vp-nav-wrapped .vp-dw { display: flex; position: absolute; top: -28px; left: 0; margin: 0; }
}
/* Владелец 17.10.2026: «защитить контент от копирования» - на публичных
   страницах текст не выделяется, долгое нажатие на фото (iOS) не даёт
   сохранить его; поля ввода работают как обычно. См. скрипт в layout.php. */
/* только body.vp-public (ставит inc/layout.php) - редактор статей в админке
   тоже подключает этот style.css, его не трогаем. */
/* Владелец 18.10.2026: «пока вернёмся к полной открытости» - запрет
   копирования снят (правила user-select/touch-callout/user-drag удалены,
   скрипт в layout.php тоже). Класс body.vp-public оставлен - безвреден. */
/* Tezisy 2026-09-03 п.3: font-size button moved into the spot the
   language switcher vacated (far right of this row) - auto margin
   pushes it flush right regardless of the dropdown's own width, which
   lines it up with .vp-search-icon's right edge below (that icon sits
   at the true right edge of .vp-header-main too, since .vp-nav's own
   flex:1 consumes all the remaining space before it). */
.vp-aa-toggle { margin-left: auto; }
/* Вечерние тюн-тезисы 2026-09-03 п.8: this button's own frame made
   equal-size with .vp-lang-dropdown summary's (measured live: lang was
   naturally ~60x28 from its flag+text content, Aa ~36x35 from just two
   letters - different fonts/content give different natural sizes even
   off IDENTICAL padding/border/font-size, as these two already shared).
   min-width (not a hard width) so neither box can shrink below this,
   but a future longer language label still isn't forced to clip -
   64x30 comfortably covers both today's actual content and a bit of
   slack. The Aa letters end up with real breathing room inside the
   now-wider box, per the explicit ask (font-size itself untouched -
   "decide later"). */
.vp-aa-toggle, .vp-lang-dropdown summary { min-width: 64px; height: 30px; box-sizing: border-box; justify-content: center; }
/* Вечерние тезисы 4.9.26 п.8: "decide later" from a couple tezisy back,
   now decided - up from 13px so the Аа letters read at the same visual
   size as the language button's own text beside it (Bitter serif bold
   at a shared 13px still looked smaller than system-ui - matching
   px doesn't mean matching apparent size across different typefaces). */
.vp-aa-toggle {
  font-family: 'Bitter', Georgia, serif;
  font-weight: 700;
  font-size: 16px;
  color: #444;
  background: #fff;
  border: 1px solid #ccc;
  border-radius: 4px;
  padding: 3px 9px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
}
.vp-aa-toggle:hover { border-color: #7a2e2e; color: #7a2e2e; }
.vp-lang-dropdown { position: relative; }
.vp-lang-dropdown summary {
  list-style: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: system-ui, sans-serif;
  font-size: 13px;
  color: #444;
  background: #fff;
  border: 1px solid #ccc;
  border-radius: 4px;
  padding: 3px 9px;
}
/* Real flag images (assets/vendor/flags/, self-hosted) instead of
   Unicode flag emoji - Windows browsers commonly have no color-flag
   glyphs and silently fall back to the raw two-letter codes as plain
   text, which is not a look, so a real <img> is used everywhere a flag
   should show up regardless of the visitor's OS/font. */
.vp-flag-icon { width: 18px; height: 13px; object-fit: cover; border-radius: 2px; flex-shrink: 0; vertical-align: middle; }
/* Владелец 18.10.2026: национальные флаги из выбора языка убраны. В списке -
   квадрат высотой с заглавную букву (cap height system-ui ~0.7em), серый, как
   выключенные кружки легенды карты (#8f8f8f); в кнопке - слегка вытянутый по
   горизонтали треугольник «Play» цвета KOENIGSBERG из логотипа (#7d7d7d,
   замер по PNG). Знак стоит вровень с заглавными буквами (низ - на базовой линии). */
.vp-lang-mark { display: inline-block; flex-shrink: 0; width: calc(.7em + 1px); height: calc(.7em + 1px); background: #8f8f8f; position: relative; } /* владелец 18.10.2026 (следом): квадраты на пиксел больше по ширине и высоте */
/* Владелец 18.10.2026 (следом): треугольник - вторым цветом сайта, cyan #2eaadc
   (тот же, что у наведения на заголовки карточек и ссылки футера). */
.vp-lang-mark--play { width: .82em; height: .7em; background: #2eaadc; clip-path: polygon(0 0, 100% 50%, 0 100%); top: .45px; }
.vp-lang-dropdown summary::-webkit-details-marker { display: none; }
.vp-lang-dropdown summary:hover { border-color: #7a2e2e; color: #7a2e2e; }
.vp-lang-dropdown[open] summary { border-color: #7a2e2e; color: #7a2e2e; }
.vp-lang-menu {
  position: absolute;
  /* Вечерние тезисы 2026-09-03 п.1: opens toward the LEFT edge (right:0
     anchored the menu's own right edge to the button, so it grew
     leftward) - now that the button itself lives next to the logo (see
     .vp-lang-dropdown's own margin-left above), growing leftward ran the
     ~230px-wide menu straight over the logo. left:0 flips it to grow
     rightward instead, over the empty space toward the nav. */
  left: 0;
  /* Владелец 17.10.2026: у правого края (.vp-nav-wrapped) меню растёт влево - см. правило рядом с .vp-dw. */
  top: calc(100% + 6px);
  background: #fff;
  border: 1px solid #ddd;
  border-radius: 6px;
  box-shadow: 0 6px 20px rgba(0,0,0,0.14);
  /* Владелец 02.10.2026 ("можно элегантнее?"): полные родные названия
     языков вместо двухбуквенных кодов, и список - ОДНИМ столбцом, не
     сеткой. Сетка (которая и была источником "лишнего белого поля" -
     колонки были рассчитаны под сетку из 8-9 ДВУХБУКВЕННЫХ кодов, а
     не под 4 полных слова) больше не нужна: 4 пункта столбиком - самый
     обычный, самый "изящный" вид для списка НАЗВАНИЙ (в отличие от
     кодов, где компактная сетка оправдана). min-width подобран под
     самое длинное слово (LIETUVIŲ) + флаг + свои отступы - без лишнего
     запаса, который раньше давала сетка. */
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 8px;
  /* Владелец 02.10.2026, доводка: справа от самого длинного слова
     (DEUTSCH) всё ещё оставалось лишнее поле - подрезано на 20px
     (150 -> 130), для начала. */
  min-width: 130px;
  /* Leaflet's own internal panes (markers/popups/tooltips) use z-index
     values up to 700, and .leaflet-container only sets position:relative
     with no z-index of its own - so it never forms a stacking context
     that contains those values, and they leak out to compete directly
     against ordinary page elements. 20 lost to that; comfortably clear
     of anything Leaflet uses. */
  z-index: 1000;
}
.vp-lang-menu a {
  display: flex;
  align-items: center;
  gap: 8px;
  text-decoration: none;
  color: #444;
  font-size: 12.5px;
  letter-spacing: .03em;
  padding: 6px 8px;
  border-radius: 4px;
  white-space: nowrap;
}
.vp-lang-menu a:hover { background: #f1e9e4; }
@media (max-width: 480px) {
  /* Владелец 02.10.2026: столбец теперь один при любой ширине (см. выше) -
     от сетки здесь раньше была только колонка, сам отступ left:-24px
     (чтобы список не вылезал за правый край экрана) остаётся. */
  .vp-lang-menu { left: -24px; }
}
.vp-header-main {
  display: flex;
  align-items: center;
}
.vp-logo {
  font-weight: bold;
  font-size: 20px;
  letter-spacing: 0.05em;
  text-decoration: none;
  color: #1a1a1a;
  white-space: nowrap;
  /* Real Amber City logo replacing the "NEW LOGO" text placeholder. Fixed
     56x56 footprint (the header row's own original sizing need) that the
     actual image - larger, absolutely positioned, centered on this box -
     is deliberately allowed to spill out of on every side, per the
     owner's own "вылазит за хедер" ask, without growing the header bar
     itself (the row's height still comes from the nav/search-icon text,
     never from the now-oversized logo). */
  position: relative;
  width: 56px;
  height: 56px;
  flex-shrink: 0;
}
/* Owner: rotate CLOCKWISE (not counter-clockwise as first tried), and
   bigger - big enough to visibly overflow the header bar's own edge
   above and below. transform-origin stays centered (the default) so the
   rotation doesn't shift the visual center point away from .vp-logo's
   own anchor box. */
.vp-logo-img {
  position: absolute;
  top: 50%; left: 50%;
  height: 120px;
  width: auto;
  z-index: 2;
  /* Owner's own DevTools tweak: bigger (was 80px) and off-center (was
     -50%,-50%) so the badge spills further right of its anchor.
     Follow-up: 15px further up, then lowered 5px back down (net 10px). */
  transform: translate(-30%, calc(-50% - 10px)) rotate(25deg);
}
/* Владелец 06.10.2026: новый логотип "Kaliningrad & Koenigsberg" - широкий
   горизонтальный лейбл (не круглый значок), обычный инлайн-флекс-элемент
   в .vp-header-main (без absolute/rotate трюков старого .vp-logo-img) -
   высотой, не шириной, остальное по пропорции самого PNG. Старые
   .vp-logo/.vp-logo-img выше НЕ трогались - рабочий откат на старый
   логотип, если новый не подойдёт. */
.vp-logo--new {
  display: flex;
  align-items: center;
  flex-shrink: 0;
}
.vp-logo-img--new {
  display: block;
  /* Владелец 06.10.2026: "крупнее на макете" - на макете лого занимает
     ~105px высоты (замерено по приложенному скриншоту хедера) против
     52px первой прикидки - ровно вдвое. */
  height: 104px;
  width: auto;
}
/* Владелец 07.10.2026: мобильный вариант логотипа (без девиза, см.
   kaliningrad-koenigsberg-mobile.png) - скрыт по умолчанию, десктопная
   версия видна. Брейкпоинт ниже меняет это местами. */
.vp-logo-img--mobile { display: none; }
/* Владелец 07.10.2026: "Доработать админку чтобы скрытие [пункта меню]
   было отдельно для двух видов устройств" - пункт остаётся в разметке
   всегда, прячется только визуально нужным брейкпоинтом (ссылка
   остаётся рабочей по прямому адресу, боты её тоже видят) - то же
   place-in-DOM-always решение, что уже применяется у map/kiosk через
   feature_*_visible, просто на уровне одного пункта и per-device, а не
   всего сайта. .vp-nav на десктопе flex, на мобильном grid - в обоих
   display:none просто убирает элемент из потока, остальные
   переставляются сами (никакой "дыры", ровно то, из-за чего Экскурсии
   нарочно остаются в мобильном меню несмотря на желание убрать их с
   десктопа). */
@media (min-width: 601px) {
  .vp-nav-hide-desktop { display: none; }
}
@media (max-width: 600px) {
  .vp-nav-hide-mobile { display: none; }
}
@media (max-width: 600px) {
  /* Владелец 07.10.2026: "логотип ставим по ширине контентного блока" +
     "под логотипом в мобильной версии не будет девиза" - мобильный
     вариант картинки (без девиза) растягивается на всю ширину своего
     контейнера (.vp-logo--new и так уже стретчится на всю ширину
     .vp-header-main в этом же брейкпоинте, см. flex-direction:column
     ниже) - width-based, не height-based масштабирование, в отличие от
     десктопа. */
  .vp-logo-img--new { display: none; }
  .vp-logo-img--mobile { display: block; width: 100%; height: auto; }
}
.vp-nav {
  display: flex;
  flex-wrap: wrap;
  /* Tezisy-5 п.1: gaps between nav items x0.95 (was 7px/31.5px). */
  gap: 6.65px 29.925px;
  flex: 1;
  /* Tezisy п.2: logo-to-menu gap = 1.5x the gap BETWEEN menu items
     (31.5px), i.e. 47.25px - can't come from .vp-header-main's own
     shared flex `gap` any more (that applied the same value to every
     pair of children, logo<->nav AND nav<->search-icon alike), so it's
     now an explicit margin on .vp-nav itself instead. */
  margin-left: 47.25px;
  /* Tezisy 1.9-3 п.3: nudged 3px down + 0.8cm right - position:relative
     (not margin) so it's a pure visual offset, not affecting the logo or
     search icon's own position or the header row's height.
     Follow-up: another 15px further right.
     Вечерние тезисы 2026-09-03 п.4: the header-utility row work earlier
     today left this nav sitting noticeably higher than before (and than
     .vp-search-icon beside it) - 10px back down as a first correction:
     3px -> 13px.
     Завершая сессию 4.9.26 п.1: +45px right, in lockstep with
     .vp-lang-dropdown's own +45px above - making room for a wider/
     horizontal logo redesign coming later.
     Тезисы 6.9.26 п.1: +20px more, same lockstep pattern.
     Десктопные тезисы 8.9.26 п.3: +6px, aligning this pair to the
     footer's first link column (see .vp-lang-dropdown above).
     Владелец 06.10.2026: весь этот накопленный сдвиг компенсировал то,
     что старый логотип был крошечным невидимым 56x56 якорем с большой
     картинкой АБСОЛЮТНО спозиционированной поверх (вылезающей за рамки,
     не участвующей в потоке flex) - реальная ширина логотипа на
     расположение nav вообще не влияла, поэтому сдвиг и пришлось
     подбирать вручную. Новый логотип - обычный flex-элемент настоящей
     ширины (~264px), сам толкает nav вправо потоком - старый сдвиг
     поверх этого даёт двойной эффект и вылезает за контейнер (последний
     пункт меню наезжает на иконку поиска). Убран - остались только
     margin-left (зазор логотип-меню) и top (вертикальная правка, к
     ширине не относится). */
  /* Владелец 07.10.2026: "главное меню надо снизу выровнять по нижнему
     краю девиза" - девиз ("AB EXTERIORIBUS...") - последняя строка
     лого-картинки, т.е. её собственный нижний край. .vp-header-main
     центрирует детей по умолчанию (align-items:center), и это лого -
     самый высокий элемент ряда, так что сам контейнер ровно той же
     высоты, что и лого - значит "выровнять по низу контейнера" даёт
     ровно нужный результат. align-self:flex-end - точное правило
     вместо прежнего eyeballed position:relative;top:13px (откуда
     взялось то самое 13px - уже не воспроизводимо, просто подбор под
     тогдашний старый логотип). */
  align-self: flex-end;
}
.vp-nav a {
  text-decoration: none;
  /* Tezisy 1.9-2 п.1: real old-site nav typeface, see the @font-face
     above - 'OpenSansBold'/'Open Sans Bold' kept as the old site's own
     fallback chain before the generic sans-serif. */
  font-family: 'LatoBold', 'OpenSansBold', 'Open Sans Bold', sans-serif;
  font-size: 14px;
  font-weight: 700;
  /* Tezisy-5 п.1: another 10% lighter on top of #858585 (133,133,133):
     133*0.9 + 255*0.1 = 119.7 + 25.5 = 145.2 -> 145 = #919191 */
  color: #919191;
  text-transform: uppercase;
  /* Tezisy 1.9-2 п.1: old site's own .menu .nav>li uses -0.1px, tighter
     than the previous 0.03em (~0.42px at 14px) - part of "the same font"
     look, not a separate ask. */
  letter-spacing: -0.1px;
}
.vp-nav a:hover { color: #2eaadc; }
/* Current section (its section.php / article.php page) gets the
   accent color permanently, not just on hover, plus an underline. */
.vp-nav a.active { color: #2eaadc; text-decoration: underline; text-underline-offset: 4px; }
.vp-search-icon {
  font-size: 16px; opacity: 0.6; background: none; border: none; padding: 4px; cursor: pointer;
  font-family: inherit; line-height: 1;
  /* Was the .vp-header-main shared flex `gap` (28px) before that became
     nav-specific margin above - kept the same visual spacing here. */
  margin-left: 28px;
  /* Tezisy 1.9-2 п.8: .vp-nav got its own position:relative;top:3px a few
     tezisy back (a pure visual offset, doesn't move it within the shared
     flex row) - this button never got the same nudge, so it now sits 3px
     higher than the nav text beside it. Matching offset here (both icon
     states - magnifier and the close-X share this one button) puts it
     back on the same row.
     Вечерние тюн-тезисы 2026-09-03 п.7: .vp-nav's own top grew to 13px in
     the meantime (the header-utility-row rework's regression fix, a
     couple tezisy back) without this icon following along again -
     measured live (.vp-nav a top=93px vs this icon's top=83px in the
     current layout): a 10px gap, so this matches .vp-nav's OWN value
     exactly rather than re-guessing a new offset from scratch. */
  /* Тот же переход на align-self:flex-end, что и у .vp-nav выше (07.10.2026)
     - раньше этот top:13px существовал только чтобы совпасть с
     .vp-nav'овым собственным top:13px и остаться с ним на одном уровне;
     теперь оба используют одно и то же точное правило, так что остаются
     на одном уровне автоматически, без дублирования магического числа. */
  align-self: flex-end;
}
.vp-search-icon:hover { opacity: 1; }
/* Tezisy 1.9-2 п.8: the close-X (search-close.png, injected by
   inc/layout.php's JS) doubled from 16x16 - was rendering visibly
   smaller than the nav's own uppercase letters beside it once the row
   was aligned; a plain CSS size (rather than the img tag's own width/
   height attributes) so both stay easy to tune together later. */
.vp-search-icon img { width: 32px; height: 32px; display: block; }
/* Десктопные тезисы 8.9.26 п.9: REGRESSION FIX (mine, from 7.9.26).
   The mobile-only search button added to .vp-header-utility for мобильный
   п.1 was only ever STYLED inside the max-width:600px block - it was never
   hidden above it, so every desktop page showed two search icons stacked
   in the same column (the utility row's one right above .vp-header-main's).
   Hidden by default here; the mobile block re-shows it with display:inline-flex. */
.vp-search-icon--mobile { display: none; }
/* Мобильные тезисы 8.9.26 п.3: "Главная" is a mobile-only menu item -
   hidden in the base cascade, shown inside the max-width:600px block.
   (Same base-hide pattern as .vp-search-icon--mobile right above, which
   is exactly what desktop п.9 had to fix when I forgot it.) */
.vp-nav-home { display: none; }
/* Full-width bar under the header (brief п.17, design copied from the
   original site: a plain #2eaadc banner with a text input + magnifying
   glass button). Toggled by the header's search icon, closes on
   click-outside (brief п.16) - see the script in vp_footer(). */
/* Tezisy-5 п.3: same bug shape as the footer-width fix (tezisy-4) -
   max-width alone on .vp-search-form with the matching padding living on
   the OUTER .vp-search-bar instead meant the search bar's visible input
   row spanned the full 1100px while header/main's own content was inset
   by 24px padding on the SAME element - a real, measurable mismatch at
   viewports >=1100px. Padding moved off .vp-search-bar (vertical only
   now) onto .vp-search-form itself, alongside its own max-width. */
.vp-search-bar { background: #2eaadc; padding: 14px 0; }
.vp-search-form { max-width: 1100px; margin: 0 auto; padding: 0 24px; display: flex; gap: 8px; }
.vp-search-input {
  flex: 1; padding: 10px 14px; border: none; border-radius: 4px;
  font: inherit; font-size: 15px;
}
.vp-search-submit {
  border: none; border-radius: 4px; background: #707173; color: #fff;
  width: 44px; font-size: 16px; cursor: pointer;
}
.vp-search-submit:hover { background: #8b8c8f; }
.vp-search-submit { display: inline-flex; align-items: center; justify-content: center; }
/* владелец 21.10.2026: лупа на серой кнопке +10% (24 -> 26.4px) */
.vp-search-submit .vp-search-icon-svg { width: 26.4px; height: 26.4px; }
/* Tezisy 1.9-3 п.6: real search results - thumbnail/date/title/fragment/
   author, one horizontal row per result, matching the old site's own
   result format instead of the generic card grid. */
.vp-search-results { display: flex; flex-direction: column; gap: 24px; margin: 28px 0 60px; }
/* Tezisy 2026-09-03 п.4: on the personal author page specifically, the
   gap from the contributor-card frame down to the first thumbnail here
   must equal .vp-main's own 40px top padding (the gap from the header
   divider down to that same frame) - overrides just the top value from
   .vp-search-results above (same specificity, wins on source order). */
.vp-contributor-publications { margin-top: 40px; }
/* Владелец (13.09.2026, "Библия"): на персональных страницах авторов,
   когда публикаций больше одной, расстояние от нижнего блока (представление
   САМОЙ ПЕРВОЙ публикации автора на сайте - раз список идёт от новых к
   старым, она внизу списка) до футера - не менее 100px. Базовые
   .vp-search-results дают 60px снизу - этого мало, только когда
   публикация ровно одна такого требования нет (список короче на глаз,
   отдельно не оговорено). */
/* Специфичность в два класса, не один - .vp-section-feed (строка 704,
   margin: 28px 0 0 - обнуляет margin-bottom) идёт ПОЗЖЕ в файле и с
   одним классом побеждал бы по порядку каскада при равной специфичности.
   Тот же паттерн, что уже несколько раз ловился в админке этой сессией. */
.vp-contributor-publications.vp-contributor-pubs-multi { margin-bottom: 100px; }
.vp-search-result { display: flex; gap: 20px; padding-bottom: 24px; border-bottom: 1px solid #eee; position: relative; }
.vp-search-result:last-child { border-bottom: none; }
/* Владелец 07.10.2026: страница автора, подшитая первой в результаты
   поиска по его фамилии - тот же .vp-search-result, но с круглым фото
   (как на самой contributor.php, см. .vp-contributor-photo) и лёгкой
   подсветкой фона, чтобы визуально читалось как "это не просто ещё один
   результат", а отдельная, более важная карточка. */
.vp-search-result--contributor {
  background: #f8f4f0; border-radius: 10px; padding: 16px; margin: 0 0 20px;
  border-bottom: none;
}
.vp-search-result-thumb--contributor { border-radius: 50%; overflow: hidden; }
.vp-search-result-thumb--contributor .vp-search-result-img { border-radius: 50%; }
.vp-search-result-contributor-label {
  display: inline-block; font-family: system-ui, sans-serif; font-size: 12px;
  text-transform: uppercase; letter-spacing: 0.05em; color: #a35c2e; font-weight: 600;
  margin-bottom: 4px;
}
/* Tezisy 1.9-2(2) п.5: contributor.php's own reuse of this component
   (see vp_render_search_result()'s $show_share param) adds a share
   widget in the block's bottom-right corner, matching the real old-site
   markup's own .search-result-info row (author name left, share icons
   right) - search.php itself never sets $show_share, so this rule is
   simply absent there. */
.vp-search-result-share { position: absolute; right: 0; bottom: 24px; }
.vp-search-result-thumb { flex-shrink: 0; display: block; width: 200px; height: 130px; }
.vp-search-result-img { width: 100%; height: 100%; object-fit: cover; display: block; }
.vp-search-result-body { flex: 1; min-width: 0; font-family: system-ui, sans-serif; }
.vp-search-result-date {
  display: block; color: #999; font-size: 13px; margin-bottom: 4px;
}
.vp-search-result-body h3 {
  margin: 0 0 8px; font-size: 19px; font-family: 'Bitter', Georgia, serif; line-height: 1.3;
}
.vp-search-result-body h3 a { color: #1a1a1a; text-decoration: none; }
.vp-search-result-body h3 a:hover { color: #2eaadc; }
.vp-search-result-snippet {
  margin: 0 0 8px; color: #555; font-size: 14.5px; line-height: 1.55;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
.vp-search-result-snippet strong { color: #1a1a1a; font-weight: 700; }
@media (max-width: 600px) {
  .vp-search-result { flex-direction: column; gap: 10px; }
  .vp-search-result-thumb { width: 100%; height: 180px; }
}
.vp-main {
  /* Tezisy п.13: header/content/footer must share ONE working width on
     EVERY page, no exceptions - header (.vp-site-header-inner) and
     footer (.vp-footer-inner) were already both 1100px; .vp-main used to
     split into a narrower 720px "reading width" (plain pages) vs a
     1100px ".vp-main--wide" (section/map/etc.), which the brief now
     rules out entirely. Unified to 1100px for every page: the --wide
     class below is kept only so no call site needs editing, but is now
     a harmless no-op (both resolve to the same max-width). */
  max-width: 1100px;
  margin: 0 auto;
  /* Tezisy-4 п.2: gap from the content block to the footer (or to the
     gray "load more" bar when it's present) x5 - tezisy-2 п.6 had taken
     this x0.1 (80px -> 8px) as a distinct, smaller edit; this is a fresh
     x5 off THAT value (8 * 5 = 40), not a reversal of it. */
  padding: 40px 24px 40px;
  /* Needed since the sticky-footer flexbox change made <main> a flex
     item: align-items:stretch should give it full cross-axis width on
     its own, but when <main> contains a CSS Grid with an "1fr" track
     whose content is entirely position:absolute (map.php's #vp-map -
     Leaflet's own tiles/markers never contribute intrinsic width), the
     browser falls back to sizing <main> off that grid's min-content
     instead of stretching it - collapsing the 1fr column to ~2px.
     Forcing width:100% here (verified live via a temporary debug probe:
     this alone fixed the grid's "220px 1fr" resolving to "220px 2px")
     sidesteps the whole min-content path; max-width+margin:auto still
     centers it exactly as before once width:100% exceeds the cap. */
  width: 100%;
}
.vp-main--wide { max-width: 1100px; }
/* Tezisy-4 п.1: Home page top section - left 2/3 is the Cover Story card
   (vp_render_cover_card(), same DOM shape as vp_render_article_card()
   just under a .vp-card--cover modifier), right 1/3 stacks the
   Excursions promo box + the new pull-quote block. Replaces the old
   hero+teasers .vp-home-grid (that markup no longer exists - hero_article
   now renders a standard card, not a bespoke <h1> hero).
   Tezisy-6 (end-of-summer session) п.1: "2fr 1fr" was only an
   APPROXIMATION of two-thirds - a single 44px gap for 2 tracks doesn't
   consume the same space as the real standard grid's two 16px gaps for
   3 tracks, so Cover Story was never actually exactly as wide as two
   standard columns. Fixed the same way as tezisy-5 п.4's featured-card
   alignment: this IS the real reference grid now (repeat(3,1fr),
   gap:16px, identical to .vp-feed-regular-grid), with the cover column
   spanning tracks 1-2 and the sidebar getting track 3 - Cover Story's
   width is exactly 2 standard columns and the sidebar is exactly 1, by
   construction, not by approximation. */
.vp-home-top {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 16px;
  align-items: start;
  /* Tezisy-6 (follow-up): every card-to-card distance on the page is now the same
     16px standard gap (see below) - the header-to-Cover-Story distance
     should read as "the standard gap, plus a deliberate 20px" (36px),
     not the generic 40px every other page's .vp-main padding-top gives
     it. Pulling this one element up instead of touching .vp-main itself
     keeps every other page's own header-gap math (section-title
     centering, etc.) completely untouched.
     Tezisy-7 п.5: another 10px off that 36px (was margin-top:-4px,
     40-4=36) - 40-14=26px now.
     Tezisy 1.9-3 п.2: -1/3 of that 26px gap = 17.33px target, so
     margin-top = -(40-17.33) = -22.67px. */
  margin-top: -22.67px;
}
.vp-home-cover-col { grid-column: 1 / 3; }
/* Tezisy-6 (follow-up): the standard 16px gap comes from a real flex
   `gap` now, not margins on the children - .vp-excursions-box and
   .vp-article-list each carry their OWN `margin: 24px 0` for their other
   (non-sidebar) uses, which was fighting the previous margin-based
   approach in two ways: equal-specificity rules lost to source order for
   margin-TOP, and even where they didn't, adjacent block margins
   COLLAPSE to the larger value rather than adding, so a 16px override
   next to a 24px neighbor silently rendered as 24px. `gap` sidesteps
   both problems, as long as each child's own margin is zeroed instead
   (see the two rules below) so it can't add on top of the gap. */
/* 2026-09-03: min-width:0 is load-bearing, not decorative. Without it,
   this grid item's automatic minimum size is its min-content - and
   .vp-article-list li a below is `white-space: nowrap`, so its
   min-content EQUALS its full unwrapped text width. A "Новое на сайте"
   article whose title happens to be long (any article without a short
   translation for the current $lang - i.e. the RU fallback title, which
   is often a long descriptive sentence rather than a short headline)
   forced this 1-of-3 track past its 1fr share, stealing width from the
   cover column (grid-column 1/3) and visibly inverting the 2:1 cover:
   sidebar ratio - reported as the Home page "mirroring" on every
   language except the 4 with full title coverage for that slot's
   article. min-width:0 caps the grid item at 0 instead, so the actual
   1fr track sizing wins and the anchor's own overflow:hidden +
   text-overflow:ellipsis (already on .vp-article-list li a) does the
   truncating job it was always meant to do. */
.vp-home-top-sidebar { grid-column: 3 / 4; display: flex; flex-direction: column; gap: 16px; min-width: 0; }
/* Tezisy-7 п.5: the gap between the top section (cards 1-3: Cover Story
   + the 2 teasers) and home_feed's own first featured card (card 4)
   must be the same standard 16px as everything else - home_feed's own
   output is just `.vp-section-feed` (see inc/blocks.php), which carries
   its OWN margin-top:28px for its OTHER use on real Section pages
   (tuned there for a specific header/title-centering reason - left
   alone). Scoping the override to "a .vp-section-feed that comes right
   after .vp-home-top" (only ever true on the Home page) - higher
   specificity than that base rule, so no risk of losing to source order
   or collapsing the way tezisy-6's sidebar fix did. */
.vp-home-top + .vp-section-feed { margin-top: 16px; }
@media (max-width: 780px) {
  .vp-home-top { grid-template-columns: minmax(0, 1fr); }
  .vp-home-cover-col, .vp-home-top-sidebar { grid-column: 1; }
  .vp-header-main { flex-wrap: wrap; row-gap: 8px; }
  h1 { font-size: 26px; }
  .vp-contributor-page-header { flex-direction: column; align-items: flex-start; gap: 16px; }
  .vp-contributor-card-link { flex-direction: column; }
  /* Tezisy 1.9-2 п.6: -20% (was 80px). */
  .vp-contributor-photo, .vp-contributor-card-link .vp-contributor-photo--placeholder { width: 64px; }
}
html[data-fontsize="large"] .vp-main { font-size: 1.1em; }
html[data-fontsize="xlarge"] .vp-main { font-size: 1.22em; }

/* --- section pages: article + kaleidoscope-snippet card grid --- */
.vp-section-title {
  font-size: 30px; text-align: center;
  /* Tezisy-2 п.7: 10% lighter (blend 10% white into #4d4d4d/77,77,77):
     77*0.9 + 255*0.1 = 69.3 + 25.5 = 94.8 -> 95 = #5f5f5f */
  color: #5f5f5f;
  /* Centered vertically between the header and the first card: the gap
     above (.vp-main's own 40px top padding) must equal the gap below.
     .vp-section-feed's own top margin is 28px (tuned separately, left
     alone), so this margin is set to 40-28=12 to make the two gaps match
     exactly (40px header-to-title, 12+28=40px title-to-first-card). */
  margin: 0 0 12px;
  /* Tezisy-5 п.2: still reads as below-center in the space between the
     header and the first card - raise the text 10px WITHOUT changing
     that space's total height. A transform doesn't affect layout/margins
     at all (unlike changing the margin split, which would need reworking
     the exact 12px value above and would move where the NEXT element
     starts) - the title's box stays exactly where it was, only its
     painted position shifts up. */
  transform: translateY(-10px);
}
.vp-card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 20px;
  margin: 28px 0 60px;
}
.vp-card {
  border-radius: 0; /* original site's own .panel has no radius either */
  /* Was overflow:hidden - had to relax it so the new share-widget
     dropdown (tezisy п.8, an absolutely-positioned popup) isn't clipped
     by its own card. Nothing here actually depended on the clip: there
     is no border-radius to conform to, and both the image (object-fit)
     and clamped title/excerpt text already self-clip on their own boxes. */
  overflow: visible;
  display: flex;
  flex-direction: column;
  text-decoration: none;
  color: inherit;
}
.vp-card--article .vp-card-link:hover h3,
.vp-card--product:hover h3 { color: #2eaadc; }
/* Every card in a .vp-card-grid - article, date, quote, product alike -
   shares one fixed box size, on every page that uses the grid. Content
   that varies (title/excerpt length, presence of a photo) is clamped or
   flex-pinned to fit instead of changing the box, so a row never mixes
   card heights.
   Tezisy 1.9-2(2) п.7: -20px (400->360... 380->360) - .vp-card-body has
   no flex-grow of its own, so the leftover space in .vp-card-link's own
   flex:1 column (media is a fixed 150px, body's h3/p are line-clamped to
   a fixed content height) was rendering as dead white space between the
   end of the excerpt and .vp-card-meta below it (a DIFFERENT flex child,
   sibling to .vp-card-link, not inside it) - shrinking the shared box
   height removes exactly that leftover space without touching the
   fixed-size media, clamped text, or meta row at all. */
.vp-card-grid .vp-card { aspect-ratio: auto; height: 360px; }

/* Владелец (13.09.2026): на стандартных карточках-анонсах Главной и
   страниц разделов анонс (excerpt) убран целиком - остаётся только
   заголовок, и он должен помещаться ЦЕЛИКОМ любой длины (никакого
   line-clamp/обрезания). Высота карточки при этом должна визуально
   совпадать с большой горизонтальной (featured) карточкой - взято как
   min-height (не фиксированный height), чтобы очень длинный заголовок
   всё равно мог вытолкнуть карточку выше этого значения, а не обрезаться
   - оба требования владельца одновременно ("помещаться целиком" и
   "высота как у featured") иначе противоречили бы друг другу. Селекторы
   намеренно с префиксом контейнера (.vp-card-grid/.vp-feed-regular-grid/
   .vp-home-teasers), чтобы специфичность гарантированно перебивала
   соседний .height:360px без !important - карточки с анонсом (map.php,
   "Читайте также" на article.php, тизеры не тронуты) остаются как были.
   Значение высоты - измеренное вживую (getBoundingClientRect) у реальной
   featured-карточки на section.php, не подобрано на глаз. */
.vp-card-grid .vp-card.vp-card--no-excerpt,
.vp-feed-regular-grid .vp-card.vp-card--no-excerpt,
.vp-home-teasers .vp-card.vp-card--no-excerpt {
	height: auto; min-height: 333px;
}
.vp-card--article.vp-card--no-excerpt .vp-card-body { overflow: visible; }
.vp-card--article.vp-card--no-excerpt h3 {
	display: block; -webkit-line-clamp: unset; overflow: visible;
}

/* Владелец (16.09.2026): анонс теперь и в десктопной версии тоже - под
   заголовком, обрезан по 3 строкам с многоточием (та же клэмп-логика,
   что уже задана ниже в базовом .vp-card--article p, раньше просто
   перебивавшаяся этим же правилом с display:none - карточка height:
   360px константа, поэтому клэмп обязателен, а не auto-рост). На
   мобильном (см. media-запрос ниже) клэмп снимается - там высота
   карточки подстраивается под содержимое. */

.vp-card--article { background: #fff; border: 1px solid #eee; }
.vp-card--article .vp-card-link { display: flex; flex-direction: column; flex: 1; min-height: 0; text-decoration: none; color: inherit; }
/* Wraps just the illustration, so the date badge (tezisy п.10) can be
   pinned to ITS corner without affecting the card body's own layout. */
.vp-card--article .vp-card-media { position: relative; flex-shrink: 0; height: 150px; }
.vp-card--article .vp-card-img { width: 100%; height: 100%; object-fit: cover; margin: 0; border-radius: 0; display: block; }
/* Tezisy п.10: publication date moves onto the illustration's top-left
   corner (was in .vp-card-meta, opposite the author) - matches the
   original site's own small-card layout (.panel-time over the image). */
.vp-card-date-badge {
  position: absolute; top: 8px; left: 8px;
  background: rgba(0,0,0,0.6); color: #fff;
  font-family: system-ui, sans-serif; font-size: 13px; line-height: 1.3;
  padding: 3px 8px; border-radius: 3px;
}
.vp-card--article .vp-card-body { padding: 12px 14px; font-family: system-ui, sans-serif; overflow: hidden; }
.vp-card--article h3 {
  /* Tezisy 1.9 п.9: +5px gap to the first line of text (was 6px) - the
     new reference value the featured card's own gap gets matched against
     below.
     Вечерние тезисы 2026-09-03 п.6: +2 "typographic points" on the
     heading kegl - this codebase's own established rounding for that
     unit (1pt -> a clean 1.5px, see .vp-card-meta/.vp-card--cover's own
     comments) makes 2pt a clean +3px: 17 -> 20px. */
  font-size: 20px; margin: 0 0 11px; font-family: 'Bitter', Georgia, serif; line-height: 1.25;
  /* Владелец (16.09.2026): "на всех карточках-анонсах в десктопной
     версии убрать ограничение по размеру для заголовков - поместятся и
     в 4, и даже в 5 строк" - клэмп снят на десктопе тоже (было 2 строки,
     не только на мобильном). */
  display: block; overflow: visible;
}
.vp-card--article p {
  font-size: 14.5px; color: #666; margin: 0; line-height: 1.5;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
/* Author name (link to their contributor.php page, accent color +
   underline so it visibly reads as a hyperlink) on the left, the share
   widget (tezisy п.10: now lives where the date used to be) on the
   right - opposite corners of the card. No contributor linked -> plain
   non-link text; no author at all -> the corner is just empty, the
   share widget still shows on the right. */
.vp-card-meta {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  /* Tezisy-2 п.5: this block must sit equidistant from the panel's
     bottom edge as it is from the left/right edges (14px) - it used to
     have 0 bottom padding, sitting flush against the card's own border,
     while keeping 14px on the sides. Only the illustration is allowed to
     bleed to an edge; every other element (this row included) keeps a
     uniform inset. */
  padding: 0 14px 14px;
  margin-top: 6px;
  /* Tezisy п.9: author/date text +1 step (was 11.5px) - 1 "typographic
     point" (~1.33px) rounded to a clean +1.5px. */
  font-family: system-ui, sans-serif; font-size: 13px; color: #999;
}
.vp-card-author, .vp-card-author--plain {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0;
}
.vp-card-author { color: #2eaadc; text-decoration: underline; }
.vp-card-author--plain { color: #999; text-decoration: none; }

/* Cards for content that has no photo yet (real migration is still in
   progress) get a same-size neutral slot instead of just losing the
   image and leaving the text floating with a gap before the share row -
   keeps every card the same shape whether or not it has a picture. */
.vp-card-img--placeholder {
  background: linear-gradient(135deg, #f1e9e4, #e6d9cd);
}

/* --- section pages: featured-item feed (original-site composition:
   3 groups of 1 featured + 6 regular = 21 items before "load more",
   3 columns for the regular cards instead of the flat auto-fill grid
   drifting to 4-5 columns on wide screens) --- */
/* Owner: no 3 real "blocks" here, just one continuous feed where
   positions 1/8/15 happen to be the large featured cards - so every gap
   (between groups, between a featured card and its row, within a row)
   must be the SAME value, not larger around the featured ones. Also
   reduced 20% (0.8x) from the original 20px, freeing that space for
   wider columns/images (grid `1fr` tracks absorb it automatically). */
.vp-section-feed { display: flex; flex-direction: column; gap: 16px; margin: 28px 0 0; }
.vp-feed-group { display: flex; flex-direction: column; gap: 16px; }
/* Владелец 10.10.2026: на узких телефонах (320-360px) последние карточки
   были шире остальных и страница двигалась влево-вправо - колонка 1fr
   = minmax(auto,1fr) не сжимается уже min-content карточки, а это строка
   автора+иконок (white-space:nowrap; длинное имя вроде "Константин
   Карчевский" = 297px при колонке 257px). minmax(0,1fr) - колонка всегда
   равна доступной ширине, имя обрезается многоточием (уже было задано
   у .vp-card-author). Так же во всех сетках карточек ниже. */
.vp-feed-regular-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; }
/* Вечерние тезисы 2026-09-03 п.7: another -20px (380->360). Measured live
   before touching this: the gap between the excerpt's own bottom and
   .vp-card-meta below it (dead space in .vp-card-link's flex:1 column,
   same mechanism as the 400->380 cut noted above) was 80px, so this
   comes entirely out of that leftover space - media height, the h3/p
   line clamps, and .vp-card-meta's own size are all untouched; only the
   blank gap between them shrinks (80px -> 60px), which is what keeps
   every element "in its place" per the explicit ask. */
.vp-feed-regular-grid .vp-card { height: 360px; }

/* Tezisy-2 п.7: on the big horizontal cards (feed positions 1/8/15),
   every element - image included this time - sits at an equal distance
   from the panel's edges, 15% more than the standard card's 14px:
   14 * 1.15 = 16.1px. This is the opposite illustration rule from the
   small cards (image bleeds to the edge there) - confirmed against the
   original site's own big blocks, where the illustration is ALSO inset,
   which is what visually ties it to the text next to it instead of
   reading as a separate strip. */
/* Tezisy-3 п.5: the author (+ share) has to live in the text column, not
   in a strip under the photo - but vp_share_row() contains its own <a>
   tags and a <details>, so it CANNOT be nested inside .vp-card-link
   (an <a>) without the browser silently closing that outer link early
   (confirmed live - it was breaking .vp-card-link into two pieces).
   Solution: .vp-card itself becomes the grid, .vp-card-link gets
   display:contents (so its own box disappears from layout but its
   children still render - and clicking any of them still activates the
   link, since it's still the actual DOM ancestor) so .vp-card-media and
   .vp-card-body become direct grid items, and .vp-card-meta - a real
   DOM sibling of .vp-card-link, never inside it - slots into the same
   grid as its own item in the text column's bottom row. */
/* Tezisy-5 п.4: the image/text boundary must land EXACTLY where it would
   if this same row were divided into the .vp-feed-regular-grid below it
   (repeat(3,1fr), gap:16px) - image right edge flush with column 2's
   right edge, text left edge flush with column 3's left edge. Rather
   than reverse-engineering that boundary with calc() math, this grid
   just IS that reference 3-column grid (same track/gap definition) -
   image spans tracks 1-2 (its right edge lands exactly at column 2's own
   right edge, by how column-spanning works), text spans track 3 only (its
   left edge lands exactly at column 3's own left edge). No outer padding
   on the card itself, or the reference coordinate frame would be off by
   that amount relative to the real grid below - image is full-bleed on
   its 3 outer sides (matching how a regular small card's image already
   bleeds), text gets its own inset instead (see below). */
.vp-card--featured.vp-card--article {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  /* Tezisy-7 п.1: rows are auto now (were "1fr auto") - the image's own
     aspect-ratio (below) is what should determine this card's height,
     not a flexible track stretching to fill whatever the text column
     happens to need. */
  grid-template-rows: auto auto;
  gap: 16px;
  padding: 0;
}
.vp-card--featured.vp-card--article .vp-card-link { display: contents; }
/* Tezisy-7 п.1: the white "frame" around the illustration is back - top/
   bottom/left padding on the wrapper (revealing the card's own white
   background through it), right stays at 0 so that edge is still flush
   with column 2's own right edge exactly as before (untouched per the
   owner's own "ничего не надо менять" on that side). 16px matches the
   site's one standard gap value, same as every other card-to-card
   distance. height:auto (was 100%, which needed row 1 to already have a
   size to stretch into) - now the image's own aspect-ratio decides the
   height, and this wrapper (spanning both rows) just follows it. */
.vp-card--featured.vp-card--article .vp-card-media {
  grid-column: 1 / 3; grid-row: 1 / span 2;
  width: 100%; height: auto;
  padding: 16px 0 16px 16px;
  box-sizing: border-box;
}
/* Tezisy-9 п.5: ratio changed again, deliberately - matches a STANDARD
   card's own illustration proportions now (340px column / 150px fixed
   media height, measured live at this project's usual 1400px reference
   viewport = 34:15 ≈ 2.267:1), not the 3:2 tezisy-7 set. Cover Story is
   explicitly excluded from this (keeps its own 16:9, see п.1 above) -
   this is only about making every OTHER card's illustration read as the
   same "wide filmstrip" shape. Still fixed/locked (never a free-floating
   value) and still the real "кадрирование" via object-fit:cover on
   whichever photo doesn't already match. */
.vp-card--featured.vp-card--article .vp-card-img {
  aspect-ratio: 34 / 15;
  height: auto;
}
.vp-card--featured.vp-card--article .vp-card-img--placeholder {
  aspect-ratio: 34 / 15;
  min-height: 0;
}
/* Tezisy-9 п.6: the date badge's own top:8px/left:8px (base rule,
   shared with every card) is measured from .vp-card-media's OWN edge,
   which tezisy-7's white-frame padding (16px top/left, above) moved the
   ACTUAL PHOTO 16px inward from - without this override the badge sits
   in the white frame gap, not on the photo at all (confirmed live: it
   had drifted off the image entirely). +16 to both offsets lands it
   back at a true 8px inset from the photo's own edge, matching every
   standard card. */
.vp-card--featured.vp-card--article .vp-card-date-badge {
  top: 24px;
  left: 24px;
}
/* Text width = a regular card's own text width (column width - 2*14px
   padding) + 14px (the left inset small cards have) - i.e. flush left at
   the column boundary, with only the usual 14px kept on the right/top/
   bottom, per the owner's own formula. */
/* Tezisy 1.9-2 п.9: text column narrowed 3px, given straight to the gap
   from the illustration - a left padding on the body itself (previously
   0) rather than touching the grid's own gap/track sizing, which would
   also shift the untouched media column's width via the shared 3-track
   `gap` math above. */
/* Владелец 22.10.2026: справа текст отступает от края карточки в полтора
   раза больше, чем слева от иллюстрации (слева 16px gap + 3px = 19px,
   справа должно быть 28.5px; 27.8px padding + ~0.7px рамки карточки,
   измерено живьём на 1400px). То же справа у строки Автор/Поделиться. */
.vp-card--featured.vp-card--article .vp-card-body { grid-column: 3 / 4; grid-row: 1; padding: 16px 27.8px 0 3px; }
/* Tezisy 1.9 п.9: real bug found while checking this - row 2 (this grid
   cell) was being stretched taller than its own content by the image
   item spanning both rows (grid-row:1/span 2 needs more height than row
   1 alone provides, and the extra gets distributed onto row 2), so
   `.vp-card-meta`'s default stretch + its own `align-items:center` was
   centering the author/share row in the MIDDLE of that oversized cell,
   nowhere near the card's actual bottom edge - not "firmly standing" at
   all. `align-self:end` collapses it back to its own natural content
   height and anchors it to the cell's bottom edge instead; padding-bottom
   changed 16px -> 14px to match a standard card's own distance exactly,
   per the owner's explicit ask (not the usual 16.1px "everything scaled
   up" convention used elsewhere on this card type). */
@media (min-width: 601px) {
  .vp-card--featured .vp-card-meta { grid-column: 3 / 4; grid-row: 2; align-self: end; padding: 0 27.8px 14px 0; margin-top: 0; }
}
/* Tezisy 1.9-2(2) п.2, corrected: RIGHT not left (owner's own follow-up
   correction) - this actually ALIGNS the author link with the heading
   above it, both now 3px right of the original shared 642.9px edge
   (the heading's own text moved right when its padding-left went 0->3px
   in the previous pass; the author link had stayed behind at the old
   edge until this rule brought it in line). Scoped to .vp-card-author
   only, not .vp-card-meta as a whole, so the share icons on the right
   side of the same flex row don't move. */
@media (min-width: 601px) {
  .vp-card--featured.vp-card--article .vp-card-author { margin-left: 3px; }
}
.vp-card--featured.vp-card--article h3 { font-size: 26.5px; display: block; overflow: visible; }
.vp-card--featured.vp-card--article p { font-size: 14.5px; -webkit-line-clamp: 5; }
.vp-card--featured.vp-card--date,
.vp-card--featured.vp-card--quote { padding: 40px; }
.vp-card--featured.vp-card--date .vp-card-date-big { font-size: 22px; }
.vp-card--featured.vp-card--quote p { font-size: 18px; -webkit-line-clamp: 8; }
@media (max-width: 900px) {
  .vp-feed-regular-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 600px) {
  .vp-feed-regular-grid { grid-template-columns: minmax(0, 1fr); }

  /* Владелец (16.09.2026): "имеет смысл отказаться от деления в
     мобильной версии карточек на большие и стандартные - все
     карточки-анонсы должны наполняться по одному паттерну: картинка с
     датой сверху, полный заголовок, полный анонс, автор слева/
     Поделиться справа снизу. Константы: мин. высота карточки, высота
     картинки, расстояния между элементами, кегль/интерлиньяж текста."
     Featured-карточка раньше отдельно перестраивалась на CSS Grid при
     900px - теперь ровно тот же брейкпоинт (600px) и тот же механизм
     (flex-колонка), что у стандартных и Cover Story карточек ниже -
     никакого собственного макета не остаётся, только сам контент может
     быть длиннее/короче. */
  .vp-card--featured.vp-card--article {
    display: flex; flex-direction: column; gap: 0;
  }
  .vp-card--featured.vp-card--article .vp-card-media {
    width: 100%; height: 150px; padding: 0;
  }
  /* Владелец 27.09.2026: на мобильном featured-картинка (1/8/15 на
     Разделах, 4/11 на Главной) держала десктопный aspect-ratio:34/15,
     который на 150px-контейнере при мобильной ширине даёт ~143.5px -
     6.5px пустоты над заголовком ("как будто пустая строка"). Стандартные
     карточки просто растягивают img на всю высоту контейнера
     (object-fit:cover уже кадрирует) - делаем то же самое здесь. */
  .vp-card--featured.vp-card--article .vp-card-img,
  .vp-card--featured.vp-card--article .vp-card-img--placeholder {
    aspect-ratio: auto; height: 100%;
  }
  .vp-card--featured.vp-card--article .vp-card-date-badge { top: 8px; left: 8px; }
  .vp-card--featured.vp-card--article .vp-card-body { padding: 12px 14px 0; }
  /* Кегль - тот же, что у стандартных карточек (20px/14.5px), не свои
     desktop-увеличенные 22px - константа "кегль и интерлиньяж" из
     задачи. .vp-card-meta НЕ переопределяем вовсе - наследует базовое
     margin-top:6px/padding:0 14px 14px, тот же зазор, что у всех. */
  .vp-card--featured.vp-card--article h3 { font-size: 20px; }
  .vp-card--featured.vp-card--article p {
    font-size: 14.5px; display: block; -webkit-line-clamp: unset; overflow: visible;
  }

  /* Cover Story - уже почти совпадала (высота картинки/кегли те же),
     недоставало только общей константы минимальной высоты. */
  .vp-card--cover.vp-card--article { min-height: 333px; }

  /* Общая минимальная высота карточки-анонса - единая на все три типа
     (была 360px фиксированных у каждой отдельно, для no-excerpt -
     min-height:333px - теперь ВСЕ карточки-анонсы, с анонсом или без,
     ведут себя одинаково: высота = содержимое, но не меньше 333px). */
  .vp-card-grid .vp-card,
  .vp-feed-regular-grid .vp-card,
  .vp-home-teasers .vp-card {
    height: auto; min-height: 333px;
  }
  /* Featured-карточка живёт СНАРУЖИ .vp-feed-regular-grid (в своей
     .vp-feed-group обёртке, см. vp_render_feed_batch) - три правила
     выше её не находят, добираем отдельно. */
  .vp-card--featured.vp-card--article { min-height: 333px; }
  /* Владелец 16.09.2026 (map.php поз. 1/8/15, "подшаманить"): когда
     заг+анонс короче пола min-height, остаток раньше повисал ПОСЛЕ
     Автор/Поделиться, а не перед ним - блок "подпрыгивал" вверх от
     края плашки. Прижимаем к низу тем же приёмом margin-top:auto,
     что уже применён у .vp-excursions-box. */
  .vp-card--featured.vp-card--article .vp-card-meta { margin-top: auto; }
}

/* Tezisy-4 п.1: Cover Story card - the single biggest announcement card
   on the site (top-left of Home, 2/3 width). Structurally the plain
   .vp-card--article shape (full-bleed image, NOT the featured card's
   1.15x inset - the brief calls for the same standard elements, just
   scaled up), so this is purely a sizing override: aspect-ratio instead
   of a guessed fixed height keeps the illustration full-bleed at any
   width the 2/3 column resolves to. Title 18-20px per the brief despite
   being the "biggest" card - that refers to overall footprint, not type
   size; 19px is the range's midpoint. */
/* Tezisy 1.9-2 п.3/5: Cover Story's total height is now a hard constant
   (628px, box-sizing:border-box so the 1px border is included) instead of
   "whatever the aspect-ratio image + fixed text zones happen to sum to" -
   overflow:hidden as a hard backstop. Combined with .vp-card-media's own
   fixed height right below (396px) and body/meta's already-fixed 186+44,
   the three parts sum to exactly this by construction (396+186+44+2=628),
   so this is belt-and-suspenders, not load-bearing on its own - but
   matches the same explicit-height treatment every other element in this
   6-card block now gets, per the owner's "железно, неизменяемо". */
.vp-card--cover.vp-card--article { height: 628px; overflow: hidden; box-sizing: border-box; }
/* Tezisy 1.9-2 п.3: switched from aspect-ratio (responsive, varied with
   the column's own width) to a hard fixed height - the owner's explicit
   ask was that NOTHING about these cards' sizing responds to content or
   context anymore. object-fit:cover (inherited from the shared
   .vp-card--article .vp-card-img rule) crops the photo to fill this
   fixed box at any column width, same convention the small teaser cards
   already use for their own 150px media. min-height:0 still needed - see
   the flex min-height:auto note this replaces (the underlying flex-item
   bug this guarded against is still a risk with any fixed-height flex
   child). */
.vp-card--cover .vp-card-media { height: 396px; min-height: 0; }
/* Tezisy-9 п.1: white field below the illustration doubled and matched
   exactly to a standard card's own (230px = 380 total - 150 media,
   measured live) - explicit heights on body+meta (186+44=230) rather
   than reverse-engineering font/line-clamp metrics to land on the exact
   figure by chance. Headline kegl doubled (19->38, this being the
   "передовица" of the page) - line-clamp stayed at 2 lines (a real
   2-line front-page headline, not a smaller single line), which is what
   ate most of the newly-doubled field, so the meta-description paragraph
   below it dropped from 3 lines to 2 to fit what's left. */
/* Владелец 07.10.2026: "в самом первом большом анонсе блок
   Заголовок+Текст анонса поднять на 6 pix" - единственная карточка
   с этим классом на сайте (hero_article/Cover Story, см.
   vp_render_cover_card() в inc/blocks.php), значит никакую другую
   карточку не задевает. Верхний padding -6px (18->12) - сам блок
   (h3+p) сдвигается вверх на 6px внутри НЕИЗМЕННОЙ высоты 186px, так
   что .vp-card-meta ниже не двигается.
   Владелец 07.10.2026 (следом): "можно (и нужно) понять его ещё на
   3 pix" - ещё -3px (12->9), итого -9px от исходных 18px. */
.vp-card--cover.vp-card--article .vp-card-body { padding: 7px 20px 18px; height: 186px; box-sizing: border-box; } /* Владелец 07.10.2026 (ещё раз): "давай ещё на 2 поднимем" - 9->7px (итого -11px от исходных 18px). */
/* Tezisy 1.9-2(2) п.9: Bitter had no bold version rendering here before -
   no explicit font-weight meant the browser's own UA-stylesheet h3 bold
   (which resolves to the family's 700 instance), now stepped up to the
   family's real Black (900) instance instead, self-hosted above, for
   more visual punch on the one heading this site treats as its biggest. */
.vp-card--cover.vp-card--article h3 { font-size: 42.5px; font-weight: 700; display: block; overflow: visible; }
/* Tezisy 1.9-2(2) п.9: +1 "point" on the excerpt, same rounding this
   codebase always uses for that unit (~1.33px -> a clean 1.5px): 15 +
   1.5 = 16.5px. */
.vp-card--cover.vp-card--article p { font-size: 16.5px; -webkit-line-clamp: 2; }
.vp-card--cover .vp-card-meta { padding: 0 20px 18px; height: 44px; box-sizing: border-box; }

/* Tezisy-5 п.9: two standard announcement cards right under Cover Story,
   in the SAME left column (not spanning the sidebar too) - grid item
   becomes [cover card, teasers row] stacked, sized independently of the
   sidebar's own (shorter) content per .vp-home-top's align-items:start.
   Tezisy-6 (follow-up): gap standardized to the same 16px used
   everywhere else (was 24px). */
.vp-home-cover-col { display: flex; flex-direction: column; gap: 16px; }

/* Heading above the "Новое на сайте" list in the top sidebar (was a
   separately-authored text block above .vp-article-list; now emitted by
   the article_list block itself so the two can never drift apart). */
.vp-sidebar-heading {
  font-family: 'Bitter', Georgia, serif; font-size: 18px; font-weight: 400;
  color: #1a1a1a; margin: 0 0 4px;
}
/* Tezisy 1.9 п.7: bold on "Новое на сайте" specifically (its only real
   call site, see the dark-card note above .vp-sidebar-block--dark). */
/* Tezisy 1.9-2 п.4: +10% on the heading-to-first-item gap specifically -
   the first li's own 6.67px top padding was the whole gap (heading had
   no margin-bottom of its own), so 6.67 * 1.1 = 7.34px total needs just
   0.67px added here (doesn't touch the padding shared by every OTHER
   item-to-item gap in the list). */
/* Вечерние тюн-тезисы 2026-09-03 п.9: heading +2pt (18 -> 21px, this
   codebase's usual 1pt=1.5px), staying put - only the list below it
   moves (see .vp-home-top-sidebar .vp-article-list's own margin-top). */
.vp-sidebar-block--dark .vp-sidebar-heading { font-weight: 700; font-size: 21px; margin-bottom: 0.67px; }
/* CTA inside the excursions box, linking to the real Excursions section
   page - reuses the site's established secondary accent (#2eaadc) since
   there is no public .vp-btn base style to build on (only an admin-
   scoped one exists). Tezisy-7 п.6: own margin-top removed - it now
   sits inside .vp-excursions-box-footer's flex row, whose OWN margin-top
   provides the gap above; a margin here too would just push the button
   out of line with the share icons beside it (flex items don't collapse
   margins the way normal-flow siblings do). */
.vp-excursions-btn {
  display: inline-block;
  /* Tezisy 1.9-2 п.2: plate trimmed 3px on every side (was 10px 18px) -
     same position, same font-size, just a more compact box around the
     label. */
  padding: 7px 15px; border-radius: 4px;
  background: #2eaadc; color: #fff;
  font-family: system-ui, sans-serif; font-size: 14px; font-weight: 600;
  text-decoration: none; text-align: center;
}
.vp-excursions-btn:hover { background: #4bb9e3; }

/* Home-page pull-quote, sourced from a Publication article's own
   quote_text/quote_author/quote_author_role fields (inc/blocks.php's
   `quote` case). Reuses the existing in-article .vp-quote look (red
   rule, italic) inside a white bordered card matching the excursions box
   above it; text is 4-line-clamped per the brief, with a footer row
   linking back to the source article (left) and a decorative share
   widget (right). Selectors scoped under .vp-home-quote so they outrank
   the generic `.vp-quote cite` rule (higher specificity) instead of
   fighting it. */
/* Tezisy 1.9 п.6: -2px left/right, -5px top/bottom, same as
   .vp-excursions-box right above it - together these two cards shrink
   enough to match Cover Story's own height. */
/* Tezisy 1.9-2 п.3/5: hard fixed height, matching .vp-excursions-box's
   own treatment right above it - 257px + 355px + the 16px sidebar gap =
   628px = Cover Story's own new fixed height. */
/* Tezisy 1.9-3 п.2: height = Cover Story's own white part (body+meta =
   186+44=230px), a new/different reference than this morning's "match
   the whole Cover Story column" rule - the footer row that used to live
   here is gone (see inc/blocks.php's 'quote' case), so the quote text
   itself can use the freed space (5-line clamp below) instead. */
.vp-home-quote { background: #fff; border: 1px solid #eee; padding: 17px 20px; height: 232px; overflow: hidden; box-sizing: border-box; }
/* Владелец 16.10.2026: весь блок цитаты - одна ссылка (<a class="vp-home-quote">) -
   выглядит как прежде, текст не синеет и не подчёркивается. */
a.vp-home-quote--link { display: block; color: inherit; text-decoration: none; cursor: pointer; }
a.vp-home-quote--link:hover { border-color: #d8d8d8; }
/* Владелец 16.10.2026: при наведении текст цитаты (без двух строк автора)
   голубеет - тот же #2eaadc, что у заголовков карточек-анонсов. */
a.vp-home-quote--link:hover .vp-quote p { color: #2eaadc; }
/* Tezisy-9 п.2: both rules gone - the red left border (vertical) AND the
   footer's own top border (horizontal, below). Removing the border also
   drops the 20px left-indent that existed purely to clear it, so the
   quote text's own width now matches its sibling above
   (.vp-excursions-box, plain 22px padding on all sides - "по ширине...
   как выше на карточке Экскурсии"). Scoped under .vp-home-quote so the
   GLOBAL .vp-quote class (still used for real in-article pull quotes)
   keeps its own border/indent untouched. */
.vp-home-quote .vp-quote { margin: 0; padding: 0; border-left: 0; }
.vp-home-quote .vp-quote p {
  /* +1 "kegl" (~1.33px, rounded to the clean +1.5px this codebase always
     uses for that unit - tezisy-1 п.9's own precedent): 15 -> 16.5px.
     font-style:italic is inherited from .vp-quote (untouched) - kept, not
     re-declared. Владелец (14.09.2026, "в библию"): полужирный - значимое
     высказывание, выбранное украсить им Главную, должно выделяться. */
  margin: 0; font-size: 16.5px; line-height: 1.55; font-weight: bold;
  /* Tezisy 1.9-3 п.2: 4 -> 5 lines, now that removing the "Весь текст"
     button + share row freed up room to show one more without the quote
     card growing past its own fixed 230px. */
  display: -webkit-box; -webkit-line-clamp: 5; -webkit-box-orient: vertical; overflow: hidden;
}
/* Tezisy 1.9-2 п.3: byline shifted to the right edge of the text block -
   width:fit-content + margin-left:auto shrinks each line to its own
   content width and pushes it flush right, while the text inside stays
   left-ranged (default), so a line that ever wraps still reads normally
   instead of centering or ranging right character-by-character. */
.vp-home-quote .vp-quote-author { display: block; width: fit-content; margin: 15px 0 0 auto; font-style: normal; font-size: 13.5px; color: #333; font-weight: 600; }
.vp-home-quote .vp-quote-role { display: block; width: fit-content; margin: 2px 0 0 auto; font-style: normal; font-size: 12.5px; color: #888; }
.vp-quote-footer {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  /* Tezisy 1.9 п.6 escape valve: after the padding/heading/footer changes
     above, real measurement showed Cover Story ended up ~4.8px TALLER
     than Excursions+Quote combined (628.1 vs 623.3px) - the opposite of
     what shrinking further would fix, so this grows instead of shrinks,
     closing the real gap rather than the anticipated direction. */
  margin-top: 20.8px; padding-top: 14px;
  font-family: system-ui, sans-serif;
}
.vp-quote-link { color: #2eaadc; text-decoration: underline; font-size: 13px; }

/* Gray "load more" bar, like the original site's button-more - a plain
   link styled as a full-width button, growing ?show= on click (see
   inc/blocks.php's section_grid case) instead of an AJAX fetch, so it
   works with no JS and matches this codebase's minimal-JS convention. */
.vp-load-more {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  /* Tezisy п.5: full-bleed edge-to-edge like the footer - the old
     negative-margin trick only cancelled .vp-main's OWN padding, which
     still left the bar capped at .vp-main's max-width (1100px), not the
     actual viewport edges the footer reaches. calc(50% - 50vw) breaks it
     out of the centered/padded column regardless of that width. */
  /* Владелец 08.10.2026: та же проблема, что на мобильном (vw != clientWidth
     из-за резерва под scrollbar-gutter - даже на десктопе это давало
     постоянные 8px переполнения на КАЖДОЙ проверенной ширине окна,
     не только на одной), но на десктопе простой "отменить padding
     родителя" не работает - .vp-main сам уже уже вьюпорта. Вместо
     сырого vw - CSS-переменная --vp-vw, проставленная ранним
     инлайн-скриптом в <head> из document.documentElement.clientWidth
     (см. inc/layout.php) - 100vw остаётся запасным значением, если
     скрипт по какой-то причине не выполнился (JS выключен и т.п.). */
  width: var(--vp-vw, 100vw);
  margin-left: calc(50% - (var(--vp-vw, 100vw) / 2));
  margin-right: calc(50% - (var(--vp-vw, 100vw) / 2));
  /* Tezisy-4 п.2: gap from the content above x5 (7.7 * 5 = 38.5). Bottom
     margin cancels .vp-main's own bottom padding (now 40px, see above) so
     this still sits flush against the footer with no gap. */
  margin-top: 38.5px;
  margin-bottom: -40px;
  padding: 20px; text-align: center;
  background: #d7d7d7; color: #818181;
  font-family: system-ui, sans-serif; font-size: 15px; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.04em;
  text-decoration: none; /* no radius - it's flush/full-bleed now, like the original's own plashka */
}
.vp-load-more:hover { background: #dfdfdf; }
/* Владелец 16.10.2026: финальная плашка раздела Эпос («СКАЗКИ ГЕРМАНИИ») -
   те же размеры/положение, что у «Показать ещё», фон на 20% темнее
   (#d7d7d7 = 215 -> 172 = #acacac), текст 100% чёрный, без иконки. */
.vp-load-more.vp-feed-end { background: #acacac; color: #000; }
.vp-load-more.vp-feed-end:hover { background: #b8b8b8; }
/* Tezisy п.5: classic download glyph (arrow resting on a tray/line) to
   the right of the label - inline SVG with stroke=currentColor so it
   always matches the bar's own text color, same convention as the
   back-to-top button's chevron. */
.vp-load-more svg { flex-shrink: 0; }

/* --- homepage: standard announcement cards under Cover Story --- */
.vp-home-teasers {
  display: grid;
  /* Tezisy-5 п.9: exactly two standard cards side by side, back to this
     block's original role (was briefly 3-wide for a since-removed
     20-item use, then dropped entirely in tezisy-4's redesign - now
     re-added under Cover Story with a fixed limit:2).
     Tezisy-6 п.1: gap matches the reference grid's own 16px, and since
     .vp-home-cover-col is now exactly 2 standard columns wide (see
     .vp-home-top above), 2 equal tracks here land each card at exactly
     one standard column's width too - no separate math needed. */
  grid-template-columns: repeat(2, 1fr);
  gap: 16px;
  /* Tezisy-6 (follow-up): this block's own margin-top (20px) used to
     STACK on top of .vp-home-cover-col's flex `gap` (24px, now 16px) -
     margins and flex gap don't collapse into each other, so the real
     distance between Cover Story and this row was 24+20=44px, not the
     intended 24px. Removed entirely now that the parent's own gap alone
     gives the standard 16px. */
}
/* Tezisy-6 п.1: "не надо мудрить" - these must be the SAME standard card
   size used everywhere else (.vp-card-grid/.vp-feed-regular-grid's own
   380px/150px), not a smaller bespoke "mini teaser" size left over from
   this block's pre-tezisy-4 role. */
/* Вечерние тезисы 2026-09-03 п.7: matches .vp-feed-regular-grid's own
   -20px cut right above (380 -> 360) - same standard card, same leftover
   dead space absorbing it. */
.vp-home-teasers .vp-card { height: 360px; }
.vp-home-teasers .vp-card-media { height: 150px; }
/* Tezisy п.3: every announcement-card plashka on section pages is white,
   no exceptions - the Kaleidoscope-derived date/quote snippet cards used
   to be dark/cream to look visually distinct from real article cards;
   now they match .vp-card--article's own white+border treatment, with
   their text recolored for contrast on white instead of their old dark
   background. */
.vp-card--date {
  background: #fff; color: #1a1a1a; border: 1px solid #eee;
  justify-content: center; align-items: center; text-align: center;
  padding: 20px; font-family: system-ui, sans-serif;
}
.vp-card-date-big { font-size: 15px; color: #7a2e2e; letter-spacing: 0.08em; margin-bottom: 10px; }
.vp-card--date p { font-family: 'Bitter', Georgia, serif; font-size: 15px; margin: 0; }
.vp-card--quote {
  background: #fff; color: #1a1a1a; border: 1px solid #eee;
  justify-content: center; align-items: center; text-align: center;
  padding: 22px; font-style: italic;
}
.vp-card--quote p {
  font-size: 14px; margin: 0;
  display: -webkit-box; -webkit-line-clamp: 6; -webkit-box-orient: vertical; overflow: hidden;
}
.vp-card--quote cite { display: block; margin-top: 10px; font-style: normal; font-size: 12px; color: #7a2e2e; }

/* --- shop --- */
.vp-shop-intro { color: #666; margin: -10px 0 20px; }
.vp-card--product { background: #fff; border: 1px solid #eee; }
.vp-card--product .vp-card-img { width: 100%; height: 140px; object-fit: cover; margin: 0; border-radius: 0; flex-shrink: 0; }
.vp-card--product .vp-card-body { padding: 14px 16px; font-family: system-ui, sans-serif; display: flex; flex-direction: column; gap: 6px; flex: 1; min-height: 0; overflow: hidden; }
.vp-card-tag { font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; color: #7a2e2e; }
.vp-card--product h3 {
  font-size: 15px; margin: 0; font-family: 'Bitter', Georgia, serif; line-height: 1.25;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.vp-card--product p {
  font-size: 12.5px; color: #666; margin: 0;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.vp-card-actions { display: flex; gap: 8px; margin-top: auto; }
.vp-card-actions .vp-btn { font-size: 13px; padding: 7px 14px; }
.vp-btn-disabled {
  font: inherit; font-size: 13px; padding: 7px 14px; border-radius: 4px;
  border: 1px solid #ddd; background: #f5f5f5; color: #aaa; cursor: not-allowed;
}

/* --- map --- */
/* Tezisy-7 п.4: the legend moved from a 220px left sidebar to a
   horizontal row ABOVE the map (still below the page's own <h1>), so
   the map itself can take the full content width instead of sharing it
   with a fixed-width column - height stays the same 640px it always
   was, only the width constraint is gone. */
/* Tezisy 1.9-2 п.6: top margin trimmed 24px -> 18px - the triple rule
   below now grows upward into that space (see .vp-map-legend-rule),
   so the heading-to-items gap doesn't just get taller on top of it. */
/* 5.9.26: title/map/legend/feed reorder left the two gaps around this
   block off the owner's stated targets - map-to-legend needed -10px,
   legend-to-feed needed +10px. Bottom margin 16 -> 38 (collapses against
   .vp-section-feed's own 28px top margin, so it must exceed 28 to win
   the max()); top margin (18) is untouched here - the map-to-legend gap
   is governed by .vp-map-canvas's own bottom margin below instead. */
.vp-map-legend-bar { margin: 18px 0 38px; font-family: system-ui, sans-serif; font-size: 13.5px; }
/* Тезисы 6.9.26 п.3: map-to-legend gap -10px more (was already the
   winning side of the collapse against .vp-map-legend-bar's own 18px
   top margin above, per the 5.9.26 note - so trimming this one value
   alone moves the visible gap). */
.vp-map-legend-title { font-weight: 600; margin: 0 0 10px; color: #444; }
/* Tezisy 1.9-3 п.4: replaces the old "Слои (N точек)" technical line -
   70% black / 30% white = 255*0.3 = 76.5 -> #4d4d4d, the same "N% black"
   formula used elsewhere on this site (e.g. the dark "Новое на сайте"
   card). Full content width, single hairline.
   Tezisy 1.9-2 п.6: a single line "смотрелась бедно" - was made three
   (pixel-space-pixel-space-pixel) via two extra box-shadow lines below
   the real border, own bottom margin trimmed 16px -> 10px to absorb the
   space they added.
   Tezisy 1.9-2(2) п.4: three turned out to be one too many - back to two
   (border-top + one box-shadow line, the bottom-most of the previous two
   removed). That line's own ~3px is no longer being "eaten" by the
   margin, so bottom margin goes back up 10px -> 13px, INCREASING the gap
   to the category list below, per the explicit ask. */
.vp-map-legend-rule {
  /* Tezisy 2026-09-03 п.6 landed a single 2pt line, 50% grey (#808080).
     Вечерние тезисы 2026-09-03 п.5: thickness x2 growing UPWARD (a plain
     border-top-width grows DOWNWARD instead, the wrong direction here),
     20% lighter (#999999) - done via a 2pt border-top (anchoring the
     rule's bottom edge in place) PLUS a second 2pt box-shadow line
     offset upward to sit flush against it.
     Уточняющие тезисы 2026-09-03 п.9: that two-line stack read as NOT
     solid (a border and a box-shadow can leave a hairline seam between
     them depending on rendering) - one single 4pt box-shadow line
     instead (no border-top at all) is guaranteed seamless by
     construction, still anchored/growing the same way (upward, from the
     rule's own zero-height box). Lighter again - 25% black:
     255*0.75 = 191.25 -> 191 = #bfbfbf.
     Тезисы 4.9.26 п.10: #bfbfbf "растворилась" into the page's own
     background - 20% DARKER now (the opposite direction from every
     previous pass on this rule), same relative-blend formula this
     codebase always uses, just toward black instead of white:
     191 * 0.8 = 152.8 -> 153 = #999999.
     Тезисы 4.9.26 (вечер) п.2: the REAL reason it "потерялась" wasn't
     the color - verified live: even a temporary 20px solid RED
     box-shadow on this element rendered completely invisible. Browsers'
     own default UA stylesheet for <hr> sets `overflow: hidden` (a
     historical leftover from the classic sunken/ridge <hr> rendering) -
     box-shadow is clipped by an element's OWN overflow, so once the
     double-border+box-shadow stack was simplified to a pure box-shadow
     line with no real border at all, that default silently clipped it
     to nothing. A real border-* is never affected by the element's own
     overflow, which is exactly why this stayed invisible only after
     that switch, not before. Fix is just this one override - thickness
     (4pt) and color (#999999, already exactly 40% black: 255*0.6=153)
     were both already correct. */
  border: none;
  overflow: visible;
  box-shadow: 0 -4pt 0 0 #999999;
  margin: 0 0 13px;
}
/* Tezisy 1.9 п.2: 6 left-justified columns instead of a wrapping flex
   row - equal-width tracks so every column of checkboxes lines up
   regardless of how long that row's own label happens to be; each
   .vp-map-legend-item is itself a left-starting flex row, so content
   never centers within its cell. */
.vp-map-legend-items {
  display: grid; grid-template-columns: repeat(6, 1fr); gap: 8px 20px;
  /* Tezisy 2026-09-03 п.6: shifted 2px down within its own space - pure
     visual nudge, the rule line above and the map canvas below keep
     their exact positions. */
  position: relative;
  top: 2px;
}
@media (max-width: 900px) {
  .vp-map-legend-items { grid-template-columns: repeat(3, 1fr); }
}
@media (max-width: 560px) {
  .vp-map-legend-items { grid-template-columns: repeat(2, 1fr); }
}
.vp-map-legend-item { display: flex; align-items: center; gap: 6px; cursor: pointer; }
/* Владелец 16.10.2026: названия категорий бывают в 2 строки - интерлиньяж
   в полтора раза меньше (21.6 -> 14.4px), чтобы такая позиция читалась
   цельной; на ту же разницу (7.2px) больше зазор между рядами - шаг
   однострочных позиций прежний, к соседям ничего не приближается. */
.vp-map-legend-item { line-height: 14.4px; }
.vp-map-legend-items { row-gap: 15.2px; }
/* Владелец (15.09.2026): "квадратики с закликнутыми галками... нейтральный
   тёмно-серый, например Black 70%, чтобы не путаться с цветными кружками" -
   без этого чекбокс красился браузером в свой стандартный (обычно синий)
   цвет, который на этой карте у категории "Города" теперь И ЕСТЬ реальный
   синий (#0000FF, тот же пункт задачи) - максимально похожие цвета совсем
   рядом друг с другом. accent-color - именно то свойство, которым красится
   нативный чекбокс, без кастомной вёрстки псевдоэлементов. */
.vp-map-toggle { accent-color: rgba(0, 0, 0, .7); }
.vp-map-swatch { width: 12px; height: 12px; border-radius: 50%; display: inline-block; flex-shrink: 0; }
.vp-map-count { color: #999; font-variant-numeric: tabular-nums; }
/* Владелец 17.10.2026: переключатель категории = кружок её цвета:
   закрашен - включена, только контур - выключена. Нативный чекбокс скрыт
   визуально, но остаётся в разметке (клавиатура, скринридеры, скрипты). */
.vp-map-legend-item { position: relative; gap: 8px; }
.vp-map-legend-item .vp-map-toggle { position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0; pointer-events: none; }
/* Владелец 17.10.2026 (следом): выключено - сплошной тёмно-серый кружок
   (#5f5f5f, цвет заголовка страницы); включено - белый кружок с контуром
   цвета категории. */
.vp-map-legend-item .vp-map-swatch {
  width: 14px; height: 14px; box-sizing: border-box;
  /* владелец 17.10.2026: выкл. - на 30% светлее (#5f5f5f -> #8f8f8f) */
  border: 2px solid #8f8f8f; background: #8f8f8f; transition: background .15s, border-color .15s;
}
.vp-map-legend-item .vp-map-toggle:checked + .vp-map-swatch { background: #fff; border: 2.5px solid var(--c); }
.vp-map-legend-item .vp-map-toggle:focus-visible + .vp-map-swatch { outline: 2px solid #2eaadc; outline-offset: 2px; }
/* 5.9.26: top margin 0 -> 28 so it collapses with .vp-section-title's
   own 12px bottom margin (+ the title's own -10px translateY) to match
   the 38px title-to-featured-card gap other section pages use. Bottom
   margin 40 -> 30 per the owner's explicit -10px ask (collapses against
   .vp-map-legend-bar's 18px top margin, still the winning side). */
.vp-map-canvas { width: 100%; height: 640px; border-radius: 8px; border: 1px solid #ccc; background: #eef1f0; margin: 28px 0 20px; }
@media (max-width: 780px) {
  .vp-map-canvas { height: 420px; }
}
.vp-hero-img, .vp-article-img, .vp-block-image {
  width: 100%;
  height: auto;
  border-radius: 0; /* original site: straight corners on every illustration */
  display: block;
  margin-bottom: 20px;
}
h1 { font-size: 32px; line-height: 1.15; margin: 0 0 16px; }

/* --- Публикация (article.php) - Вечерние тезисы 2026-09-03, первый
   заход на этот типовой макет --- */
/* п.1/3: white background just for this template (vp_header()'s new
   $white_bg param sets this body class on article.php only) - every
   other page keeps the site's usual grey (#e5e9ea). */
body.vp-white-page { background: #fff; }

/* п.10 (первый заход): +5pt over the global 32px h1 -> 39.5px.
   Уточняющие тезисы 2026-09-03 п.6: +3pt -> 44px.
   Тезисы 4.9.26 п.1: +2 more pt: 44 + 2*1.5 = 47px.
   Scoped to .vp-article so the global h1 rule (page.php's About/
   Contacts etc.) stays untouched. */
.vp-article h1 { font-size: 47px; }

/* Владелец (13.09.2026, 1я находка): цветная плашка с инверсией при
   наведении не вписывалась в общий дизайн - переделано на обычную
   ссылочную конвенцию сайта (a{color:#7a2e2e}/a:hover{color:#2eaadc}).
   Владелец (13.09.2026, 2я находка): display:flex на самом h1 оторвал
   значок от текста на отдельную строку при переносе заголовка - flex
   переносит ЦЕЛЫЕ flex-item'ы на новую строку, а не встраивает элемент
   в обычный текстовый поток как последнее "слово". Убрано flex целиком:
   ссылка - обычный inline-block внутри h1, переносится вместе с текстом
   как всякий другой инлайн-элемент (та же техника, что и везде на
   сайте для иконок внутри строки текста). */
.vp-admin-edit-link { display: inline-block; vertical-align: middle; margin-left: .3em; color: #7a2e2e; }
.vp-admin-edit-link:hover { color: #2eaadc; }
.vp-admin-edit-link .vp-admin-icon { width: .5em; height: .5em; stroke-width: 2; vertical-align: middle; }

/* п.3: the opening photo (or video - see .vp-video-embed below) sits
   flush against the content column's own left edge at 80% of its width
   - "выключено влево" in the BLOCK-positioning sense of that phrase
   (not the paragraph-alignment sense the body text uses below). The
   freed 20% on the right holds the caption. */
/* Вечерние тюн-тезисы 2026-09-03 п.2: +8% on the opening photo/video -
   80% * 1.08 = 86.4%. The caption's own flex:1 just absorbs whatever's
   left (13.6%, minus the 24px gap) - no separate change needed there. */
/* Вечерние тезисы 4.9.26 п.7: +9px to the gap down to the lead paragraph
   below (20 -> 29) - .vp-lead itself has no margin-top of its own, so
   this margin-bottom is the entire gap. */
.vp-article-hero { display: flex; align-items: flex-start; gap: 24px; margin-bottom: 29px; }
/* Владелец 02.10.2026: главная горизонтальная иллюстрация была УЖЕ, чем
   обычные иллюстрации в тексте (814px против типичных ~900px файлов,
   замерено живьём) - так быть не должно. Дёшево, без выхода за пределы
   общей колонки сайта (1100px): забираем ширину у колонки подписи
   справа (214.2 -> 132.8px, всё ещё шире, чем было ДО того расширения
   29.09.2026 - 119px), отдаём фото (+10%, 814 -> 895.2px). Колонка
   подписи и зазор (24px) вместе - 156.8px (было 238.2); см. тот же
   расчёт у .vp-article-caption и .vp-byline ниже - втроём держим его
   в согласии. */
.vp-article-hero .vp-article-img { width: calc(100% - 156.8px); flex-shrink: 0; margin-bottom: 0; }
.vp-article-hero .vp-video-embed { width: calc(100% - 156.8px); flex-shrink: 0; }
/* Тезисы 6.9.26 п.6: a vertically-oriented (portrait) main photo must
   never exceed some sane width - the 86.4% rule above made tall photos
   read as oversized next to the caption column. Class set server-side
   from the real media.width/height (article.php), not guessed from CSS.
   Владелец 16.09.2026: 540px was too small ("это было ошибкой") - cap
   raised to 900px; an editor who wants it narrower shrinks the source
   file itself before upload rather than the site imposing a number. */
/* Владелец 21.09.2026: "вертикальное фото не должно программно
   растягиваться до 900px или любого другого навязанного размера -
   должно иметь свой собственный размер AS IS" - убрано и width:100%
   (тянуло маленькие портретные фото на всю ширину колонки), и жёсткий
   max-width:900px (тот самый навязанный размер). Единственное, что
   осталось - max-width:100%, это не "размер", а чисто техническая
   защита от переполнения колонки, если высокий файл вдруг окажется
   шире неё - сам размер всегда берётся из реальных width/height файла. */
.vp-article-hero .vp-article-img--portrait { width: auto; max-width: 100%; }
/* Утренние тезисы 4.9.26 п.7: +2pt (13 + 2*1.5 = 16px). Now a <div> (see
   article.php) since its admin field is a real Quill instance (bold
   toggle) whose saved HTML is already its own <p> - `.vp-article-caption
   p{margin:0}` neutralizes that inner paragraph's browser-default
   margin so it doesn't add space inside this box. */
/* Вечерние тезисы 4.9.26 п.9: a) 3px up (position:relative nudge, pure
   visual offset, same pattern as .vp-nav/.vp-search-icon elsewhere).
   b) -1pt (16 - 1*1.5 = 14.5px). */
/* Владелец (13.09.2026, "правила вёрстки публикаций"): подпись к
   ГЛАВНОЙ иллюстрации обязана быть той же гарнитурой, что основной
   текст (была system-ui/sans-serif - реальное расхождение с телом,
   которое использует 'Bitter'), и на 2 "кегля" МЕНЬШЕ основного текста
   - тело 16px, та же формула перевода pt->px, что уже устоялась в этом
   файле (1pt = 1.5px): 16 - 2*1.5 = 13px (было 14.5px). */
/* Владелец (13.09.2026): "цвет шрифта подписи к главному фото - 100%
   Black" - было #777 (серый), не сам сайтовый "почти чёрный" #1a1a1a,
   а буквально #000, отдельное явное требование именно для этой подписи. */
/* Владелец (14.09.2026, "в библию"): раньше подпись была flex:1 -
   "сколько осталось места, столько и забирай". Для горизонтальных фото
   это давало ~119px (900+px hero минус 86.4% фото минус 24px gap) - для
   вертикальных (портретных) фото, где фото само куда уже (max-width:
   540px), тот же flex:1 отдавал подписи ВСЁ оставшееся место (сотни
   px, реально до трети-половины ширины текстового блока) - подпись
   растягивалась и визуально расходилась с горизонтальным случаем.
   Теперь подпись - фиксированной ширины (не flex:1) везде одинаково,
   числом взята именно та ширина, что подпись реально занимает в
   горизонтальном случае (замерено живьём) - гарантирует, что для
   вертикальных фото она больше не "не расходится", а именно совпадает
   1-в-1, а не просто "стала поменьше". Gap (24px, .vp-article-hero) уже
   был одинаковым в обоих случаях - не трогаем. */
/* Владелец 19.09.2026: +1 кегль (13->14px), интерлиньяж -1px
   (19.5px -> 18.5px, было 1.5 от старого 13px - теперь фиксированный px,
   чтобы точно соответствовать запрошенному "минус 1 пункт"). */
.vp-article-caption {
  /* Владелец 29.09.2026: в 1,8 раза шире (119 * 1.8 = 214.2px).
     Владелец 02.10.2026: часть этой ширины отдана фото (см. .vp-article-
     hero .vp-article-img выше) - 132.8px, это всё ещё шире, чем
     исходные 119px, на десктопе; в мобильной версии подпись по-прежнему
     во всю ширину. */
  flex: 0 0 132.8px; width: 132.8px; margin: 0;
  font-family: 'Bitter', Georgia, "Times New Roman", serif; font-size: 14px; line-height: 18.5px; color: #000;
  position: relative; top: -3px;
}
.vp-article-caption p { margin: 0; }
/* п.5: one responsive 16:9 box for both the auto-converted YouTube embed
   and a pasted third-party <iframe> (VKvideo etc., see
   vp_video_embed_html() in inc/blocks.php) - aspect-ratio keeps it
   correctly proportioned at any width without the old padding-top hack.
   Also usable inline in the body text itself (Quill's own video button). */
.vp-video-embed { position: relative; width: 100%; aspect-ratio: 16 / 9; background: #000; margin: 20px 0; }
.vp-article-hero .vp-video-embed { margin: 0; }
.vp-video-embed iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
@media (max-width: 700px) {
  .vp-article-hero { flex-direction: column; }
  .vp-article-hero .vp-article-img, .vp-article-hero .vp-video-embed { width: 100%; }
  /* Владелец 21.09.2026: тот же принцип, что и на десктопе - портретное
     фото не растягиваем шире его собственного размера файла и на
     мобильном тоже, только сжимаем при необходимости (max-width). */
  .vp-article-hero .vp-article-img--portrait { width: auto; max-width: 100%; }
  .vp-article-hero .vp-article-caption { width: 100%; flex: 0 0 auto; }
}

/* п.3: the running text column is the mirror move - originally a 90%/
   10%-indent percentage pair, now (Вечерние тюн-тезисы 2026-09-03 п.3) a
   literal fixed 900px - "most illustrations on the old site were 900px
   wide", so that's the new reference width, not a fraction of whatever
   .vp-article-hero happens to measure. Still flush at the content
   block's own RIGHT edge (margin-left:auto with margin-right:0 pushes a
   fixed-width block as far right as its container allows), still 100%-
   capped for viewports narrower than 900px. Paragraphs inside stay
   left-aligned/ragged-right ("выключка влево" in the OTHER, purely
   typographic sense of that phrase - not the block-position sense
   above) - already every browser's own default, no separate rule needed
   for that part. */
/* Вечерние тезисы 4.9.26 п.9c: line-height -1 "notch" off the site's
   global 1.6 (body{line-height:1.6}, otherwise inherited as-is) -> 1.5,
   explicit override scoped to this template only. */
/* Десктопные тезисы 8.9.26 п.2 + п.4, applied together (they compose):
   п.2 shifts the whole reading column 25px LEFT without changing its
   width -> margin-right:25px (it is right-flush via margin-left:auto, so
   a right margin is what moves it). п.4 then makes it 25px WIDER,
   growing LEFT into the free white field, since the right edge is
   already flush -> width 900 + 25 = 925px. Net effect measured at
   1440px: left edge 338 -> 288 (50px left in total), right edge
   1238 -> 1213. Every sibling that mimics this column (.vp-lead,
   .vp-share-row-bottom, and the tag list п.8 moves into the article)
   carries the identical width+margin pair below, so the column keeps ONE
   left edge. О проекте takes only п.2, Контакты is positioned
   absolutely by п.5 - both scoped further down. */
/* Владелец 08.10.2026: реальный скриншот с телефона подтвердил, что
   .vp-related-articles (вчерашний фикс) - НЕ единственная причина
   горизонтального "гуляния" страницы - абзац "От редакции" САМ
   переполняет свою колонку, когда включён крупный текст (кнопка
   "Aa" - data-fontsize="large"/"xlarge"). Причина: текст набран со
   связывающими пробелами (&nbsp;) между короткими словами и соседним
   словом/ссылкой (типографское правило - не отрывать предлог/короткое
   слово в конец строки) - при обычном размере шрифта на 875-925px
   колонке это не создаёт проблем, но при увеличенном через Aa кегле
   один из таких неразрывных кусков перестаёт помещаться в строку
   целиком - без overflow-wrap браузер скорее вылезет за край
   колонки, чем разорвёт &nbsp;-связку. Подтверждено живьём
   (data-fontsize="large": 11px переполнения; "xlarge": 51px) -
   на обычном размере шрифта не воспроизводится вообще, поэтому
   раньше не был пойман. overflow-wrap:break-word - разрешает перенос
   ТОЛЬКО когда обычных точек переноса не хватает (не трогает nbsp-
   связки и обычный перенос слов в штатных случаях). */
.vp-article-body { width: 925px; max-width: 100%; margin-left: auto; margin-right: 25px; line-height: 1.5; overflow-wrap: break-word; }
/* п.2 "аналогично - на странице О проекте": the shift, but NOT п.4's
   widening (that ask named Публикация only), so the width stays 900. */
.vp-page--about .vp-article-body { width: 900px; }
/* п.5: Контакты' text block is aligned to a specific vertical line - the
   header menu's and the footer's first link column's shared left edge
   (x=406 at 1440px, see the +6px lockstep shift in п.3 above). The
   column is right-flush at x=1238, so 1238-406 = 832px wide with no
   right margin puts its left edge exactly on that line. */
.vp-page--contacts .vp-article-body { width: 832px; margin-right: 0; }

/* Вечерние тезисы 6.9.26 п.5/6: О проекте and Контакты (page.php's
   constructor pages) now use the Публикация layout - same white page,
   same 900px right-flush reading column (.vp-article-body above, reused
   verbatim rather than duplicated) - minus the date/place row and the
   author block, which those pages have no data for anyway. Only the
   heading differs from a real article's: left-ranged (was centered),
   100% black (was #5f5f5f) and +4pt on the page.php size it had
   (30 + 4*1.5 = 36px), NOT the 47px an article h1 uses. */
.vp-page-title {
  font-size: 36px; text-align: left; color: #000;
  line-height: 1.15; margin: 0 0 16px;
}
/* Утренние тезисы 7.9.26 п.4: Контакты only (О проекте deliberately left
   as it was - the ask named just this page). a) heading-to-text gap
   doubled, 16 -> 36px (the owner's own "+20 pix примерно"). b) the last
   line ("Собственная лёгкая CMS...") gets double the normal
   paragraph-to-paragraph gap - adjacent <p> margins collapse to 1em
   (16px) here, so 32px - and goes italic. */
.vp-page--contacts .vp-page-title { margin-bottom: 36px; }
.vp-page--contacts .vp-article-body > p:last-child {
  margin-top: 32px; font-style: italic;
}
/* Владелец 01.10.2026: страница короткая, и под контентным блоком
   оставалась пустота - main растягивается до футера. Часть её перенесена
   наверх. Сверху до заголовка должно быть МЕНЬШЕ, чем снизу до футера:
   блок поднят на две строки основного текста (2 * 24px) от середины -
   72px сверху против 168px снизу (было 38 и 202).
   Только десктоп - на мобильном свободного места нет, там страница
   и так прокручивается. */
@media (min-width: 701px) {
  .vp-page--contacts .vp-page-title { margin-bottom: 70px; }
}

/* п.1 (Вечерние тюн-тезисы 2026-09-03): the lead paragraph is now its
   own dedicated admin field (see admin/article-edit.php's separate Quill
   window, article.php's $lead) rendered as its own <p class="vp-lead">
   - NOT auto-detected via :first-child on body_ru any more (that guess
   broke whenever a body happened to open with something other than a
   plain paragraph - a blockquote translator's-note, a heading, etc. -
   showing whatever THAT was in the lead's visual "slot" instead).
   Italic, NOT bold (must read as visually distinct from Вынос below,
   per the owner's explicit ask) - originally +4pt (22px) over the
   16px body default, tuned down 1px to 21px the same evening.
   Уточняющие тезисы 2026-09-03: п.2 - same width/position as the body
   text right below it (was full content width before, genuinely wider
   than .vp-article-body - a real mismatch, not just a subtle one).
   п.3 - gap down to the body x1.5 (20px -> 30px). */
/* Тезисы 4.9.26 п.3: -2pt (21 - 2*1.5 = 18px). */
/* Now a <div> (see article.php) for the same reason as .vp-article-
   caption above - Quill's own saved HTML for anything typed fresh is
   already p-wrapped. */
/* Десктопные тезисы 8.9.26 п.2/п.4: the lead is part of the same reading
   column, so it takes the identical width+right-margin pair as
   .vp-article-body - otherwise the column would have two left edges. */
.vp-lead {
  font-style: italic; font-size: 18px; line-height: 1.5;
  width: 925px; max-width: 100%; margin: 0 25px 30px auto;
}
.vp-lead p { margin: 0; }
/* Владелец 16.10.2026: выключка лида/эпиграфа из редактора (ql-align-*). */
.vp-lead .ql-align-center { text-align: center; }
.vp-lead .ql-align-right { text-align: right; }
.vp-lead .ql-align-justify { text-align: justify; }
/* Утренние тезисы 10.9.26, побочная находка: a lead field can itself hold
   an <img> (e.g. friedrichstein carries a 900px one), and unlike
   .vp-article-body this block had no max-width for images - so it pushed
   past the viewport at 390px. Same treatment as the body's illustrations. */
.vp-lead img { display: block; max-width: 100%; height: auto; }

/* п.6/8: "Вынос" reuses Quill's OWN blockquote button (admin/article-
   edit.php) rather than a bespoke format/button - centered, bold+italic,
   +2pt over body text (16 + 2*1.5 = 19px) - visibly smaller AND bold
   where .vp-lead above is bigger and NOT bold, the two deliberately
   distinct. Flanking rules (Вечерние тюн-тезисы 2026-09-03 п.5): 2pt
   thick now (was 1pt), and narrower than the text column instead of
   running edge-to-edge - 80% width (720px when the body is 900px, per
   the owner's own example), centered under the centered text. A plain
   border-top/bottom on the blockquote itself can't do "narrower than
   its own box", hence the switch to ::before/::after pseudo-element
   lines instead of a real border. */
/* Уточняющие тезисы 2026-09-03 п.8: the OUTER distance to the
   surrounding body text (this margin - separate from the padding that
   holds the rules close to the quote text itself, untouched) cut by
   1.5x: 32 / 1.5 = 21.33px. */
.vp-article-body blockquote {
  position: relative;
  margin: 21.33px 0; padding: 12.56px 0; border: none;
  text-align: center; font-weight: 700; font-style: italic;
  font-size: 19px; line-height: 1.5;
  /* Владелец 02.10.2026: выносы были шире, чем иллюстрации в тексте
     (925px - вся колонка - против их 875px). Та же ширина, что и у
     иллюстраций - min() на случай узкого экрана (та же оговорка, что
     уже ловилась на иллюстрациях: голое число без min() не сжимается
     на телефоне). Margin слева и так был 0 (не auto) - при ширине
     меньше колонки вынос поэтому сам, без доп. правил, примыкает к
     левому краю, а не центрируется как блок; текст и линейки сверху/
     снизу внутри него по-прежнему centered - это их собственная,
     отдельная выключка (text-align и margin:auto у ::before/::after
     ниже), её никто не трогает.
     Владелец 02.10.2026 (вечер, регрессия): ЭТО ДОЛЖНО БЫТЬ max-width,
     НЕ width. С голым "width" вынос перестал обтекать врезку (float:
     right, см. display:flow-root ниже - тот самый механизм обтекания) -
     у flow-root-блока с ЗАДАННОЙ шириной нет места сжаться до
     доступного рядом с float пространства, он просто не помещается
     там и падает целиком НИЖЕ врезки, оставляя пустую полосу сбоку.
     "max-width" оставляет сам width в auto - вынос по-прежнему не
     шире 875px, когда рядом нет врезки (shrink-to-fit у flow-root-блока
     без float обычно и даёт полную доступную ширину, max-width её
     подрезает), но рядом с врезкой shrink-to-fit использует ОСТАВШУЮСЯ
     (меньшую) ширину, а не фиксированную 875 - ровно та гибкость,
     которая и обеспечивала обтекание изначально. */
  max-width: min(875px, 100%);
}
/* Тезисы 4.9.26 (утро) п.3: 40px shorter overall (20px off each side) -
   calc() keeps the original responsive 80% as the base instead of
   switching to a flat px width, so this still scales with the body
   column if that 900px ever changes. */
.vp-article-body blockquote::before,
.vp-article-body blockquote::after {
  content: ""; display: block; width: calc(80% - 40px); border-top: 2pt solid #2eaadc;
}
.vp-article-body blockquote::before { margin: 0 auto 20px; }
.vp-article-body blockquote::after { margin: 20px auto 0; }
/* Владелец 17.09.2026: абзацы (и пустая строка-интервал между ними)
   внутри ОДНОГО выноса - у Quill'а нет своего группирующего блота для
   вынос/blockquote (в отличие от List), поэтому несколько строк подряд
   с форматом "цитата" превращаются в НЕСКОЛЬКО отдельных <blockquote>,
   каждый со своими линейками. Считаем любой ряд подряд идущих
   <blockquote> ОДНИМ визуальным выносом: линейка-разделитель - только
   у первого сверху и последнего снизу, а между соседними - гораздо
   меньший, обычный межабзацный зазор вместо полного отступа+линейки,
   рассчитанного на два РАЗНЫХ по смыслу выноса. */
.vp-article-body blockquote + blockquote { margin-top: 12px; }
.vp-article-body blockquote + blockquote::before { display: none; }
.vp-article-body blockquote:has(+ blockquote) { margin-bottom: 12px; }
.vp-article-body blockquote:has(+ blockquote)::after { display: none; }

/* п.6/8: "Врезка" reuses Quill's OWN code-block button - restyled away
   from its default monospace/dark-panel look into a plain boxed callout
   that reads as body copy (Bitter), not source code. */
.vp-article-body pre.ql-syntax {
  font-family: 'Bitter', Georgia, serif; white-space: pre-wrap;
  background: #f7f4f1; border-left: 4px solid #7a2e2e;
  padding: 18px 22px; margin: 32px 0; font-size: 16px; line-height: 1.6; color: #1a1a1a;
}

/* п.7: subheadings always get +1 extra line of space above them, on top
   of the normal paragraph-to-paragraph gap (browser-default 1em top/
   bottom on <p>, collapsing to ~1em between two adjacent paragraphs) -
   1em (that normal gap) + 1.6em (one full line at body's own 1.6
   line-height) = 2.6em.
   Тезисы 4.9.26 п.6: that computed to a real x3-too-big gap live (48.67px
   on an 18.72px h3, would be 62.4px on a 24px h2) - `em` here scales off
   the HEADING's own (larger) font-size, not the body text's, so "2.6em"
   never actually meant "1 body line" the way the comment intended. Fixed
   px instead, computed once from the body's real 16px/1.6: 1em=16 +
   1.6em=25.6 -> 41.6px, no longer font-size-dependent.
   п.6 also gives subheadings their own explicit font-size for the first
   time (+2pt over the browser-default sizes this inherited before:
   h2 24+3=27px, h3 18.72->21.72px, cleaned to 22px) - needed to make
   "+2pt" a real, stated delta rather than an invisible browser default. */
/* Владелец 29.09.2026: интервал над подзаголовками увеличен на одну
   строку основного текста (24 -> 48px). Снизу остаётся 0 - подзаголовок
   вплотную к своему тексту. Мягкий перенос (.vp-p-nogap-true, правило
   ниже) по-прежнему убирает верхний отступ целиком, поэтому два
   подзаголовка подряд стоят вплотную. */
.vp-article-body h2, .vp-article-body h3 { margin-top: 48px; margin-bottom: 0; }

/* Владелец 18.09.2026: Shift+Enter в редакторе - принудительный перенос
   строки БЕЗ обычного межабзацного интервала ("мягкий перенос"),
   полезно для пустой строки в вёрстке. Обычные margin схлопываются
   между соседними блоками по большему значению, поэтому мало обнулить
   верхний отступ у этой строки - нужно снять и нижний у ПРЕДЫДУЩЕЙ
   (см. этот же приём у blockquote + blockquote выше). */
.vp-article-body p.vp-p-nogap-true { margin-top: 0; }
.vp-article-body p:has(+ p.vp-p-nogap-true) { margin-bottom: 0; }

/* Владелец 18.09.2026: фото в 2-3 колонки внутри текста публикации -
   то, что было на старом сайте (десктоп), на мобильном складывается в
   один столбец. Настоящий Quill-формат (photoRow blot), не сырой HTML -
   см. admin/article-edit.php - переживает сохранение как единое целое. */
/* Владелец 27.09.2026: раньше был только верхний отступ (24px 0 0) -
   при отсутствии на странице своего нижнего отступа расстояние ДО
   следующего текста определял схлопнутый margin соседа и почти везде
   раздувалось лишней пустой строкой в самом HTML (отдельно вычищена).
   Теперь симметрично 24px сверху/снизу - как у обычной одиночной
   иллюстрации (см. .vp-article-body p:has(img) ниже). */
.vp-article-body .vp-photo-row {
  display: grid; gap: 16px; margin: 24px 0;
  /* Владелец 03.10.2026: ширина блока "фото в 2-3 колонки" должна
     совпадать с новой шириной одиночных горизонтальных иллюстраций
     (875px, см. [[visitprussia_hero_width_2026-10-02]]). max-width, не
     width - то же самое правило, что и у .vp-article-body blockquote
     (см. [[visitprussia_blockquote_width_search_fix_2026-10-02]]):
     display:grid тоже создаёт свой BFC, и с голым width блок точно так
     же перестал бы сужаться рядом с плавающей врезкой и просто падал
     бы под неё целиком, а не вставал рядом. margin слева/справа и так
     был 0 (не auto), поэтому при сужении блок сам, без доп. правил,
     остаётся прижат к левому краю. */
  max-width: min(875px, 100%);
}
.vp-article-body .vp-photo-row[data-cols="2"] { grid-template-columns: repeat(2, 1fr); }
.vp-article-body .vp-photo-row[data-cols="3"] { grid-template-columns: repeat(3, 1fr); }
.vp-article-body .vp-photo-row figure { margin: 0; }
.vp-article-body .vp-photo-row img {
  display: block; width: 100%; height: auto; margin: 0;
}
.vp-article-body .vp-photo-row figcaption {
  font-family: 'Bitter', Georgia, "Times New Roman", serif; font-size: 16px;
  font-style: italic; color: #000; margin-top: 0;
}
/* Владелец 02.10.2026: добавлена возможность делать п/ж фрагменты внутри
   этой подписи (admin/article-edit.php, vp-photorow-caption).
   Тем же днём сюда было добавлено font-style:normal на <strong>, чтобы
   жирный фрагмент не становился Bold+Italic - отменено: владелец отдельно
   объяснил про подписи к иллюстрациям вообще ("Жирный курсив в подписях...
   их стиль - выделение как раз жирным курсивом смысловой основы"), это
   нарочный приём редактора, а не то самое единственное исключение-только-
   для-Цитаты. Жирный фрагмент здесь снова наследует Italic от подписи
   вокруг него, как и везде в остальных подписях на сайте. */
@media (max-width: 600px) {
  .vp-article-body .vp-photo-row[data-cols] { grid-template-columns: 1fr; }
}

/* Владелец 18.09.2026: врезка (v1, "для начала так") - тёмная плашка
   1/4 ширины текстового блока, обтекание текстом. Настоящий Quill-формат
   (vrezka blot), не сырой HTML - см. admin/article-edit.php. */
/* Владелец 18.09.2026: обтекание основным текстом увеличено до 25px
   (было 15px) - внутренние поля вокруг иллюстрации/текста остаются 15px. */
/* Владелец 02.10.2026: врезка должна быть выровнена по верхнему краю
   строки текста, рядом с которой она стоит (враппер - плавающий float,
   вставляется между двумя абзацами в том месте, где стоял курсор - "своя
   строка" это первая строка абзаца СРАЗУ ПОСЛЕ врезки в разметке, именно
   вдоль него идёт обтекание). Раньше единый margin:25px добавлял те же
   25px и СВЕРХУ, из-за чего верх плашки съезжал на 25px ниже верха этой
   строки (измерено живьём на kamen_lzhi: разница 25.0px день в день).
   Убираем только верхний отступ - остальные три стороны (обтекание
   текстом слева/снизу, видимое поле справа до края колонки) не трогаем,
   это другая задача. */
.vp-article-body .vp-vrezka {
  float: right; width: 25%; box-sizing: border-box;
  margin: 0 25px 25px 25px; padding: 15px;
  background-color: rgba(0, 0, 0, 0.7); color: #fff;
}
.vp-article-body .vp-vrezka img { display: block; width: 100%; height: auto; margin: 0 0 15px; }
/* Владелец 18.09.2026: шрифт врезки по умолчанию - PT Sans (Bold у
   заголовка, Regular Italic у текста), не Bitter, как везде на сайте. */
/* Владелец 18.09.2026: кегль заголовка +2pt (был 14px = -2pt от
   основного текста статьи, стал 16px = как основной текст статьи).
   Интерлиньяж заголовка отдельно уменьшен на 15% (наследовавшиеся от
   .vp-article-body 1.5 -> 1.275). */
.vp-article-body .vp-vrezka h4 {
  font-family: 'PT Sans', Arial, sans-serif; font-size: 16px;
  font-weight: 700; font-style: normal; color: #fff; margin: 0 0 8px; line-height: 1.275;
}
/* Владелец 18.09.2026: интерлиньяж текста врезки уменьшен на ~13%
   (было 1.5, стало 1.3 - в запрошенном диапазоне 10-15%). */
.vp-article-body .vp-vrezka p {
  font-family: 'PT Sans', Arial, sans-serif; font-size: 14px;
  font-weight: 400; font-style: normal; color: #fff; margin: 0 0 8px; line-height: 1.3;
}
.vp-article-body .vp-vrezka p em { font-style: italic; }
.vp-article-body .vp-vrezka p strong { font-weight: 700; }
.vp-article-body .vp-vrezka p:last-child { margin-bottom: 0; }
/* Владелец 03.10.2026: мягкий перенос строки (Shift+Enter) во врезке -
   уникальный тег <vp-br>, не <br> (см. admin/article-edit.php - обычный
   <br> как tagName этого блота ломал разбор ЛЮБОГО <br> в теле любой
   статьи при каждой загрузке в редактор). Без display:block браузер
   показал бы его как безымянный инлайн-элемент без переноса вообще. */
.vp-article-body .vp-vrezka p vp-br { display: block; }
@media (max-width: 600px) {
  /* Владелец 18.09.2026: полноширинный full-bleed (в край экрана) был
     ошибочным решением - надо вровень с основным текстом, как у обычного
     абзаца, а не в стык с краями экрана. Просто заполняет .vp-article-body
     без своих доп. полей по бокам (те же границы, что у текста). */
  .vp-article-body .vp-vrezka { float: none; width: auto; margin-left: 0; margin-right: 0; margin-top: 25px; margin-bottom: 25px; }
}
.vp-article-body h2 { font-size: 27px; }
.vp-article-body h3 { font-size: 22px; }

/* п.2/8: custom Quill format (admin/article-edit.php's "Cap" toolbar
   button) for a caption under an inline illustration - the checklist's
   "Подпись к иллюстрации", authored as its own paragraph right after an
   inserted image rather than a combined figure/figcaption blot. Quill's
   ClassAttributor appends the format's VALUE to the class name (verified
   live in the admin editor) - "vp-caption-true", not the plain
   "vp-caption" a hand-written class toggle would produce.
   Вечерние тюн-тезисы 2026-09-03 п.6 had put a default 20px margin
   below every illustration (collapsed to 0 specifically when a caption
   follows). Уточняющие тезисы 2026-09-03 п.4: that 20px was a real
   regression, not a neutral default - most migrated articles already
   space their inline illustrations by hand with literal <br><br> inside
   the surrounding <span> (confirmed live: heart_of_koenigsberg's own
   body), so the margin was STACKING on top of that existing manual
   spacing instead of replacing it, producing the "suddenly huge gaps"
   the owner reported - on images with AND without a caption alike, since
   the base margin was there regardless. Fix: no default spacing at all
   ("не нужны ВООБЩЕ", the owner's own words) - .vp-caption-true's own
   5px margin-top (unchanged) is the entire gap when a caption is used;
   an editor wanting space around a caption-less illustration adds their
   own blank line/paragraph break, same as everywhere else in this
   editor. max-width/height:auto stay as a real safety cap (Quill's
   inline "image" button drops a bare, unclassed <img> with no sizing of
   its own - unrelated to the margin bug, still needed). */
/* Завершая сессию 4.9.26 п.5: that "no spacing at all" was specifically
   about the DOWNWARD side (image-to-caption, still margin:0 - the owner
   is happy with that one) - the br-stripping content migration (see
   memory) fixed the INCONSISTENCY of the gap ABOVE an image, but landed
   it at a flat 0 for every article, which now reads as text sitting
   flush against the illustration from above. Real, deterministic "one
   line" gap on that side only: one line at this template's own
   line-height (16px font * 1.5 = 24px). Margin-collapse with whatever
   ordinary top margin the preceding paragraph already has (Quill's
   default <p>, unset here - browser default ~1em/16px) resolves to the
   larger of the two (24px) automatically, so this alone is enough
   regardless of what precedes the image. */
.vp-article-body img { display: block; max-width: 100%; height: auto; margin: 24px 0 0; }
/* Владелец 02.10.2026: горизонтальные иллюстрации В ТЕКСТЕ должны быть
   хотя бы на 20px уже главной (875px против 895.2px) - иначе на их фоне
   теперь куда заметнее уже-не-такая-узкая главная иллюстрация не читалась
   бы как действительно главная. Только текстовые абзацы (.vp-article-
   body p img) - враезки/фото-в-колонки/галерея не задеты, у них своя
   разметка без <p> и свои собственные правила ширины. Портретные - тоже
   не задеты (:not), им нельзя навязывать размер (см. выше). Ничего не
   меняется для выключки: без явного класса от редактора (ql-align-*)
   картинка как блочный элемент и так стоит по левому краю - собственных
   margin:auto тут никогда не было. */
.vp-article-body p img:not(.vp-img-portrait) { max-width: min(875px, 100%); }
/* Тулбар-опция "Выключить" (Quill align: центр/право/по ширине) -
   класс ql-align-* от самого Quill, без соответствующего правила здесь
   работал только визуально в самой админке (там свой quill.snow.css),
   а не на публичной странице. Общее правило для любого элемента без
   специфического стиля - картинки дополнительно получают auto-margins,
   т.к. они display:block и одного text-align им не хватает. */
.vp-article-body .ql-align-center { text-align: center; }
.vp-article-body .ql-align-center img { margin-left: auto; margin-right: auto; }
.vp-article-body .ql-align-right { text-align: right; }
.vp-article-body .ql-align-right img { margin-left: auto; margin-right: 0; }
.vp-article-body .ql-align-justify { text-align: justify; }

/* --- ВРЕЗКИ (тезисы 7.9.26 п.9, оформление задано 10.9.26) ---
   On the old site these sit in the margin beside the paragraph they
   annotate, adding detail/фактуру to it, and were simply hidden on
   mobile. Owner's brief: render them "вывороткой на темно-серых (72%
   Black) плашках", on mobile too.
   72% black in this codebase's own "N% black" convention (see the #4d4d4d
   / 70% note on .vp-map-legend elsewhere): 255 * (1 - 0.72) = 71.4 -> 71
   -> #474747.
   These are NOT rare: 293 blocks across 64 articles came over from the
   old site, all carrying the single class `page-block`, and 116 of them
   contain an <img>. Median body 672 chars (p90 1768, max 4916) - real
   supplementary blocks, not one-line captions, which is why a true
   narrow margin note isn't practical: the free white field left of the
   925px reading column is only ~102px. A float beside the text is the
   closest honest realisation of "на полях... рядом с фрагментом" - the
   block sits alongside the passage it belongs to and the text wraps
   around it.
   CORRECTED 10.9.26 (дневные тезисы п.6) — I had styled the WRONG element.
   The owner explained the old admin's mechanism: the grey-dotted toolbar
   button wrapped a fragment of the MAIN TEXT together with its врезка
   (that wrapper is `.page-block`), and only then did the yellow-dotted
   button mark off the врезка itself INSIDE it (that is `.page-caption`).
   So `.page-block` is the "Родительский фрагмент" - ordinary body text -
   and my plate on it produced exactly the mess reported: врезки on grey
   plates sitting on even darker plates.
   Owner's decision: the parent fragment gets NO plate, and the врезка
   itself is a dark plate the FULL WIDTH OF THE TEXT, inline in the body
   rather than in the margin ("Врезки у нас будут в теле Публикации, а не
   на полях"). Hence no float and no 42% width anywhere below. */
.vp-article-body .page-block {
  float: none; clear: none;
  width: auto;
  margin: 0;
  padding: 0;
  background: none;
  color: inherit;
  font-size: inherit;
  line-height: inherit;
}
/* The врезка itself: 72% black (255 * 0.28 = 71 -> #474747), inverted.
   Владелец 16.09.2026, первая итерация - назад к плавающей врезке
   ("название говорит само за себя - он врезается в текст, который его
   обтекает"): примыкает к правому краю текстового блока, ширина 25% от
   него, текст обтекает сверху/слева/снизу. Актуальные параметры плашки
   (тёмный фон, инверсия, кегль/интерлиньяж, паддинг) не трогаем -
   меняется только позиционирование. */
.vp-article-body .page-block .page-caption {
  float: right; clear: none;
  width: 25%;
  box-sizing: border-box;
  margin: 4px 0 20px 24px;
  padding: 20px 22px;
  background: #474747;
  color: #fff;
  font-size: 15px;
  line-height: 1.5;
}
/* Первая итерация - на мобильном 25% ширины было бы нечитаемой узкой
   полоской, поэтому во всём остальном пока во всю ширину, как раньше. */
@media (max-width: 600px) {
  .vp-article-body .page-block .page-caption {
    float: none; width: auto; margin: 24px 0;
  }
}
.vp-article-body .page-block .page-caption p { margin: 0 0 12px; }
.vp-article-body .page-block .page-caption > *:last-child { margin-bottom: 0; }
/* The site accent (#2eaadc) only reaches ~3.7:1 against this plate, so
   links get a lightened tint of the same hue - white body text on
   #474747 is ~9.5:1 and needs no help. */
.vp-article-body .page-block .page-caption a { color: #8ad6f2; }
.vp-article-body .page-block .page-caption a:hover { color: #b6e6f8; }
/* An image inside a врезка fills the plate. */
.vp-article-body .page-block .page-caption img { width: 100%; margin: 0 0 12px; }
/* Guard added 13.9.26: a врезка's image is normally meant to fill the
   plate edge-to-edge, but small portrait thumbnails (already flagged
   .vp-img-portrait by vp_mark_portrait_images()) were getting the same
   unconditional width:100%, upscaling a real 140x180px source to ~880px
   wide and badly blurring it. Let the portrait-marked case fall back to
   its natural size instead. */
.vp-article-body .page-block .page-caption img.vp-img-portrait { width: auto; max-width: 100%; }
.vp-article-body .page-block .page-caption img:last-child { margin-bottom: 0; }
/* Тезисы 12.09.2026: ТРЕТИЙ, отдельный смысл того же .page-block wrapper -
   two (or more) `.thumbnail` children (each an <img> + <em> caption),
   found on rybnaya_derevnya_gorditsya_ili_styditsya - a side-by-side pair
   of illustrations, meant to sit in a row on the old site. Had zero CSS
   here before, so they fell through to plain stacked full-width divs with
   huge gaps. Distinct from .page-caption (the вынос-как-плашка case) -
   this selector only matches when a .thumbnail child is actually present. */
.vp-article-body .page-block:has(.thumbnail) {
  display: flex;
  flex-wrap: wrap;
  gap: 20px;
  align-items: flex-start;
  margin: 24px 0;
}
.vp-article-body .page-block .thumbnail {
  flex: 1 1 260px;
  min-width: 0;
}
.vp-article-body .page-block .thumbnail img { width: 100%; height: auto; display: block; }
.vp-article-body .page-block .thumbnail em {
  display: block; margin-top: 8px; font-size: 14px; line-height: 1.4; color: #666; font-style: italic;
}
/* A parent fragment that carries an illustration keeps the ordinary
   in-body illustration treatment, not the plate's. */
.vp-article-body .page-block > p img,
.vp-article-body .page-block > img { width: auto; }
/* The other 3 `.page-caption`s live in the running text, right after a
   Вынос, and are ordinary photo captions ("Орган Альтроссгартенской
   кирхи") - white background, so they take the site's usual caption
   treatment, NOT a plate. */
.vp-article-body > .page-caption,
.vp-article-body blockquote + .page-caption {
  font-size: 14.5px; line-height: 1.5; color: #777; margin: 8px 0 20px;
}
/* A врезка must never poke out below the article into "Читайте также". */
.vp-article-body::after { content: ""; display: block; clear: both; }
/* Вынос (blockquote) draws flanking horizontal rules via ::before/::after.
   A block box still spans the full column width beside a float - only its
   LINE boxes shrink - so those rules ran straight underneath the plate.
   flow-root makes the blockquote its own formatting context, so the whole
   box (rules included) sits beside the float instead of under it.
   Chosen over overflow:hidden, which would clip the rules' own margins. */
.vp-article-body blockquote { display: flow-root; }
/* (The mobile override that used to live here undid the desktop float.
   There is no float any more - врезки are full text width in both
   versions per дневные тезисы 10.9.26 п.6 - so nothing to override.) */

/* SAFETY NET, added 10.9.26: one guard so no image anywhere inside a
   publication can ever exceed its container, whatever markup or future
   rule it arrives with. This is deliberate belt-and-braces: a CSS syntax
   error of mine earlier today silently killed the rules that follow a
   comment, and an unconstrained image is exactly the symptom the owner
   reported. Cheap insurance against that whole class of failure. */
.vp-article img, .vp-lead img { max-width: 100%; }
/* Тезисы 4.9.26 п.5: 5px still read as a real gap - flush now (0),
   "почти вплотную" per the owner's own description; the font's own
   internal leading above cap-height is all the visual space that's left,
   nothing added on top of it by this rule. */
.vp-article-body .vp-caption-true {
  font-family: 'Bitter', Georgia, "Times New Roman", serif; font-style: italic; font-size: 16px; color: #000; margin-top: 0;
}

/* --- Утренние тезисы 10.9.26 --- */

/* п.1: a centred portrait illustration must have its caption centred too.
   The migrated bodies keep the caption in the SAME paragraph as the image
   far more often than in a marked-up caption element (there is no single
   caption class in this content - see the caption survey), so centring the
   whole paragraph that holds the portrait image is what actually catches
   the real cases. The image itself is already centred by its own auto
   margins; this adds the text beside/below it. Landscape illustrations are
   untouched: they stay flush left, as does the hero whatever its shape.
   NOTE the selector is `p:has(img…)`, matching the image at ANY depth -
   the migrated markup very often wraps it (`<p><span><img></span></p>`,
   also em/strong/a), so a direct-child `>` selector silently missed most
   real cases (measured: it left them at the browser default 16px). */
/* Владелец 29.09.2026: раньше вертикальная иллюстрация центрировалась
   САМА, по ориентации файла, и выбор "по левому краю" в редакторе ничего
   не менял - выбор "влево" у Quill означает "снять класс выключки", а
   правило по ориентации тут же центрировало картинку снова. Теперь
   выключка - свойство самого абзаца: центр ставится только там, где он
   задан явно (ql-align-center). Всем абзацам, которые центрировались
   прежним правилом, класс проставлен разовым проходом по контенту
   (286 абзацев в 159 статьях, fix_portrait_align.php), поэтому вид
   страниц не изменился - изменилось лишь то, что теперь это можно
   переназначить из редактора. */
/* Выключка абзаца управляет картинкой любой формы, а не только
   вертикальной: иначе горизонтальное фото нельзя было бы выключить по
   центру из редактора - ровно та же беда, только наоборот. Картинка -
   display:block, её двигают поля, а не text-align. */
.vp-article-body p.ql-align-left img { margin-left: 0; margin-right: auto; }
.vp-article-body p.ql-align-right img { margin-left: auto; margin-right: 0; }
/* and the properly-marked caption element, when it follows a portrait */
.vp-article-body img.vp-img-portrait + .vp-caption-true,
.vp-article-body p.ql-align-center:has(img) + .vp-caption-true { text-align: center; }
/* Владелец 02.10.2026 (третий заход, найдено на kamen_lzhi, lang=lt):
   "подпись" в реальном контенте - это НЕ один застывший шаблон. Обзор
   всего корпуса (scan_caption_patterns.php) нашёл помимо простого
   ".vp-caption-true" ещё непомеченные продолжения второй/третьей строки
   - просто ещё один <p class="...ql-align-center..."> без vp-caption-true
   вообще (сам факт, что строка своя собственным классом ql-align-center
   выключена по центру, и есть её разметка-как-подпись - отдельного
   класса "подпись" в этом контенте в принципе нет, см. комментарий у
   .vp-caption-true выше). Правило выше красит центром только ПЕРВУЮ
   .vp-caption-true - у второй/третьей СТРОКИ, если это НЕ отдельная
   .vp-caption-true, а голый .ql-align-center, центр уже есть от
   собственного класса, но если это снова .vp-caption-true БЕЗ своего
   ql-align-center (как во flora_kaliningradskoj...), эта продолжающая
   строка оставалась textAlign:start - некрасиво, хотя и не "уезжала"
   вправо (ширину это отдельное 2209-2232 правило уже чинит). Цепочка
   ниже красит центром КАЖДОЕ звено цепочки (безвредно даже для тех,
   что и так self-ql-align-center - то же самое значение). */
/* Владелец 03.10.2026: якорь цепочки ниже был :has(img.vp-img-portrait) -
   сузил до "только портретных", хотя центрировать текст продолжающей
   строки подписи нужно и у ЛЮБОЙ центрированной иллюстрации (ровно как
   прямой сосед строкой выше, :has(img) без уточнения орентации). */
.vp-article-body p.ql-align-center:has(img) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true),
.vp-article-body p.ql-align-center:has(img) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true),
.vp-article-body p.ql-align-center:has(img) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true),
.vp-article-body p.ql-align-center:has(img) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true),
.vp-article-body p.ql-align-center:has(img) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true),
.vp-article-body p.ql-align-center:has(img) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true)
{ text-align: center; }

/* п.9: one standard body line (16px * 1.5 = 24px) above AND below every
   illustration and every illustration+caption block, and the same to a
   Вынос - replacing the previous mix where an image had 24px above but
   nothing below, so the gap underneath came from whatever the next
   element's own margin happened to be (16px for a paragraph, more for a
   blockquote). Margins between siblings collapse to the larger value, so
   stating 24px on both sides of the image and on the caption's bottom
   makes every one of those junctions resolve to exactly one line.
   Rather than juggling each element's own margins (which failed: the
   image is usually wrapped in a span/em, so its margins sit INSIDE the
   paragraph and direct-child selectors missed it), the spacing is owned by
   ONE element - the paragraph that holds the illustration. The image's own
   vertical margins are zeroed there, so an in-paragraph caption also stays
   tight against its image, which is the earlier "почти вплотную" decision.
   Images that are direct children of the body (no wrapping paragraph) keep
   the margins on themselves. */
.vp-article-body p:has(img) { margin-top: 24px; margin-bottom: 24px; }
.vp-article-body p img { margin-top: 0; margin-bottom: 0; }
.vp-article-body > img { margin: 24px 0; }
/* The marked-up caption element, when it is a sibling rather than inline. */
.vp-article-body .vp-caption-true { margin-bottom: 24px; }
/* Владелец 29.09.2026: подпись вплотную к иллюстрации и в этом случае -
   у абзаца с картинкой убираем нижний отступ, когда следом идёт подпись
   (иначе между ними оставалось 24px). */
.vp-article-body p:has(img):has(+ .vp-caption-true) { margin-bottom: 0; }
.vp-article-body > img:has(+ .vp-caption-true) { margin-bottom: 0; }

/* Quill's own built-in video button (admin toolbar) drops a bare
   <iframe class="ql-video"> straight into the body flow, unwrapped -
   made responsive here the same way .vp-video-embed is for the hero
   slot, so a video pasted mid-article behaves like any other
   full-width illustration instead of keeping Quill's small fixed
   editor-preview size. */
.vp-article-body iframe.ql-video {
  display: block; width: 100%; aspect-ratio: 16 / 9; border: 0; margin: 20px 0;
}

/* п.9: "Читайте также" - 6 standard cards, reusing the exact same grid/
   card markup as a Section page's own feed (.vp-feed-regular-grid).
   Вечерние тюн-тезисы 2026-09-03 п.4: a real grey plate now (was plain
   white, blending into the white Публикация background around it) -
   the SAME grey the rest of the site's pages sit on (#e5e9ea, body's own
   background - Home/Section pages never override it, article.php is the
   only page that does), and a top rule matching - literally reusing, not
   just eyeballing - .vp-site-header's own real border-bottom (1px
   #d1d1d1).
   Уточняющие тезисы 2026-09-03 п.5: full-bleed edge-to-edge like the
   footer itself, flush against it with no gap - same calc(50% - 50vw)
   break-out .vp-load-more already uses elsewhere, plus the same
   margin-bottom:-40px trick to cancel .vp-main's own bottom padding.
   Needs a real inner wrapper (.vp-related-articles-inner, matching how
   .vp-site-header/.vp-site-footer each have their own "-inner" child)
   since the actual heading+card-grid content must stay at the site's
   normal 1100px reading width - only the background/border are meant to
   reach the true viewport edges. Bottom padding specifically 35px (the
   owner's explicit number, distance from the last card row down to the
   footer); top stays the original 30px (not respecified).
   Тезисы 4.9.26: п.8 - one more full body line above the top rule, on
   the owner's own call that the gap read as too tight (48 + 1.6*16 =
   73.6px). п.9 - the plate's own bottom padding +10px (35 -> 45px), same
   "distance from the last card row to the footer" spot as before. */
.vp-related-articles {
  /* Владелец 08.10.2026: та же проблема, что на мобильном (vw != clientWidth
     из-за резерва под scrollbar-gutter - даже на десктопе это давало
     постоянные 8px переполнения на КАЖДОЙ проверенной ширине окна,
     не только на одной), но на десктопе простой "отменить padding
     родителя" не работает - .vp-main сам уже уже вьюпорта. Вместо
     сырого vw - CSS-переменная --vp-vw, проставленная ранним
     инлайн-скриптом в <head> из document.documentElement.clientWidth
     (см. inc/layout.php) - 100vw остаётся запасным значением, если
     скрипт по какой-то причине не выполнился (JS выключен и т.п.). */
  width: var(--vp-vw, 100vw);
  margin-left: calc(50% - (var(--vp-vw, 100vw) / 2));
  margin-right: calc(50% - (var(--vp-vw, 100vw) / 2));
  /* Вечерние тезисы 6.9.26 п.1: 73.6 -> 63.6, the exact 10px that
     .vp-share-row-bottom above gained, so dropping that row 10px lower
     doesn't also push the footer 10px further down. */
  /* Владелец 16.09.2026: -12px здесь, +12px на .vp-article .vp-tags'
     own margin-top - Теги сдвигаются на 12px ниже внутри ТОГО ЖЕ общего
     промежутка (87.6px), а не растягивают его. */
  margin-top: 51.6px;
  margin-bottom: -40px;
  background: #e5e9ea;
  border-top: 1px solid #d1d1d1;
}
/* Тезисы 4.9.26 (утро) п.2: bottom padding +15px (45 -> 60).
   Тезисы 6.9.26 п.4: +5px more (60 -> 65) - distance from the bottom of
   the 3-card row down to the footer. */
.vp-related-articles-inner { max-width: 1100px; margin: 0 auto; padding: 30px 24px 65px; }
.vp-related-articles h2 { font-family: 'Bitter', Georgia, serif; font-size: 24px; margin: 0 0 20px; }
/* Уточняющие тезисы 2026-09-03 п.7: +2pt (14 + 2*1.5 = 17px) - only used
   on article.php (confirmed), so this is a plain increase with no other
   template to worry about. The contributor's own <small> byline-role
   text scales proportionally (browser default ~0.8em of its parent). */
.vp-byline {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin: 20px 0;
  font-family: system-ui, sans-serif;
  font-size: 17px;
  color: #666;
  /* Вечерние тезисы 4.9.26 п.3: Поделиться (the flex:space-between's
     right-hand child) shifts left to align flush with the main photo's
     OWN right edge below it - this padding-right is exactly the width
     the photo does NOT occupy, on the same 100%-of-.vp-article width
     basis both share.
     Владелец 29.09.2026: колонка подписи стала в 1,8 раза шире
     (119 -> 214.2px), фото - соответственно уже, поэтому здесь теперь
     не 13.6%, а те же 238.2px (214.2 подпись + 24 промежуток), что и в
     .vp-article-hero .vp-article-img - иначе "Поделиться" перестал бы
     совпадать с правым краем фото.
     Владелец 02.10.2026: колонка подписи сузилась до 132.8px (фото
     взамен стало шире) - то же самое число здесь, 132.8 + 24 = 156.8px. */
  padding-right: 156.8px;
}
/* Above the <h1> (brief п.9/10): date, then place after a slash. */
.vp-page-time { font-family: system-ui, sans-serif; font-size: 14px; color: #888; margin-bottom: 6px; }
/* Below the <h1> (brief п.7/8): a linked contributor gets a round photo
   + name + one-line byline; no contributor set falls back to plain text
   (.vp-author-name--plain), same look as before this feature existed. */
.vp-page-author { display: flex; align-items: center; gap: 12px; text-decoration: none; color: inherit; }
.vp-page-author-photo {
  width: 50px; height: 50px; border-radius: 50%; object-fit: cover; flex-shrink: 0;
}
.vp-page-author-photo--placeholder { background: linear-gradient(135deg, #f1e9e4, #e6d9cd); }
.vp-page-author-text { display: flex; flex-direction: column; }
/* Тезисы 4.9.26 п.2: Имя (1-я строка) больше не п/ж - "светлым" (только
   на article.php - .vp-author-name is not used anywhere else, confirmed
   before touching it). 2-я строка (роль/регалии контрибьютора) +1pt:
   12.5 + 1*1.5 = 14px. */
.vp-author-name { font-weight: 400; color: #1a1a1a; }
.vp-page-author:hover .vp-author-name { color: #2eaadc; }
/* Тезисы 4.9.26 (утро) п.1: 2-я строка (роль/регалии) курсивом. */
.vp-page-author-text small { color: #888; font-size: 14px; font-style: italic; }
.vp-author-name--plain { font-weight: 400; }
/* Under the share row (brief п.11: same accent color as everywhere else). */
.vp-tags { list-style: none; display: flex; flex-wrap: wrap; gap: 6px 14px; padding: 0; margin: 4px 0 24px; font-family: system-ui, sans-serif; font-size: 13px; text-transform: uppercase; letter-spacing: 0.03em; }
/* Вечерние тезисы 6.9.26 п.2 put the tag list on its own plate below
   "Читайте также"; Десктопные тезисы 8.9.26 п.8 reverses that outright -
   the plate and the whole .vp-tags-bar wrapper are gone (removed from
   article.php too), tags now live inside <article> between the text and
   Поделиться (see .vp-article .vp-tags above). */
/* Тезисы 6.9.26 п.5: second Поделиться copy, between the body/tags and
   the "Читайте также" rule below - own breathing room since it isn't
   inside .vp-byline's flex row like the first copy above.
   Вечерние тезисы 6.9.26 п.1: a) flush with the TEXT's own left edge,
   not the article container's - .vp-article-body is a fixed 900px block
   pushed right (margin-left:auto), so its left edge sits well inside
   the container; the same width+auto-margin pair here lands this row on
   exactly that edge instead of the container's. b) 10px lower
   (24 -> 34) - and .vp-related-articles' own margin-top drops by the
   same 10px (73.6 -> 63.6) so the total distance from the last line of
   text down to the footer is unchanged, per the explicit ask. */
/* Десктопные тезисы 8.9.26 п.1: +12px lower (34 -> 46).
   п.2/п.4: same width+right-margin pair as the rest of the column.
   NOTE: п.8 moves the tag list in ABOVE this row, so the real distance
   from the last line of text down to Поделиться is now the tags' own
   gap + their height + this 46px. Both asks came in the same batch;
   this applies each one literally to its own element. */
/* Утренние тезисы 10.9.26 п.2: Поделиться and Теги swapped, so Поделиться
   is now the FIRST block after the text and inherits the text-to-block gap
   the owner approved for that position (64px total): 48px here plus the
   last paragraph's own 16px bottom margin, which does not collapse because
   .vp-article-body clears its floats. */
.vp-share-row-bottom {
  width: 925px; max-width: 100%; margin: 48px 25px 0 auto;
}
/* Десктопные тезисы 8.9.26 п.8: the tag list moved out of its own plate
   under "Читайте также" and back up into the article, between the last
   line of text and Поделиться. Same column geometry as everything else
   here so it shares the one left edge. */
/* Ночные тезисы 10.9.26 (десктоп) п.1: the gap above Теги must equal the
   one below Поделиться, i.e. the distance down to the grey "Читайте
   также" plate. Measured live: that lower gap is 64px, while this one was
   40px. The 40 was the last paragraph's own 16px bottom margin plus this
   24px (they don't collapse - .vp-article-body clears its floats and so
   ends its own margin flow), so 64 - 16 = 48px here. */
/* Утренние тезисы 10.9.26 п.2: Теги are now the LAST block of the
   publication's own field, sitting one standard body line (24px) under
   Поделиться. The 64px gap below them, down to the grey "Читайте также"
   plate, comes from that plate's own margin-top - so the sequence reads
   text —64— Поделиться —24— Теги —64— Читайте также. */
.vp-article .vp-tags {
  width: 925px; max-width: 100%; margin: 36px 25px 0 auto;
}
/* Ночные тезисы 10.9.26 (десктоп) п.2: on the old site every VERTICALLY
   oriented illustration inside the running text was centred. 291 of the
   2238 in-body images are portrait. The class is set server-side from the
   real media.width/height (vp_mark_portrait_images() in inc/blocks.php) -
   CSS cannot ask an image for its aspect ratio, and the migrated markup
   carries no width/height attributes to go on.
   Explicitly NOT the hero: that lives outside .vp-article-body and stays
   flush left whatever its orientation, per the same ask. */
.vp-article-body p.ql-align-center img { margin-left: auto; margin-right: auto; }
/* Владелец 02.10.2026: после того как горизонтальные иллюстрации и
   выносы сузились до 875px (и остались прижаты к левому краю), у
   портретного фото, центрируемого margin:auto, рамкой для центрирования
   по-прежнему служил абзац шириной 925px (ширина всей текстовой
   колонки, у абзаца с картинкой своей ширины никогда не было) - поэтому
   центр портретного фото (925/2=462.5px от левого края) съехал на 25px
   правее центра 875px-рамки иллюстраций/выносов (875/2=437.5px), что и
   стало заметно рядом друг с другом. Сама картинка остаётся "как есть"
   (ширину ей не навязываем, правило 21.09.2026 не трогается) - сужаем
   только АБЗАЦ, который её центрирует, до той же 875px-рамки. Только
   для центрированных ПОРТРЕТНЫХ (это и есть сегодняшняя жалоба) -
   выравнивание влево/вправо уже и так не зависит от ширины контейнера
   (margin-left:0 / margin-right:0 держат картинку у своего края
   независимо от того, насколько widе сам абзац), поэтому их не
   перепроверяем и не трогаем.
   Владелец 03.10.2026 (два новых случая той же беды): (1) свежезагруженная
   портретная иллюстрация, вставленная в редактор вставкой/перетаскиванием
   (а не кнопкой "Вставить изображение"), раньше не получала класс
   vp-img-portrait вовсе - Quill в этом случае хранит её как data:-URI, а
   не /uploads/-ссылку, и vp_mark_portrait_images() эту картинку просто
   не находила (см. правку той функции в inc/blocks.php - теперь смотрит
   собственные width/height атрибуты тега, а не только БД по /uploads/).
   (2) уменьшенная ГОРИЗОНТАЛЬНАЯ иллюстрация - "портрет" тут вообще ни
   при чём, дело в УЗОСТИ: рамка 875px нужна любой картинке у́же этой
   рамки, не только портретным. Поэтому селектор ниже теперь проверяет
   vp-img-narrow (эффективная ширина <875px, считается в PHP, см. ту же
   функцию) вместо vp-img-portrait - ширина и ориентация оказались
   двумя РАЗНЫМИ, независимыми признаками, которые до сих пор были слиты
   в один. Портретные картинки шириной РОВНО 900px (гербы/печати,
   см. тезисы 02.10.2026 про квадратные/круглые) при этом НЕ сужают свой
   абзац - именно для них backward-compat и важен: иначе они бы
   обрезались по max-width:100% уже более узкого контейнера. */
.vp-article-body p.ql-align-center:has(img.vp-img-narrow) { width: min(875px, 100%); }
/* Та же причина, но для подписи под портретным фото: text-align:center
   (строка 1983-1984) центрирует текст подписи В ПРЕДЕЛАХ СОБСТВЕННОЙ
   ширины блока подписи. Абзац с картинкой мы только что сузили до
   875px, а подпись как была 925px, так и осталась - её текст снова
   центрировался бы на 25px правее уже исправленной картинки. Сужаем
   подпись тем же правилом и до той же рамки.
   Владелец 02.10.2026 (второй заход): подпись - это не всегда ОДИН
   абзац - встречаются 2-3 подряд (текст подписи + отдельной строкой
   "Фото ФИО"/источник). Правило с одним "+" сужало только ПЕРВЫЙ абзац
   подписи - второй/третий оставались на 925px и снова "уезжали"
   вправо относительно уже исправленных картинки и первой строки.
   Третий заход (kamen_lzhi, lang=lt): в реальном контенте продолжающая
   строка подписи не обязана носить класс vp-caption-true вообще - это
   может быть просто ещё один <p class="...ql-align-center...">,
   см. комментарий у правила text-align:center выше (scan_caption_
   patterns.php прогнан по всему корпусу, это подтверждённый, не
   гипотетический список реальных вариантов разметки). "Подпись" в этом
   контенте определяется ТОЛЬКО через эти два класса - отдельного
   класса "подпись" как такового нет.
   Цепочка смежных "+" ловит ЛЮБОЕ число подряд идущих таких абзацев
   (до 6 - в корпусе на сегодня максимум 3), не трогая НИКАКОЙ другой,
   не смежный с этой картинкой caption дальше по странице, и не трогая
   абзац с ДРУГОЙ картинкой (:not(:has(img)) - это уже начало следующей
   иллюстрации, а не продолжение подписи текущей). */
.vp-article-body p.ql-align-center:has(img.vp-img-narrow) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true),
.vp-article-body p.ql-align-center:has(img.vp-img-narrow) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true),
.vp-article-body p.ql-align-center:has(img.vp-img-narrow) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true),
.vp-article-body p.ql-align-center:has(img.vp-img-narrow) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true),
.vp-article-body p.ql-align-center:has(img.vp-img-narrow) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true),
.vp-article-body p.ql-align-center:has(img.vp-img-narrow) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true) + p:not(:has(img)):is(.ql-align-center, .vp-caption-true)
{ width: min(875px, 100%); }
/* единственная картинка в корпусе, лежащая вне абзаца - выключку ей
   задать нечем, оставляем как было (по центру). */
.vp-article-body > img.vp-img-portrait { margin-left: auto; margin-right: auto; }
.vp-tags a { color: #888; text-decoration: none; }
.vp-tags a:hover { color: #2eaadc; text-decoration: underline; }

/* --- Контрибьюторы (listing + single author page) --- */
/* Tezisy-2 п.8: rebuilt as a single stack of full-width horizontal cards
   (was a multi-column grid of square-ish tiles) - matches the original
   site's own Contributors listing. */
.vp-contributors-list { display: flex; flex-direction: column; gap: 20px; margin: 28px 0 60px; }
.vp-contributor-card {
  display: flex; flex-direction: column;
  /* 1px dark-gray (50% black on white) frame, per spec. */
  border: 1px solid #808080;
  /* Every element on the card - photo included - sits this same distance
     from the frame; matches the "equidistant from the edge" rule used
     everywhere else in this pass, reusing the featured-card's 16.1px. */
  padding: 16.1px;
  /* Tezisy 1.9-2 п.7: anchors the now-absolutely-positioned share corner
     below. */
  position: relative;
}
.vp-contributor-card-link {
  display: flex; align-items: flex-start; gap: 16.1px;
  text-decoration: none; color: inherit;
}
.vp-contributor-card-link:hover h3 { color: #2eaadc; }
.vp-contributor-photo, .vp-contributor-photo--placeholder {
  /* Tezisy 1.9-2 п.6: -20% (was 20% of the card's own width, i.e. 1/5 -
     now 16%). .vp-contributor-share's own left-offset below has to move
     with it to stay aligned under the (unchanged-width) text column. */
  width: 16%; aspect-ratio: 1;
  border-radius: 50%; object-fit: cover; flex-shrink: 0;
}
.vp-contributor-photo--placeholder { background: linear-gradient(135deg, #f1e9e4, #e6d9cd); }
/* Tezisy-7 п.2: almost doubled per the owner's explicit ask (their own
   example: "if it's 14 now, set it to 24" - actual base was 16, but 24
   is the literal target they gave, not a ratio to reapply). */
.vp-contributor-card-body h3 { margin: 0 0 4px; font-size: 24px; }
/* Утренние тезисы 7.9.26 п.1a: the "who they are in life" line moves 3pt
   (4.5px) closer to the name, goes italic, and gains 2pt of кегль
   (12.5 + 2*1.5 = 15.5px). The bio below must NOT move, so the 4.5px
   taken off the top is added back to this element's own bottom margin
   (6 + 4.5 = 10.5px) rather than left to shift everything up. */
.vp-contributor-byline {
  margin: -4.5px 0 10.5px; font-size: 15.5px; font-style: italic;
  color: #888; font-family: system-ui, sans-serif;
}
/* Утренние тезисы 7.9.26 п.1b: the listing bio now carries exactly the
   same parameters as the full bio on the personal page (which п.2a below
   re-tuned) - deliberately identical now, so there is no longer a
   separate `--full` override for size/leading. */
.vp-contributor-bio {
  margin: 0; font-size: 16px; line-height: 1.3208;
  color: #666; font-family: system-ui, sans-serif;
}
/* Tezisy-2 п.8: a share row under the text, mostly decorative on this
   listing (each card already links through to the full author page). */
/* Tezisy-3 п.4: aligned under the TEXT column, not the photo - can't
   nest it inside the card's own <a> (vp_share_row() has its own <a>s and
   a <details>, invalid inside another <a>), so it's a sibling positioned
   with a left margin matching the photo's own width (20%) plus the
   card-link's internal gap (16.1px), landing its left edge exactly where
   the text column starts. */
/* Tezisy-5 п.6: flush with the bottom edge of whichever is taller (the
   round photo, in the default/common case where the text column is
   shorter) - it's already a plain flow sibling right after the flex row
   (.vp-contributor-card-link, height = tallest child), so margin-top:0
   IS "at the same level as the photo's bottom edge" by construction; it
   only drops lower on its own, automatically, when the text column ends
   up taller than the photo (was margin-top:14px, an extra gap the owner
   didn't want). */
/* Tezisy 1.9-2 п.7: moved into the card's own bottom-right corner
   (position:absolute against .vp-contributor-card's now position:relative)
   instead of a plain-flow block below the photo/text row - that flow
   block was exactly what made the gap under the photo bigger than the
   gap above it (card padding is symmetric on its own; this row's height
   was the difference). Taking it out of flow entirely makes the card's
   own height purely padding + max(photo, text) again, so top and bottom
   end up equal on their own, with no extra sizing rule needed. */
.vp-contributor-share { position: absolute; right: 16.1px; bottom: 16.1px; }
.vp-contributor-page-header { display: flex; align-items: center; gap: 24px; margin-bottom: 24px; }
/* Tezisy 1.9-2 п.6: -20% (was 120px). */
.vp-contributor-photo-big, .vp-contributor-page-header .vp-contributor-photo--placeholder {
  width: 96px; height: 96px; border-radius: 50%; object-fit: cover; flex-shrink: 0;
}
/* Tezisy 1.9-2(2) п.5: the full biography now renders inside the same
   .vp-contributor-bio paragraph as the listing card's short text (see
   vp_render_contributor_card()'s $full_bio param) - this modifier
   restores the more readable long-form size/line-height the old,
   now-removed standalone .vp-contributor-bio-full block used to have,
   without touching the compact listing-card version. */
/* Вечерние тезисы 6.9.26 п.7: интерлиньяж in the in-frame biography cut
   by a third - 1.7 * 2/3 = 1.1333.
   Утренние тезисы 7.9.26 п.2a: +2pt back on top of that - 16px * 1.1333
   = 18.13px of leading, + 3px = 21.13px, i.e. line-height 1.3208. Now
   lives on the shared .vp-contributor-bio rule above (п.1b made the
   listing and the personal page deliberately identical), so this
   selector only needs to carry what is still specific to the full card.
   Утренние тезисы 7.9.26 п.2b: last line of bio to the frame's inner
   edge +6pt (16.1 + 9 = 25.1px) - padding-bottom only, so the share
   widget pinned to this card's bottom-right corner keeps its own 16.1px
   offset from the frame. */
.vp-contributor-card--full { padding-bottom: 25.1px; }

/* Tezisy п.8: light-gray silhouette icons that pick up their brand color
   on hover (original site's own pattern), split into a Local row
   (RU-legal networks, always visible: VK/Max/OK/LiveJournal + a classic
   "share" glyph) and a Global row (networks blocked in Russia: Telegram/
   Facebook/WhatsApp/Reddit) tucked behind that glyph's dropdown - same
   <details>/<summary> mechanism as the language switcher (п.11), just a
   horizontal menu instead of a grid. One self-hosted monochrome SVG per
   brand, applied as a CSS mask and recolored via background-color -
   there is only ever ONE silhouette drawing needed per icon; "passive
   gray" vs. "brand color" is purely a CSS state on that same mask, not
   two separate artworks to keep in sync. */
.vp-share-row { display: flex; align-items: center; gap: 10px; position: relative; }
.vp-share {
  display: inline-block; width: 20px; height: 20px; flex-shrink: 0;
  background-color: #c2c2c2; /* passive light gray */
  -webkit-mask: var(--vp-icon) no-repeat center / contain;
  mask: var(--vp-icon) no-repeat center / contain;
  text-decoration: none; border: none; padding: 0; cursor: pointer;
  transition: background-color 0.15s ease;
}
.vp-share--vk     { --vp-icon: url('/assets/vendor/icons/vk.svg'); }
.vp-share--vk:hover     { background-color: #0077FF; } /* VK's official brand blue */
/* Tezisy-3 п.3: VK's icon reads visually closer to Max than Max does to
   OK, purely because OK's own mark is a tall/narrow shape with more
   built-in whitespace on either side of it within its 20x20 box - the
   actual CSS gap (10px, box-edge to box-edge) is identical on both
   sides. Only the VK->Max gap gets extra room, on top of the row's
   shared 10px gap; Max->OK is untouched. */
/* Tezisy-5 п.5: another 5px away from Max, on top of tezisy-3's own 4px.
   Tezisy 1.9 п.5: Max itself then shifts 2px further left - shrinking
   THIS gap by 2px moves Max (and everything after it) 2px left, so VK's
   own position is untouched by construction (nothing before VK moved). */
.vp-share--vk { margin-right: 7px; }
/* Tezisy-2 п.6: real MAX mark, extracted directly from the official
   brandbook (go.max.ru/brandbook#logo) - the icon-only bubble path, not
   the placeholder square+"M" guess from before. Brand color is their own
   "Синий гигант" #471AFF, the first/primary swatch in that same
   brandbook's color palette. */
.vp-share--max    { --vp-icon: url('/assets/vendor/icons/max.svg'); }
.vp-share--max:hover    { background-color: #471AFF; }
/* Tezisy 1.9 п.5: compensates the 2px taken from the VK->Max gap above -
   OK (and everything after Max) would otherwise inherit that same 2px
   leftward shift; adding it back here on the Max->OK side cancels it out,
   so OK ends up exactly where it already was - only Max actually moved. */
.vp-share--max { margin-right: 2px; }
.vp-share--ok     { --vp-icon: url('/assets/vendor/icons/ok.svg'); }
.vp-share--ok:hover     { background-color: #EE8208; } /* Odnoklassniki's official orange */
/* Tezisy-2 п.6: LiveJournal dropped from the Local row (was VK/Max/OK/LJ
   + share = 5; the owner's own width-matching plan for the two rows
   hadn't worked via the dropdown's frame alone, so simplifying to 4+4
   instead - Local: VK/Max/OK + share, Global: TG/FB/WhatsApp/Reddit). */
.vp-share--tg     { --vp-icon: url('/assets/vendor/icons/telegram.svg'); }
.vp-share--tg:hover     { background-color: #26A5E4; } /* Telegram's official blue */
.vp-share--fb     { --vp-icon: url('/assets/vendor/icons/facebook.svg'); }
.vp-share--fb:hover     { background-color: #0866FF; } /* Facebook's current official blue */
.vp-share--wa     { --vp-icon: url('/assets/vendor/icons/whatsapp.svg'); }
.vp-share--wa:hover     { background-color: #25D366; } /* WhatsApp's official green */
.vp-share--reddit { --vp-icon: url('/assets/vendor/icons/reddit.svg'); }
.vp-share--reddit:hover { background-color: #FF4500; } /* Reddit's official orange-red */
/* The classic "share" glyph itself is a generic UI icon, not a brand -
   per the owner's own instruction it hovers to the site's established
   secondary accent (#2eaadc, used everywhere else: nav/link hover,
   active nav, search bar) instead of a brand hex. */
.vp-share--more { --vp-icon: url('/assets/vendor/icons/share.svg'); }
.vp-share--more:hover, .vp-share-dropdown[open] .vp-share--more { background-color: #2eaadc; }

.vp-share-dropdown { position: relative; display: inline-flex; align-items: center; }
.vp-share-dropdown > summary { list-style: none; cursor: pointer; }
.vp-share-dropdown > summary::-webkit-details-marker { display: none; }
/* Opens UPWARD - this widget usually sits at the bottom-right of a card
   (tezisy п.10) or near the page bottom, so a downward popup would run
   off-screen/get clipped far more often than an upward one. */
.vp-share-menu {
  /* Tezisy-5 п.5: dropdown shifted 5px right (right:0 -> right:-5px, i.e.
     its right edge now sits 5px past the summary icon's own right edge). */
  position: absolute; right: -5px; bottom: calc(100% + 8px);
  display: flex; align-items: center; gap: 10px;
  background: #fff; border: 1px solid #ddd; border-radius: 6px;
  box-shadow: 0 6px 20px rgba(0,0,0,0.14);
  padding: 8px 10px;
  z-index: 1000;
}
.vp-quote {
  border-left: 3px solid #7a2e2e;
  margin: 24px 0; padding: 4px 20px;
  font-style: italic; color: #333;
}
.vp-quote cite { display: block; margin-top: 8px; font-style: normal; font-size: 14px; color: #777; }
/* Tezisy-9 п.4: this list only ever renders inside the "Новое на сайте"
   dark card now (see .vp-sidebar-block--dark below) - safe to restyle
   its base rules directly rather than adding a parallel dark-specific
   set, confirmed it has exactly one call site (inc/blocks.php's
   article_list case) and .vp-date has no other user anywhere in the
   codebase either. */
.vp-article-list { list-style: none; padding: 0; margin: 24px 0; }
/* Tezisy-9 п.4: 2-line format now (title, then author+date on their own
   row) instead of the old single-row title+date - column direction
   instead of row, and item spacing cut by a third (10px -> 6.67px) to
   make room for the added author line without growing the list's total
   height. Divider switched to a translucent white line (was #eee) -
   legible against the new dark background, invisible against the old
   light one wouldn't have made sense to keep either way. */
.vp-article-list li {
  display: flex; flex-direction: column;
  /* Tezisy 1.9-2 п.4: -25% (was 6.8px). Tezisy 1.9-3 п.2: another -3px
     (5.1 -> 2.1), moved onto the bottom padding below (title-to-author
     gap shrinks, author-to-rule gap grows by the same 3px). */
  gap: 2.1px;
  /* Tezisy 1.9-3 п.2: bottom padding +3px (6.67 -> 9.67) - the 3px taken
     from the gap above lands here, between the Автор/Дата line and the
     divider. Top padding (space before the next title) stays 6.67px,
     untouched. */
  padding: 6.67px 0 9.67px; border-bottom: 1px solid rgba(255, 255, 255, 0.15);
  font-family: system-ui, sans-serif; font-size: 14.5px;
}
/* Tezisy 1.9 п.7: no divider under the last position. */
.vp-article-list li:last-child { border-bottom: none; }
/* Tezisy 1.9-2 п.3: single-line ellipsis - an unusually long title can no
   longer wrap to a 2nd line and throw off the now-fixed-height card. */
.vp-article-list li a { color: #fff; text-decoration: none; display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vp-article-list li a:hover { color: #2eaadc; }
.vp-article-list-meta { display: flex; justify-content: space-between; gap: 12px; font-size: 12.5px; }
.vp-article-list-author { color: #ccc; }
.vp-date { color: #ccc; white-space: nowrap; }
/* Tezisy-7 п.6: white plate now (was #1a1a1a/#fff dark) - matches the
   white-card convention used by every other card on the site, including
   its own sidebar neighbor (.vp-home-quote, right below it). */
/* Владелец 26.09.2026: заголовок+текст кликабельны (ведут на Экскурсии),
   как на стандартных карточках-анонсах - визуально ничего не меняется
   (те же цвет/подчёркивание, что у обычного текста), просто оборачивает
   ссылкой. */
.vp-excursions-box-link { display: block; text-decoration: none; color: inherit; }
.vp-excursions-box {
  background: #fff; color: #1a1a1a; border: 1px solid #eee;
  /* Tezisy 1.9-3 п.2: bottom padding is now literally 14px - the same
     distance a standard card keeps between its author/date row and its
     own bottom edge - so the footer below (pinned via margin-top:auto)
     sits "fixed like author+date on standard cards", per the owner's own
     comparison. */
  padding: 17px 20px 14px; border-radius: 0; margin: 24px 0;
  font-family: system-ui, sans-serif; font-size: 15px;
  /* Tezisy 1.9-3 п.2 had this at 380px (Cover Story's photo height minus
     the standard gap, which also happened to equal a standard card's own
     height back then).
     Вечерние тезисы 2026-09-03 (addendum): Экскурсии and Новое на сайте
     swap slots in the sidebar stack (see the 'home' page's own block
     order - excursions_box/article_list positions swapped, quote stays
     between them) - Новое (still 380px, .vp-sidebar-block--dark) now
     pairs with Excursions+Quote's old 628px-tall combined slot (380+16+
     232=628, matches Cover Story regardless of WHICH 380px block sits on
     top), while Excursions moves down to pair with the teasers row
     instead - which is now 360px, not 380 (see the standard-card height
     cut a few tezisy back), so Excursions must match that, not its own
     old value. 121px of the box's own bottom dead space (footer pinned
     via margin-top:auto) absorbs the -20px with nothing else moving. */
  /* Владелец (13.09.2026): пара со стандартной карточкой в этом ряду
     (.vp-home-teasers) снова разъехалась - те карточки лишились текста
     анонса и перешли на min-height:333px (см. .vp-card--no-excerpt),
     этот блок остался на старом фиксированном 360px. Тот же принцип,
     что и во всех предыдущих правках этого блока (см. историю выше
     чуть ниже) - подстроить значение под актуальную высоту соседней
     карточки, измеренную вживую (getBoundingClientRect), а не старое
     запомненное число. */
  /* Владелец (16.09.2026): "Информер Экскурсии... должен иметь точно
     такую же высоту, как у карточек-анонсов 2/3 (360px) - увеличить за
     счёт расстояния от текста до кнопки/Поделиться" - было 333px
     (разъехалось от актуальных 360px карточек-тизеров). margin-top:auto
     на .vp-excursions-box-footer (см. ниже) уже самостоятельно
     поглощает любую разницу высоты сюда - менять больше ничего не
     нужно, просто высота самой плашки. */
  height: 360px; overflow: hidden; box-sizing: border-box;
}
.vp-excursions-box p {
  color: #666;
  /* Tezisy 1.9-2 п.2: line-clamp removed - with the heading now 8px
     higher and the gap above this paragraph a third tighter, the full
     announcement text fits inside the card's own fixed height without
     truncation; .vp-excursions-box's own overflow:hidden remains as a
     backstop for an unusually long value, not the primary clip anymore.
     Explicit margin-top replaces the browser's default <p> margin
     (~1em = 15px at this 15px font-size), cut by a third per the ask:
     15px * 2/3 = 10px.
     Tezisy 1.9-2(2) п.8: another third off that same gap:
     10px * 2/3 = 6.67px. */
  margin: 6.67px 0 0;
  /* Tezisy 1.9-2(2) п.8: line-height cut 2 "points" - this codebase's
     established point->px rounding (1pt ~= 1.5px, see the featured-card
     author/date-text kegl comments elsewhere) makes that 3px. Inherited
     line-height was the plain body default (1.6 * 15px font-size =
     24px) - now explicit so the math is visible: 24 - 3 = 21px. */
  line-height: 21px;
}
/* Button (bottom-left) + share widget (bottom-right) - same footer-row
   pattern as .vp-quote-footer/.vp-card-meta elsewhere. */
.vp-excursions-box-footer {
  /* 14.09.2026 (владелец: "все равно блок Поделиться оказывается сверху
     над обновленной кнопкой, хотя места им там вроде должно хватать") -
     было ПРАВДА так: мобильный брейкпоинт ниже раньше жёстко ставил
     flex-direction:column на ЛЮБОЙ ширине <=600px, независимо от того,
     влезают ли кнопка+"Поделиться" рядом - на живых 320-430px реальных
     телефонах кнопка "Маршруты" (короче старой "Все экскурсии") + иконки
     физически влезают (замерено: ~232px нужно, ~285px есть на 390px
     экране) - flex-wrap:wrap вместо жёсткого column: помещаются - стоят
     в ряд как на десктопе; НЕ помещаются (реально только на самых узких,
     ~320px) - сами перетекают на вторую строку, без ручного брейкпоинта. */
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px 12px;
  /* Tezisy 1.9-3 п.2: pinned to the bottom of the (now flex-column, see
     .vp-home-top-sidebar .vp-excursions-box below) card - consumes all
     remaining vertical space above it, so it never drifts with how much
     text happens to be above, matching the fixed author+date row on a
     standard card. */
  margin-top: auto;
}
/* Tezisy-7 п.3: the Экскурсии section page's OWN intro text (excursions_
   echo, see inc/blocks.php) - centered, no plate, same color/tone as the
   page's own <h1 class="vp-section-title"> (#5f5f5f) right above it. */
.vp-excursions-intro {
  text-align: center;
  color: #5f5f5f;
  font-family: system-ui, sans-serif;
  font-size: 15px;
  margin: 0 0 20px;
}
.vp-excursions-intro h3 {
  margin: 0 0 8px;
  font-family: 'Bitter', Georgia, serif;
  color: inherit;
}
/* Tezisy-6 (follow-up): margin:0 added - .vp-excursions-box's own base
   rule sets margin:24px 0 for its OTHER (non-sidebar) uses, which would
   otherwise add on top of .vp-home-top-sidebar's own flex `gap`.
   Tezisy 1.9-2 п.3: min-height dropped (the base rule's own fixed
   height:355px supersedes it) and justify-content changed center ->
   flex-start - with a hard height + overflow:hidden now in play, content
   should overflow/clip off the BOTTOM (natural reading order, matches
   "текст обрывается") rather than get clipped symmetrically off both
   ends the way center-alignment would. */
.vp-home-top-sidebar .vp-excursions-box { display: flex; flex-direction: column; justify-content: flex-start; margin: 0; }
/* Same reasoning for the "Новое на сайте" list's own margin:24px 0.
   Вечерние тюн-тезисы 2026-09-03 п.9: +5px top margin - the list moves
   down, the heading above it (own separate rule) stays exactly where it
   was. */
.vp-home-top-sidebar .vp-article-list { margin: 5px 0 0; }
/* Tezisy-9 п.4: "Новое на сайте" is now a real card, not a bare wrapper -
   "тёмно-серая (70%)" plate: 70% black ink / 30% white showing through =
   grey value 255*0.3=76.5 -> 77 = #4d4d4d, the same "N% black" math this
   codebase already uses elsewhere (tezisy-1's own white-blend formula).
   Text inverted (white) to read against it - the heading, which used to
   be dark-on-white like every sibling, needs its own explicit override
   here since inheritance alone won't fix an element with its own set
   color. Text-column width matches its white siblings above
   (Экскурсии/Цитата) via the same plain 22px padding. */
.vp-sidebar-block--dark {
  /* Tezisy 2026-09-03 п.5 had shifted this 2px down; Вечерние тезисы
     2026-09-03 п.2 raises it back up 2px - net back to its original,
     unshifted position. */
  position: relative;
  top: 0;
  background: #4d4d4d;
  color: #fff;
  /* Tezisy 1.9 п.7: left/right kept at the existing 22px (reference),
     top/bottom trimmed - part of matching this card's height against the
     2nd+3rd teaser row beside it. */
  padding: 16px 22px;
  /* Tezisy 1.9-2 п.3/5: hard fixed height - literally a standard card's
     own 380px (.vp-card-grid/.vp-home-teasers convention), not just a
     value that happens to equal it right now. If 5 real items don't fill
     it, the remainder is blank space below the list (matches the owner's
     own "если текста недостаточно, образуется свободное пространство");
     if a 6th item or longer names ever push past it, overflow:hidden
     clips it instead of growing the card. */
  height: 380px; overflow: hidden; box-sizing: border-box;
}
.vp-sidebar-block--dark .vp-sidebar-heading { color: #fff; }
/* Tezisy 1.9 п.6: season heading ("Осень 2026") 1.5x bigger - was
   inheriting the browser default h3 size (1.17em of the box's own 15px =
   17.55px), so the literal px target is 17.55 * 1.5 = 26.325px (an `em`
   value here would resolve against the PARENT's 15px, not the current
   17.55px, and land at the wrong size). Gap to the first line of text
   halved (8px -> 4px) - both part of the same Cover-Story
   height-matching pass. */
/* Tezisy 1.9-3 п.2: another -1/3 (was 4px -> 2.67px). */
.vp-excursions-box h3 {
  /* Tezisy 1.9-2 п.2: raised 8px (negative top margin - doesn't affect
     the gap to the paragraph below, which is its own separate ask). */
  margin: -8px 0 2.67px; font-size: 26.325px; font-family: 'Bitter', Georgia, serif;
}
.vp-text { margin: 20px 0; }
/* Footer (brief: black/white like the original, but roughly twice its
   ~160px height, partners column kept close to the site-nav column
   instead of pushed to the far right, NEW LOGO on the left, a partner
   site's logo slot on the right where the original had its "Разработка
   сайта" dev-studio credit). Height comes from padding/content, not a
   fixed px value, so it stays responsive instead of clipping content
   at narrow viewports or in longer languages. */
.vp-site-footer {
  background: #161616; color: #eee;
  /* Tezisy-2 п.4: the previous 0.1x reduction (48px -> 4.8px) was the
     wrong direction/instruction entirely - owner's fresh, explicit ask
     is a flat 5mm of space above the content block, replacing that
     value outright rather than adjusting it further.
     Tezisy-4: horizontal padding moved OFF this element and onto
     .vp-footer-inner/.vp-footer-credit instead - real bug found and
     confirmed live via getBoundingClientRect: with padding here instead,
     .vp-footer-inner's own max-width:1100 had no padding of its OWN, so
     its visible content spanned the full 1100px, while .vp-main and
     .vp-site-header-inner - which carry both max-width AND their 24px
     padding on the SAME element - only got 1100-48=1052px of content
     width. The footer's content really was measurably wider than the
     header/main at any viewport >=1100px, not just an impression. */
  /* Tezisy-5 п.7: +5px more on top of the flat 5mm, all other footer
     spacing (footer-inner's own padding, footer-credit's border/padding,
     the 28px bottom) explicitly left untouched - calc() keeps the
     original 5mm value traceable instead of converting it to a single
     unit and losing that history. */
  /* Ночные тезисы 10.9.26 (десктоп) п.3: the dark footer plate grows 30px
     taller, and specifically at the BOTTOM - "за счет увеличения
     расстояния от нижнего края логотипа до границы экрана" - so only the
     bottom padding changes: 28 + 30 = 58px. Top padding and everything
     inside the footer stay exactly where they were.
     Владелец (15.09.2026, "в библию"): футер ещё на 20px выше, опять
     снизу - "между последней строкой и краем экрана" - 58 + 20 = 78px.
     Владелец (09.10.2026, макет footer_1.psd): "увеличил его высоту" -
     мокап сделан наспех поверх скриншота старого футера (без отдельного
     слоя под новую высоту тёмной плашки), так что точного целевого px
     из него не вывести - оценка на глаз, +15 сверху/+20 снизу, лучшая
     оценка до прямой правки владельцем по живому результату, как и везде
     в этой истории футера. */
  padding: calc(5mm + 20px) 0 98px;
  font-family: system-ui, sans-serif;
}
.vp-footer-inner {
  max-width: 1100px; margin: 0 auto;
  padding: 0 24px;
  display: flex; flex-wrap: wrap; align-items: flex-start;
  gap: 48px 56px;
}
.vp-footer-logo a { display: block; }
.vp-footer-logo {
  /* Tezisy-2 п.4: push the content block another 5mm further from the
     logo, on top of .vp-footer-inner's own existing 56px column gap -
     needs its own margin since that gap is shared uniformly with every
     other pair of columns in the same flex row. */
  margin-right: 5mm;
  display: flex;
  align-items: center;
}
/* Владелец 09.10.2026: новый "Kaliningrad & Koenigsberg" лейбл заменяет
   круглый значок Amber City - больше не нужен старый
   anchor-box-plus-absolute-overflow-plus-rotate трюк (тот был под
   КРУГЛЫЙ значок конкретно), тот же чистый height-based подход, что уже
   применён к этому лейблу в хедере (.vp-logo-img--new). Высота 84px -
   не оценка на глаз, а размер из слоя мокапа footer_1.psd (340x84),
   измеренный владельцем напрямую в макете. */
.vp-footer-logo-img {
  display: block;
  height: 84px;
  width: auto;
}
/* Тезисы 6.9.26 п.2: both link columns shift 45px right together, logo
   and partner logo stay put - only the FIRST column needs the margin,
   since normal flex flow then carries .vp-footer-partners along right
   behind it at the same existing gap, and .vp-footer-partner-logo's own
   margin-left:auto keeps anchoring it flush to the row's right edge
   regardless (verified: adding width earlier in the row only shrinks
   that auto margin, doesn't shift the anchored item). */
.vp-footer-col:not(.vp-footer-partners) { margin-left: 45px; }
.vp-footer-col nav {
  display: flex; flex-direction: column;
  /* Tezisy-2 п.4: -5% (was 7.5px): 7.5*0.95 = 7.125px.
     Вечерние тезисы 2026-09-03 п.8: -2 "typographic points" of
     interlinye in both link blocks (internal + partners share this one
     selector) - same 1pt=1.5px codebase convention as the card-heading
     kegl change above: 7.125 - 3 = 4.125px. */
  gap: 4.125px;
}
.vp-footer-col a { color: #ccc; text-decoration: none; font-size: 13px; text-transform: uppercase; letter-spacing: 0.03em; }
.vp-footer-col a:hover { color: #2eaadc; }
.vp-footer-partner-logo { margin-left: auto; align-self: center; }
/* Real partner logo (Музей города Кёнигсберг) replacing the dashed
   "PARTNER LOGO" placeholder - owner's own mockup shows it with a
   slight counter-clockwise tilt, a "pinned photo" look matching the
   Amber City badge's own sticker aesthetic elsewhere on the page. Kept
   its own real cream background (unlike the Amber City logo, this one
   was never meant to be transparent - it's a designed card, not a
   badge cut out of a background). */
.vp-footer-partner-logo-img {
  display: block; height: 76px; width: auto;
  border-radius: 4px;
  transform: rotate(-4deg);
}
.vp-footer-autograph { font-family: system-ui, sans-serif; font-size: 11px; line-height: 20px; color: #ccc; }
/* FOOTER-GRID-BEGIN
   Владелец 10.10.2026, макет футера v3 (направляющие на скриншоте макета):
   логотип - левый верхний угол постоянен, ширина до направляющей по значку
   MAX в строке "Поделиться" карточки над ним; колонка О проекте - по левому
   краю 2-й карточки, партнёрская - по направляющей примерно в середине 2-й
   карточки; первые строки обеих колонок - на уровне 2-й строки логотипа
   (KOENIGSBERG); автограф студии - под логотипом, его 1-я строка на одной
   линии с последней строкой партнёров; бейдж музея - верх по верху
   логотипа, правый край по правому краю 3-й карточки. Все горизонтальные
   размеры привязаны к ширине карточки (--vp-card-w). Только десктоп. */
@media (min-width: 861px) {
  /* Владелец 15.10.2026: +20px снизу (71 -> 91) - партнёрская колонка
     выросла на строку (Радио Калининград), не должна подходить к краю. */
  .vp-site-footer { padding: 21px 0 91px; }
  .vp-footer-inner {
    display: grid;
    --vp-card-w: calc((min(var(--vp-vw, 100vw), 1100px) - 48px - 32px) / 3);
    grid-template-columns: calc(var(--vp-card-w) + 16px) calc(var(--vp-card-w) * 0.5085) minmax(0, 1fr) auto;
    grid-template-rows: auto;
    gap: 0;
    align-items: start;
  }
  .vp-footer-logo { grid-column: 1; grid-row: 1; margin-right: 0; }
  .vp-footer-logo-img { width: calc(var(--vp-card-w) * 1 - 92.5px); height: auto; }
  /* Владелец 15.10.2026: 1-я строка автографа - на уровне ПОСЛЕДНЕЙ строки
     партнёров при любом их числе (редактор теперь сам добавляет/убирает
     партнёров) - прижат к низу ряда (= низу партнёрской колонки), сдвиг
     -19.88px подобран замером (базовые линии совпадают): 40px высоты автографа - 24.9px шага строк
     партнёров + разница их базовых линий. Было: margin-top 123.7px. */
  .vp-footer-autograph { grid-column: 1; grid-row: 1; align-self: end; margin: 0 0 -19.88px; }
  .vp-footer-col:not(.vp-footer-partners) { grid-column: 2; grid-row: 1; margin: 25.5px 0 0; }
  .vp-footer-partners { grid-column: 3; grid-row: 1; margin-top: 25.5px; }
  .vp-footer-partner-logo-img { height: 68.64px; }
  .vp-footer-partner-logo { grid-column: 4; grid-row: 1; align-self: start; justify-self: end; margin: 5.6px 2.5px 0 0; }
}
/* FOOTER-GRID-END */
/* Владелец 17.10.2026: счётчик дней справа в футере - Franklin Gothic Demi
   Cond, ширина 90%, цвета как у логотипа в футере (верх #b7b7b7 =
   «KALININGRAD», низ #7d7d7d = «KOENIGSBERG»). Шрифт коммерческий - на
   сайт не встраивается, берётся из системы посетителя (есть на Windows с
   Office), иначе ближайший узкий гротеск. Бейдж музея на этом месте скрыт. */
.vp-footer-partner-logo { display: none !important; }
/* Владелец 17.10.2026 (следом): шрифт счётчика - Oswald Bold (Google Fonts,
   OFL), размещён у нас (assets/vendor/fonts/oswald/, только латинский набор -
   нужны лишь цифры), не с серверов Google - иначе посетитель обращался бы к
   Google. Размер +10 (38 -> 48px), строки смыкаются, как в логотипе. */
@font-face { font-family: 'Oswald'; font-style: normal; font-weight: 700; font-display: swap; src: url('/assets/vendor/fonts/oswald/oswald-700-latin.woff2') format('woff2'); }
.vp-footer-days {
  display: flex; flex-direction: column; align-items: flex-start;
  font-family: 'Oswald', 'Arial Narrow', sans-serif;
  font-weight: 700; font-size: 48px; line-height: .76; /* владелец 18.10.2026: строки ВПЛОТНУЮ на любом масштабе экрана - при .795 на 125/150/200% оставался пустой ряд пикселей; .76 проверено на 100/125/150/200% */
  /* Владелец 17.10.2026: ближе к шрифту логотипа - знаки шире на 10%
     (.9 -> .99) и расстояние между ними +10% от ширины цифры (~.5em -> .05em). */
  letter-spacing: .035em; /* владелец 18.10.2026: на 3% меньше (3% ширины цифры ≈ .015em) */
  transform: scaleX(.99); transform-origin: right top;
  white-space: nowrap; user-select: none;
}
.vp-footer-days > span { margin-right: -.035em; } /* интервал после последней цифры не сдвигает правый край */
/* Владелец 18.10.2026: под счётчиком строчными «дней и ночей» - шрифт и
   кегль копирайта (.vp-footer-autograph: system-ui 11px), отступ от низа
   цифр = просвету между строками копирайта (замер: 12px). */
.vp-footer-days-label {
  font: 400 11px/1 system-ui, sans-serif; color: #ccc; letter-spacing: 0;
  margin-top: 14px; /* владелец 18.10.2026: в 1,5 раза больше - видимые 18px (было 12) от низа цифр до верха строчных букв */ text-transform: lowercase;
}
/* Владелец 17.10.2026: разделитель тысяч вдвое уже прежнего (пробел Oswald
   .256em + интервал .05em = .306em -> .153em). */
.vp-footer-days .vp-dsep { display: inline-block; width: .153em; }
.vp-footer-days-now { color: #b7b7b7; }
.vp-footer-days-kb { color: #7d7d7d; }
@media (min-width: 861px) {
  .vp-footer-days { grid-column: 4; grid-row: 1; align-self: start; justify-self: end; margin: 20px 2.5px 0 0; }
}
@media (max-width: 860px) {
  .vp-footer-days { align-self: flex-end; font-size: 40px; }
}
/* Владелец 18.10.2026 (макет): в мобильной версии всё в футере выключено
   влево, счётчик замыкает колонну, копирайт - ровно посередине между низом
   партнёрских ссылок и верхом счётчика. Просветы считаются по видимым буквам
   (замер measureText): низ ссылок = низ строки - 4.9px, верх копирайта =
   верх блока + 6.5px, низ копирайта = низ блока - 5.5px, верх цифр = верх
   блока. Общее расстояние партнёры -> счётчик = прежнему партнёры ->
   копирайт (159px), по 66px с каждой стороны копирайта:
   28 (gap) + 26.6 + 11.4 = 66; 28 + 32.5 + 5.5 = 66. */
@media (max-width: 780px) {
  .vp-footer-autograph { order: 10; margin-top: 26.6px; }
  .vp-footer-days { order: 11; align-self: flex-start; transform-origin: left top; margin-top: 32.5px; }
}
/* Вечерние тезисы 6.9.26 п.3: .vp-footer-credit and its border-top rule
   removed outright - the credit sentence moved to the end of the
   Контакты page, and the owner uses a browser bookmark for the admin
   panel rather than the link that lived here. */
@media (max-width: 780px) {
  .vp-footer-inner { flex-direction: column; gap: 28px; }
  .vp-footer-partner-logo { margin-left: 0; align-self: flex-start; }
  /* Владелец (15.09.2026, "в библию"): те же +20px футера в мобильной
     версии идут МЕЖДУ последней строкой ("Мануфактуры" - последняя
     ссылка в .vp-footer-partners, снизу этот блок в стопке мобильного
     .vp-footer-inner) и логотипом Музея города Кёнигсберг - не во ВСЕ
     промежутки стопки равномерно (те остаются на базовых 28px), а
     именно в этот один. margin-top поверх уже существующего flex-gap:
     28 + 20 = 48px итоговое расстояние перед логотипом музея. */
  .vp-footer-partner-logo { margin-top: 20px; }
  .vp-footer-logo { max-width: 100%; }
  .vp-footer-logo-img { max-width: 100%; height: auto; }
}

/* "Наверх" button (brief п.18) - original site: a plain gray circle
   with a white chevron, fixed bottom-right, shown once the page has
   scrolled past ~1 viewport height, desktop only. Rebuilt as an inline
   SVG instead of reusing their PNG. */
/* Вечерние тезисы 4.9.26 п.10: arrow icon 3x (20 -> 60px - see the
   inline SVG in inc/layout.php, width/height bumped there); the circle
   itself grows too (50 -> 76px) so a 60px icon still sits inside it
   with reasonable padding instead of overflowing its own button. */
.vp-back-to-top {
  position: fixed; bottom: 40px; right: 40px; z-index: 900;
  width: 65px; height: 65px; border-radius: 50%; border: none;
  background: #8a8a8a; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 2px 8px rgba(0,0,0,0.2);
}
.vp-back-to-top:hover { background: #7a7a7a; }
.vp-back-to-top[hidden] { display: none; }
/* Владелец 17.10.2026: плавное появление «Наверх» (выезжает снизу) и
   плавающая «Аа» на её месте; когда «Наверх» видна, «Аа» поднимается над
   ней на высоту кнопки + 14px. */
/* Владелец 17.10.2026: «тандем» «Аа» + «Наверх» уменьшен на 10% (65 -> 58px,
   на мобильном 52 -> 47px). Размер, отступ и иконка - через переменные,
   следующий шаг уменьшения = правка только --vp-fab-size/--vp-fab-icon. */
:root { --vp-fab-size: 58px; --vp-fab-icon: 46px; --vp-fab-font: 22px; --vp-fab-inset: 40px; --vp-fab-gap: 13px; }
.vp-back-to-top {
  width: var(--vp-fab-size); height: var(--vp-fab-size);
  bottom: var(--vp-fab-inset); right: var(--vp-fab-inset);
}
.vp-back-to-top svg { width: var(--vp-fab-icon); height: var(--vp-fab-icon); }
.vp-back-to-top {
  opacity: 0; transform: translateY(24px) scale(.85); pointer-events: none;
  transition: opacity .3s ease, transform .3s ease, background .15s;
}
.vp-back-to-top.is-visible { opacity: 1; transform: none; pointer-events: auto; }
.vp-aa-float {
  position: fixed; bottom: var(--vp-fab-inset); right: var(--vp-fab-inset); z-index: 901;
  width: var(--vp-fab-size); height: var(--vp-fab-size); min-width: 0; padding: 0; margin: 0;
  /* Владелец 17.10.2026: цвета инвертированы относительно «Наверх» -
     белый круг, серые буквы и тонкая серая обводка. */
  border-radius: 50%; border: 1px solid #8a8a8a;
  background: #fff; color: #8a8a8a;
  font-size: var(--vp-fab-font); line-height: 1;
  justify-content: center;
  box-shadow: 0 2px 8px rgba(0,0,0,0.2);
  transition: bottom .3s ease, background .15s, color .15s;
}
.vp-aa-float:hover { background: #f2f2f2; color: #6a6a6a; border: 1px solid #6a6a6a; }
body.vp-totop-on .vp-aa-float { bottom: calc(var(--vp-fab-inset) + var(--vp-fab-size) + var(--vp-fab-gap)); }
@media (prefers-reduced-motion: reduce) {
  .vp-back-to-top, .vp-aa-float { transition: none; }
}
/* Владелец 07.10.2026: простая информационная плашка про куки. */
.vp-cookie-notice {
  position: fixed; left: 16px; right: 16px; bottom: 16px; z-index: 950;
  max-width: 560px; margin: 0 auto;
  background: #2b2b2b; color: #f1ece7; border-radius: 8px;
  padding: 14px 18px; box-shadow: 0 4px 20px rgba(0,0,0,0.25);
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  font-family: system-ui, sans-serif; font-size: 14px; line-height: 1.5;
}
.vp-cookie-notice[hidden] { display: none; }
.vp-cookie-notice p { margin: 0; flex: 1; min-width: 200px; }
.vp-cookie-notice button {
  background: #fff; color: #2b2b2b; border: none; border-radius: 4px;
  padding: 7px 16px; font: inherit; font-weight: 600; cursor: pointer; flex-shrink: 0;
}
.vp-cookie-notice button:hover { background: #e0e0e0; }
/* Мобильные тезисы 7.9.26 п.15: the "display:none below 780px" rule that
   used to live here is gone - Наверх is wanted on phones too.
   Ночные тезисы 10.9.26 (мобильные) п.1: its mirror image Назад has been
   removed outright (markup, JS and styles) - the site now answers the
   device's own Back correctly, so an extra in-page button was redundant. */

/* ============================================================
   МОБИЛЬНАЯ ВЕРСИЯ - тезисы 7.9.26 (16 пунктов)
   One consolidated block, last in the file so it wins on source order
   over the older scattered breakpoints it deliberately overrides.
   ============================================================ */
@media (max-width: 600px) {
  /* --- п.15: Наверх stays, Назад appears (both 10px in from the edges,
     smaller than the desktop 40px inset so they don't crowd the content
     on a narrow screen). --- */
  :root { --vp-fab-size: 47px; --vp-fab-icon: 36px; --vp-fab-font: 18px; --vp-fab-inset: 16px; --vp-fab-gap: 11px; }
  /* язык слева в мобильном ряду - меню растёт вправо. Владелец 17.10.2026:
     между кнопками языка и поиска, по центру - только дата «17 октября 2026»
     (без дня недели и погоды). */
  .vp-lang-menu { left: 0; right: auto; }
  .vp-dw { margin: 0; font-size: 13px; }
  .vp-dw-wd, .vp-dw-sep, .vp-dw-wx-wrap { display: none !important; }
}
/* Владелец 17.10.2026: телефон горизонтально в мобильной вёрстке (узкий
   экран, но лежит на боку) - места между кнопками хватает, виджет целиком:
   «17 октября 2026, Сб / ⛅ +15°». */
@media (max-width: 600px) and (orientation: landscape) {
  .vp-dw-wd { display: inline !important; }
  .vp-dw-sep { display: inline !important; }
  .vp-dw-wx-wrap { display: inline-flex !important; }
  .vp-dw-sep:has(+ .vp-dw-wx-wrap:empty) { display: none !important; }
}
@media (max-width: 600px) {

  /* --- п.1: the three option buttons (язык / Аа / поиск) as one
     horizontal row. The desktop copy of the search icon (inside
     .vp-header-main) hides; the mobile one in this row shows. The huge
     lockstep left offset the desktop language button carries
     (calc(56px + 47.25px + 0.8cm + 15px + 45px + 20px), aligning it with
     the nav's first letter) makes no sense here - reset to 0. --- */
  /* Мобильные тезисы 8.9.26 п.1: the three option buttons are justified
     across the full width - language flush LEFT, Аа CENTRED, search
     flush RIGHT. space-between does exactly that for three equal-ish
     items; the auto margins the desktop layout puts on .vp-aa-toggle
     must go, or they would fight it. */
  /* Владелец 07.10.2026: "расстояние над логотипом от него до кнопок
     должно быть равно расстоянию под логотипом от него до верхнего
     края верхней строки главного меню" - до этой правки было 0px
     сверху (header-utility-overlap-fix этой же сессии убрал общий
     flex-gap:10px у .vp-site-header-inner, который раньше давал этот
     зазор и на мобильном тоже, хотя чинил только десктопный
     overlap) против 10px снизу (margin-top на .vp-nav ниже, не
     трогался). margin-bottom здесь восстанавливает симметрию явным
     числом вместо случайно унаследованного flex-gap.
  */
  .vp-header-utility { position: static; top: auto; left: auto; right: auto; gap: 10px; justify-content: space-between; margin-bottom: 17px; }
  .vp-lang-dropdown { margin-left: 0; margin-right: 0; }
  .vp-aa-toggle { margin-left: 0; margin-right: 0; }
  .vp-aa-float { margin: 0; }
  .vp-header-main > .vp-search-icon { display: none; }
  .vp-search-icon--mobile {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 64px; height: 30px; box-sizing: border-box;
    margin-left: 0; top: 0; padding: 0;
    background: #fff; border: 1px solid #ccc; border-radius: 4px;
    color: #444; opacity: 1;
  }
  .vp-search-icon--mobile:hover { border-color: #7a2e2e; color: #7a2e2e; }
  .vp-search-icon--mobile .vp-search-icon-svg { width: 20px; height: 20px; }
  .vp-search-icon--mobile img { width: 20px; height: 20px; }

  /* --- п.2: logo becomes a full-width horizontal block, as tall as the
     round sticker itself (the badge image is 120px; the desktop anchor
     box is only 56px and lets it spill). Centred here rather than
     off-centre, since it now owns a whole row. --- */
  .vp-header-main { flex-direction: column; align-items: stretch; row-gap: 0; }
  .vp-logo { width: 100%; height: 120px; }
  .vp-logo-img { left: 50%; transform: translate(-50%, -50%) rotate(25deg); }

  /* --- п.3: main menu in three columns under the logo. Drops the
     desktop nav's visual nudges (left/top offsets, left margin) which
     only existed to align it against the logo in a single row. --- */
  /* Мобильные тезисы 8.9.26 п.2: the gap under the logo badge is matched
     to the gap above it. Measured live: the badge's visible circle runs
     59..179px, the Аа button ends at 49 (a 10px gap above), and the nav's
     first row started at exactly 179 (no gap at all below). 10px here
     makes the badge sit symmetrically between the two rows. */
  .vp-nav {
    display: grid; grid-template-columns: repeat(3, 1fr);
    gap: 10px 8px; margin-left: 0; left: 0; top: 0; flex: none;
    /* Владелец 08.10.2026: "отчёт будто у нас одинаковые 10 и 10 не
       показался мне убедительным - перепроверил в графическом
       редакторе" - DOM-коробки совпадали математически, но визуально
       (по реальным чернилам в PNG, не по прозрачным полям вокруг них)
       равенство не читалось. Просил добавить ещё: сверху 7px, снизу
       2px - 10+7=17 (см. .vp-header-utility выше), 10+2=12 здесь. */
    margin-top: 12px;
    /* 07.10.2026: the new desktop align-self:flex-end (bottom-aligns nav
       with the logo/tagline's own bottom edge in the single-row desktop
       layout) means something different once .vp-header-main switches to
       flex-direction:column here - cross-axis becomes horizontal, so an
       unset align-self would right-align/shrink the grid instead of
       letting it stretch full width. Explicit stretch restores the
       original mobile behavior. */
    align-self: stretch;
  }
  .vp-nav a { font-size: 13px; }

  /* --- Root cause of п.4-п.8: the Home top grid's items were not
     allowed to shrink (only the sidebar had min-width:0), so their
     min-content width - two 206px teaser cards side by side plus the
     16px gap = 440px - forced the single mobile column 74px wider than
     the 390px viewport. Everything "sticking out past the edge" and
     "breaking the layout" traces back to this one overflow. --- */
  .vp-home-top { grid-template-columns: minmax(0, 1fr); }
  .vp-home-cover-col, .vp-home-top-sidebar { grid-column: 1; min-width: 0; }

  /* --- п.4: Cover Story is a standard announcement card here - exactly
     the numbers a normal card uses (360px total, 150px media, 20px/700
     heading, 14.5px excerpt), not the 628px/396px/38px front-page
     monolith that "разваливает" the mobile layout. --- */
  /* Владелец (15.09.2026): "на всех карточках-анонсах в мобильной версии
     обеспечить наличие не обрывающихся заголовков с текстовыми анонсами...
     высота пусть подстраивается" - Cover Story была фиксированной 360px
     (та же ловушка, что раньше была у информера Экскурсии/большой
     карточки) - теперь auto, а h3/p ниже сняли line-clamp. */
  .vp-card--cover.vp-card--article { height: auto; }
  .vp-card--cover .vp-card-media { height: 150px; }
  .vp-card--cover.vp-card--article .vp-card-body { height: auto; padding: 12px 14px 0; }
  .vp-card--cover.vp-card--article h3 {
    font-size: 20px; font-weight: 700;
    display: block; -webkit-line-clamp: unset; overflow: visible;
  }
  .vp-card--cover.vp-card--article p {
    font-size: 14.5px;
    display: block; -webkit-line-clamp: unset; overflow: visible;
  }
  .vp-card--cover .vp-card-meta { height: auto; padding: 0 14px 14px; }

  /* --- п.5: cards 2 and 3 stack one under the other, in order, each at
     full standard width. --- */
  .vp-home-teasers { grid-template-columns: minmax(0, 1fr); }

  /* Владелец (15.09.2026): то же самое (не обрывать заголовок/анонс,
     расти по высоте) - для ОБЫЧНЫХ карточек-анонсов, не только Cover
     Story/большой карточки/информера Экскурсии выше. .vp-card--no-excerpt
     уже не клэмпает (своё правило, база файла) - не трогаем. */
  .vp-card--article h3,
  .vp-card--article p {
    display: block; -webkit-line-clamp: unset; overflow: visible;
  }
  /* Тот же селектор-специфичность, что у desktop-правила display:none
     выше (.vp-feed-regular-grid .vp-card--article p) - иначе общее
     .vp-card--article p { display:block } (ниже по специфичности) не
     победило бы его даже тут, внутри мобильного media-query. */
  .vp-feed-regular-grid .vp-card--article p,
  .vp-home-teasers .vp-card--article p {
    display: block; -webkit-line-clamp: unset; overflow: visible;
  }

  /* --- п.6/7/8: "Новое на сайте", Цитата and Экскурсии all take exactly
     the standard card width. With the overflow above fixed they inherit
     it from the single-column grid; these rules just make sure nothing
     re-introduces its own width/negative inset. --- */
  .vp-sidebar-block--dark, .vp-home-quote, .vp-excursions-box,
  .vp-home-top-sidebar .vp-excursions-box, .vp-home-top-sidebar .vp-article-list {
    width: auto; max-width: 100%; margin-left: 0; margin-right: 0;
  }
  /* Владелец 18.10.2026: фраза Цитаты на мобильном не обрезается - блок
     растёт ровно на недостающие строки. Без обрезки высота = содержимое +
     прежний пустой остаток под должностью автора (замер при 5 строках:
     232 - 204.1 = 27.9px = 1px рамки + 26.9px), не меньше прежних 232px. */
  .vp-home-quote { height: auto; min-height: 232px; padding-bottom: 26.9px; }
  .vp-home-quote .vp-quote p { display: block; -webkit-line-clamp: unset; overflow: visible; }

  /* Владелец (14.09.2026): "информер Экскурсии... должен быть демпферным
     элементом, не иметь постоянной высоты и увеличиваться по высоте
     на/для соответствующего кол-ва строк текста" - на десктопе
     height:333px (.vp-excursions-box, база выше) держит высоту вровень с
     соседней карточкой в том же ряду сетки; на мобильном это уже не
     ряд, а единая колонка - выравнивать не с кем, а фиксированная высота
     только обрезает текст, если он вдруг длиннее обычного (ровно то,
     что и произошло). overflow:hidden внизу можно оставить - без
     фиксированной высоты обрезать уже нечего. */
  /* Владелец (14.09.2026, полная спецификация мобильной версии этого
     блока, поправлено следом - "и последнее расстояния не 45 pix (моя
     ошибка), а 15 pix"): плашка "Экскурсии" на мобильном состоит из 4
     видимых элементов (заголовок, текст, кнопка "Маршруты" - блок
     "Поделиться" СНЯТ, см. ниже), которые должны стоять на РАВНОМ
     удалении друг от друга и от краёв плашки - 15px, единое число для
     всего. Высота по-прежнему auto (см. более раннюю правку - плашка
     сама растёт под текст, не обрезает). */
  .vp-excursions-box, .vp-home-top-sidebar .vp-excursions-box {
    height: auto;
    padding: 15px;
  }
  .vp-excursions-box h3 {
    /* базовое (десктопное) margin:-8px 0 2.67px тянуло заголовок на 8px
       выше паддинга - на мобильном обнуляем оба, чтобы все 15px сверху
       были ровно от паддинга плашки, без вычетов. */
    margin-top: 0;
    margin-bottom: 0;
  }
  .vp-excursions-box p {
    margin-top: 15px;
  }
  .vp-excursions-box-footer {
    margin-top: 15px;
  }

  /* Владелец (14.09.2026): "чтобы кнопка Маршруты и блок Поделиться на
     узких экранах небольших смартфонов не оказывались друг на друге,
     удаляем в мобильной версии сайта блок Поделиться... В мобильной
     версии - только кнопка Маршруты в левом нижнем углу" - снимает саму
     причину переноса/тесноты, а не подгоняет вокруг нее (десктоп не
     трогаем - там Поделиться остаётся в правом нижнем углу, как и было). */
  .vp-excursions-box-footer .vp-share-row {
    display: none;
  }

  /* 14.09.2026: п.8's forced always-column mobile layout retired - see
     the base .vp-excursions-box-footer rule above, which now wraps only
     when it genuinely doesn't fit instead of on every phone regardless
     of width. */

  /* --- п.9: both footer link columns line up with the partner block
     below them. The desktop +45px indent (тезисы 6.9.26 п.2) applies to
     the first column only and made the two disagree on a phone. --- */
  .vp-footer-col:not(.vp-footer-partners) { margin-left: 0; margin-top: 0; }
  /* --- п.10: partner logo right-ranged (was flex-start at this width). --- */
  .vp-footer-partner-logo { align-self: flex-end; margin-left: 0; }

  /* --- п.11: the share widget inside a contributor frame is absolutely
     positioned on desktop (bottom-right corner), which on a phone landed
     it on top of the bio text. Back into normal flow, below the text,
     with the same gap the byline-to-bio pair uses above it (10.5px). --- */
  .vp-contributor-share {
    position: static; right: auto; bottom: auto;
    margin-top: 10.5px;
  }
  /* --- п.12: a long author name that wraps to two lines gets a quarter
     off its leading (1.2 * 0.75 = 0.9). Single-line names are visually
     unaffected. --- */
  .vp-contributor-card-body h3 { line-height: 0.9; }

  /* --- п.13: on a personal page the byline (the second part of the
     author's presentation) must sit as far from the bio below it as it
     does from the name above. Measured live there: 12px above vs 10px
     below, so +2px on this element's own bottom margin (10.5 -> 12.5)
     evens them up. Only the full card - the listing keeps the tighter
     pairing п.1a asked for. --- */
  .vp-contributor-card--full .vp-contributor-byline { margin-bottom: 12.5px; }

  /* --- п.14: in each publication row on a personal author page, the
     share widget goes last, under the author name, instead of being
     pinned into the row's corner where it touched the name. --- */
  .vp-search-result-share {
    position: static; right: auto; bottom: auto;
    margin-top: 10px;
  }

  /* --- п.16: the map is square on a phone. --- */
  .vp-map-canvas { height: auto; aspect-ratio: 1; }

  /* ==== Дневные тезисы 10.9.26 (мобильные) ==== */

  /* п.2: every gap in the Публикация header - Дата/место, Заголовок,
     Автор, Поделиться - becomes one body line (24px). Measured before:
     6 / 20 / 8 / 18px, all different. The author and Поделиться sit in
     one wrapping flex row, so the gap between them is that row's
     row-gap, not a margin. */
  .vp-page-time { margin-bottom: 24px; }
  .vp-article h1 { margin-bottom: 24px; }
  .vp-byline {
    margin-top: 24px; margin-bottom: 24px;
    row-gap: 24px;
    /* the desktop right inset exists only to line Поделиться up with the
       hero photo's right edge in a single row - meaningless once the row
       wraps on a phone. */
    padding-right: 0;
    /* align-items:center (the desktop value, which pairs the round author
       photo with the share icons) leaves ~2px of slack above and below
       each item once the row wraps, so the measured gaps came out 22px
       where the row-gap really is 24. flex-start removes the slack rather
       than compensating for it with a fudged number. */
    align-items: flex-start;
  }

  /* п.3: on a personal author page the photo above the name triples.
     The mobile breakpoint above sets 64px, so 3x = 192px. Scoped to the
     full card, i.e. the personal page - the Контрибьюторы listing keeps
     its own size. */
  .vp-contributor-card--full .vp-contributor-photo,
  .vp-contributor-card--full .vp-contributor-photo--placeholder { width: 192px; }

  /* ==== Мобильные тезисы 8.9.26 ==== */

  /* The desktop reading column (десктопные тезисы 8.9.26 п.2/п.4) is
     925px wide with a 25px right margin. On a phone `max-width:100%`
     already pins the width to the container, and that right margin then
     pushes the whole column PAST the container edge - a real 10px
     horizontal overflow, caught live. There is no "free white field" to
     grow into at this width anyway, so the column simply runs full
     width here, as it did before those two points. */
  .vp-article-body, .vp-lead, .vp-share-row-bottom, .vp-article .vp-tags {
    width: auto; margin-left: 0; margin-right: 0;
  }

  /* п.3: "Главная" is added as the first menu item on mobile only (see
     inc/layout.php) - the desktop menu doesn't have it and isn't meant to. */
  .vp-nav-home { display: block; }

  /* п.4: the Поделиться block on Публикация pages +15%. A transform
     scales the icons AND the gaps between them as one unit, which is
     what "увеличить блок" means here; left origin keeps it anchored to
     the column's left edge instead of drifting right as it grows.
     REGRESSION FIX (10.9.26): `.vp-share-row` is a block-level flex
     container, so the BOTTOM copy (inside .vp-share-row-bottom, not a
     flex item like the byline copy) stretched to the full 327px column
     and scaling that overflowed the viewport by 25px. Only the byline
     copy was checked when this shipped. fit-content makes the scaled box
     content-sized, which is what "the block" meant all along. */
  /* Дневные тезисы 10.9.26 п.2 follow-up: the +15% used to be a
     `transform: scale(1.15)`, which grows the element VISUALLY while its
     layout box stays the old size - so the scaled row overhung its box by
     ~2px top and bottom and ate 2px off the header gaps above and below
     it, which is why they measured 22 where the row-gap really was 24.
     Scaling the icons and their gutter for real (20 -> 23px, 10 -> 11.5px)
     is the same +15% with the layout box matching what the eye sees. */
  .vp-article .vp-share-row { width: fit-content; gap: 11.5px; }
  .vp-article .vp-share-row .vp-share { width: 23px; height: 23px; }

  /* п.6 + п.8: heading sizes. These two asks overlap (п.6 said -8pt for
     Публикация, п.8 said -10pt for Публикация + О проекте + Контакты);
     per the owner's ruling the LATER point wins, so it is a flat -10pt
     (this codebase's 1pt = 1.5px, so -15px) applied once, not -18pt
     cumulative. Публикация 47 -> 32px; О проекте/Контакты 36 -> 21px. */
  /* Ночные тезисы 10.9.26 (мобильные) п.2 — supersedes the flat -10pt of
     8.9.26 п.8, and unifies Публикация with Контакты/О проекте (which
     that earlier rule had left at a noticeably smaller 21px).
     The spec is given as a concrete measurement: «Восточнопрусский» in
     guillemets must be one capital-Щ narrower than the width it sits in
     (the reference article is ostpreussenflug-1929). Measured with the
     real heading font: at 32px that string is 319px and Щ is 35px, i.e.
     9.965px and 1.084px per px of font-size. Solving
        9.965·fs + 1.084·fs = available width
     gives fs = width / 11.049.
     The available width is the reading column, not the raw viewport:
     the column is 100vw - 48px (24px padding each side), and targeting
     the raw viewport instead would make the word 338px inside a 327px
     column - i.e. it would overflow and wrap, which is plainly not what
     was asked. Sized off the column ITSELF via container query units,
     not a fixed px or a vw expression: `100vw` counts a reserved
     scrollbar, which threw the result 15px off in testing even though a
     phone reserves none. `cqi` is the container's real inline size, so
     the Щ-wide gap comes out right on any screen and in any browser.
     The containment is scoped to mobile and to .vp-article alone. */
  .vp-article { container-type: inline-size; }
  .vp-article h1,
  .vp-article .vp-page-title { font-size: calc(100cqi / 11.049); }
  /* (8.9.26 п.8 set Контакты/О проекте to 21px here — a separate, smaller
     size than Публикация's. Ночные тезисы 10.9.26 п.2 above replaces that
     with one shared, measured size for all three page types, so the old
     21px rule is gone rather than left to fight it.) */

  /* п.7 (Контрибьюторы listing):
     a) name leading +3pt on top of мобильный п.12's 0.9 (24px * 0.9 =
        21.6px, + 4.5px = 26.1px -> 1.0875);
     b) the byline drops 3pt relative to the name - п.1a's -4.5px pull-up
        is exactly cancelled, so 0;
     c) byline leading -3pt. The byline's base leading is the browser's
        `normal`, which had to be MEASURED, not assumed - probing a real
        element live gave 20.667px at this 15.5px font-size (a first pass
        assumed ~18.6px and came out visibly too tight). So -4.5px lands
        at 16.167px -> 16.167/15.5 = 1.043. */
  .vp-contributor-card-body h3 { line-height: 1.0875; }
  .vp-contributor-byline { margin-top: 0; line-height: 1.043; }

  /* п.9: on the personal author page the same byline goes -2pt instead
     of -3pt: 20.667 - 3 = 17.667px -> 17.667/15.5 = 1.1398. */
  .vp-contributor-card--full .vp-contributor-byline { line-height: 1.1398; }

  /* Ночные тезисы 10.9.26 (мобильные) п.3: on Публикация pages only the
     SECOND part of the author byline — the credentials line under the
     name — loses 2pt of leading. Measured live: 14px font currently
     resolving to 22.4px (it inherits .vp-main's 1.6), so 22.4 - 3 =
     19.4px -> 19.4/14 = 1.3857. The name line above is untouched. */
  .vp-article .vp-page-author-text small { line-height: 1.3857; }
}

/* --- admin --- */
/* Владелец (13.09.2026): "не должно всё дёргаться при переключении
   вкладок" - настоящая причина (измерено computedStyle, не на глаз):
   body { display:flex; flex-direction:column } (sticky-footer приём) +
   margin:0 auto на .vp-admin - авто-отступы по кросс-оси flex-контейнера
   ОТМЕНЯЮТ растягивание (align-items:stretch по умолчанию) и заставляют
   элемент сжиматься до ширины своего контента (shrink-to-fit), а не
   тянуться до max-width. На страницах с широкой таблицей это было не
   заметно (собственная ширина контента и так превышала 1100px, упор в
   max-width срабатывал случайно), а на table-less страницах (Настройки)
   .vp-admin реально сжимался до ~350px - отсюда и скачок ширины между
   вкладками. Фикс: явный width:100% + align-self:center вместо
   margin:auto - тогда элемент действительно растягивается и потом
   упирается в max-width, независимо от того, что внутри. */
.vp-admin { max-width: 1100px; width: 100%; align-self: center; margin: 0; padding: 30px 24px; font-family: system-ui, sans-serif; box-sizing: border-box; }
/* article-edit.php внутри <iframe> в модалке списка статей (embed-режим,
   без шапки) - контенту незачем упираться в те же 1100px, что и на
   странице со своей шапкой рядом; iframe и так уже ограничен размером
   модалки. */
body.vp-embed { background: #fff; }
body.vp-embed .vp-admin { max-width: none; }

/* Владелец (13.09.2026): теги в редакторе статьи - мульти-выбор из
   справочника (чипы + автодополнение) вместо голого текста через
   запятую, с созданием нового значения на лету. Бэкенд не менялся -
   виджет просто собирает тот же "через запятую" формат в скрытое поле. */
#vp-tag-picker {
  border: 1px solid #ccc; border-radius: 4px; padding: 8px; position: relative;
  background: #fff;
}
.vp-tag-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.vp-tag-chips:not(:empty) { margin-bottom: 6px; }
/* Владелец: "надо чтобы выбранные теги были одинаковой ширины чтоб было
   красиво" - фиксированная ширина чипа, длинные названия обрезаются
   многоточием (title="" на самом чипе показывает полное название при
   наведении). */
.vp-tag-chip {
  background: #f3e8e6; color: #7a2e2e; border-radius: 4px; padding: 4px 6px 4px 10px;
  font-size: 13px; display: inline-flex; align-items: center; justify-content: space-between; gap: 6px;
  width: 150px; box-sizing: border-box; flex-shrink: 0;
}
.vp-tag-chip-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vp-tag-chip-remove {
  border: none; background: none; color: #7a2e2e; cursor: pointer; font-size: 15px;
  padding: 0 2px; line-height: 1; opacity: .7;
}
.vp-tag-chip-remove:hover { opacity: 1; }
#vp-tag-picker input#vp-tag-input {
  border: none; outline: none; width: 100%; padding: 4px; font: inherit;
}
.vp-tag-suggestions {
  position: absolute; top: 100%; left: -1px; right: -1px; margin-top: 2px;
  background: #fff; border: 1px solid #ccc; border-radius: 4px;
  max-height: 220px; overflow-y: auto; z-index: 20; box-shadow: 0 8px 20px rgba(0,0,0,.12);
}
.vp-tag-suggestion { padding: 8px 12px; cursor: pointer; font-size: 14px; }
.vp-tag-suggestion:hover { background: #f3e8e6; }
/* Тезисы 13.09.2026: дашборд (admin/index.php) в две колонки, чтобы
   помещался на 1920x1080 без прокрутки - раньше всё шло одной
   центрированной колонкой ~1100px, пустуя по краям широкого экрана.
   Отдельный класс, а не правка самого .vp-admin - остальные страницы
   админки (редактор статьи и т.п.) устроены как форма и не должны
   вдруг растягиваться на всю ширину. */
/* 13.09.2026: дашборд больше не шире остальных страниц - см. .vp-admin. */
.vp-dashboard-cols {
  display: grid;
  grid-template-columns: 2fr 3fr;
  gap: 0 40px;
  align-items: start;
  min-width: 0;
}
.vp-dashboard-col-pages, .vp-dashboard-col-articles { min-width: 0; }
/* 13.09.2026 (владелец): пропорция колонок 2:3, и ОБЕ заполняются
   блоками минимальной ширины, что переносятся на следующую строку,
   когда не влезают - блоки внутри колонки одного размера. Раньше слева
   был плоский список строк на всю ширину (при попытке сделать его
   сеткой из 2 жёстких под-колонок при узкой левой колонке (320px) блоки
   не влезали и вылезали поверх правой колонки - белый текст оказывался
   прямо на карточках). auto-fill сам решает, сколько блоков влезает в
   строку при текущей ширине колонки, вместо жёсткого числа колонок. */
.vp-dashboard-col-pages .vp-pages-grid {
  grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
  gap: 10px;
}
/* 13.09.2026: кнопки "+ Новая статья"/"Открыть список" переносились на
   вторую строку при более узкой правой колонке - явный flex nowrap,
   чтобы это не зависело от точной ширины колонки на разных экранах. */
.vp-admin-actions-row { display: flex; gap: 10px; flex-wrap: nowrap; align-items: center; }
.vp-admin-actions-row > * { flex-shrink: 0; }
.vp-dashboard-col-pages .vp-pages-row {
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
  border: 1px solid #eee;
  border-radius: 6px;
  padding: 10px 12px;
}
.vp-dashboard-col-pages .vp-pages-title { white-space: normal; overflow: visible; }
.vp-dashboard-col-articles .vp-admin-grid {
  grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
}
/* 13.09.2026: у минимума 170px кнопки вроде "Точки карты (157)" и
   "Порядок карточек" переносились на вторую строку - у одних карточек
   кнопка в одну строку, у других в две, высоты візуально не совпадали.
   210px - по факту измеренный минимум, на котором все реальные подписи
   кнопок влезают в одну строку. */
.vp-dashboard-col-articles .vp-admin-card .vp-btn { white-space: nowrap; }
@media (max-width: 1300px) {
  .vp-dashboard-cols { grid-template-columns: 1fr; }
}
.vp-admin h1 { font-family: system-ui, sans-serif; font-size: 22px; }
/* Владелец (13.09.2026): "таблица не должна дёргаться по ширине при
   применении фильтра" - с обычным table-layout:auto браузер каждый раз
   пересчитывает ширину колонок под ТОЛЬКО ВИДИМЫЕ строки (JS-фильтр
   скрывает display:none, не удаляет), так что колонки визуально
   "плавают" при каждом нажатии клавиши в поиске. table-layout:fixed
   останавливает это, но тогда все безразмерные колонки делят место
   ПОРОВНУ - поэтому три обязательные последние колонки (Создано/
   Изменено/действия, теперь всегда именно там - см. правку про порядок
   колонок выше) получают свою фиксированную ширину явно, а Заголовок +
   доменные колонки (Категория и т.п.) делят оставшееся место сами. */
.vp-admin table { width: 100%; border-collapse: collapse; margin: 16px 0; table-layout: fixed; }
.vp-admin table th:first-child, .vp-admin table td:first-child { width: 24%; }
/* 14.09.2026 (владелец): "обязательный столбец ID на первую позицию" в
   переиспользуемом компоненте списка - общее правило ":first-child:24%"
   выше теперь ошибочно доставалось бы новой узкой колонке ID вместо
   настоящего Заголовка (измерено - ID получал 252px/24%, Заголовок - лишь
   остаток). .vp-list-head - класс строки заголовка именно у
   vp_admin_list_table_head() (не у самодельных таблиц вроде модалки
   категорий карты, которых это не должно касаться) - ID узкий, Заголовок
   (второй столбец) получает те же 24%, что раньше доставались первому. */
/* 5% дало реально ~32px на узких таблицах (Настройки > Страницы/Теги, там таблица уже ~640-800px, не 1052px) - #511 в nowrap не помещался и наезжал на соседнюю колонку (измерено - оверфлоу без переноса вместо переноса, ещё хуже). max() держит абсолютный минимум независимо от ширины конкретной таблицы. */
/* max(5%, 52px) молча игнорировался браузером в table-layout:fixed (колонка реально рендерилась в унаследованные 24% от общего правила ниже, не 52px) - плоский px без функции надёжнее. */
.vp-admin table tr.vp-list-head th:first-child { width: 52px; }
.vp-admin table tr.vp-list-head th:nth-child(2) { width: 24%; }
.vp-admin table td:first-child { white-space: nowrap; }
/* 14.09.2026 (владелец): "редактор макета хоть как-то напоминал разметку
   страницы" - схема блоков поверх редактора. Фон - цвет самого сайта
   (не админский серый), чтобы читалось как миниатюра страницы; широкие
   блоки - flex-basis:100% (свой ряд), узкие (Цитата/Блок экскурсий) -
   ~31% и встают рядом друг с другом как реальный сайдбар. */
.vp-layout-map {
	display: flex; flex-wrap: wrap; gap: 8px; margin: 16px 0 28px; align-items: flex-start;
	padding: 14px; border-radius: 8px;
	background: #f7f3ee; border: 1px solid #e2d9cc;
}
.vp-layout-map-tile {
	flex: 1 1 100%; display: flex; flex-direction: column; gap: 2px;
	padding: 10px 12px; border-radius: 6px; background: #fff;
	border: 1px solid #ddd; text-decoration: none; color: #3a1414;
	font-size: 13px; transition: border-color .15s ease, transform .15s ease;
}
.vp-layout-map-tile:hover { border-color: #7a2e2e; transform: translateY(-1px); }
.vp-layout-map-tile--hidden { opacity: .45; }
.vp-layout-map-pos { font-size: 11px; color: #999; font-weight: 600; }
.vp-layout-map-tile--selected { border-color: #7a2e2e; border-width: 2px; box-shadow: 0 0 0 2px rgba(122,46,46,.15); }
/* 14.09.2026 (владелец): клик по схеме показывает ТОЛЬКО редактор
   выбранного блока - остальные скрыты, пока не выбраны. РЕАЛЬНЫЙ баг
   найден живьём: правило было на .vp-block-editor (без второго класса) -
   тот же класс совпадает с <details class="vp-block-editor"> у групп
   переводов интерфейса (Настройки > Интерфейс, класс переиспользован для
   визуала карточки, случайное совпадение имени) - все переводы разом
   пропали сайт-вайд. .vp-page-block - отдельный класс только для
   admin/page.php, никогда не переиспользуется. */
.vp-page-block { display: none; }
.vp-page-block.vp-page-block--active { display: block; }
/* Владелец (14.09.2026): Главная - реальный жёсткий шаблон в 2 колонки
   (см. index.php) продолженный лентой ниже - ОДИН непрерывный фон на всё
   (раньше 2-колоночный ряд и Лента были в разных плашках - "шестой блок
   отрезан от всех"), колонка - свой вертикальный стек. */
/* Владелец (14.09.2026): "шестой не растянут на всю ширину" - причина:
   базовое правило .vp-layout-map уже задавало align-items:flex-start, а
   flex-direction:column здесь разворачивает оси на 90° - flex-start стал
   действовать на ШИРИНУ (перекрёстная ось), поэтому лента съёживалась до
   content-width вместо растяжения. align-items:stretch возвращает
   растяжение по ширине для прямых детей (сам ряд колонок и лента ниже). */
/* Владелец (14.09.2026): "расстояние между 7 и 6 другое, чем между 1 и 7"
   - реальный зазор между .vp-home-top и home_feed тоже измерен - те же
   16px, что и внутри колонки, а тут стоял общий gap:8px от базового
   правила .vp-layout-map. Один и тот же масштабированный gap везде. */
.vp-layout-map--home { flex-direction: column; align-items: stretch; gap: 4.2565px; }
.vp-layout-map-row { display: flex; gap: 8px; align-items: flex-start; width: 100%; }
/* gap - тоже измеренное значение (16px на реальной странице / 4 = 4px),
   не произвольное - от него зависит совпадение высот левой/правой колонки. */
.vp-layout-map--home .vp-layout-map-col { flex: 2 1 0; display: flex; flex-direction: column; gap: 4.2565px; }
.vp-layout-map--home .vp-layout-map-col--narrow { flex: 1 1 0; min-width: 160px; }
.vp-layout-map--home .vp-layout-map-col .vp-layout-map-tile { flex: 1 1 auto; }

/* 14.09.2026 (владелец): "дате публикации слишком много места, когда
   заголовки не влазят" - "Публикация" (короткая дата) была единственной
   колонкой БЕЗ явной ширины и поэтому забирала весь остаток (370px);
   "Заголовок" при этом был жёстко зафиксирован в 24% и не мог расти.
   Меняем местами: у "Публикация" - узкая фиксированная ширина под дату,
   "Заголовок" (2-я колонка) - auto, забирает освободившееся место. */
#vp-articles-table th:nth-child(2), #vp-articles-table td:nth-child(2) { width: auto; }
#vp-articles-table th:nth-child(3), #vp-articles-table td:nth-child(3) { width: 110px; }

/* Владелец (14.09.2026): "числу столько места не надо" - тот же баг, что
   у Статей ("Публикация"), но системный: у vp_admin_list_table_head()
   extra_cells (Публикаций/Байлайн/Категория/Файл/Покупка/Координаты) нет
   явной ширины, поэтому под table-layout:fixed ВСЕ такие колонки в одной
   таблице делят остаток ПОРОВНУ, не по реальной надобности содержимого
   (короткое число получало те же 185-370px, что и текстовая колонка
   рядом). Явные проценты (сумма 100%) под каждую таблицу, где короткая
   колонка мешает длинной вырасти - Заголовок/Байлайн/Файл получают
   реально нужное место.
*/
/* Владелец (14.09.2026): "не надо было так сильно сжимать столбец,
   что заголовок её сплющило" - "Публикаций" (8%=84px) было физически
   уже своего собственного заголовка (нужно 106px, живой замер
   getBoundingClientRect) - word-break:break-word (.vp-admin table th,
   общее правило) ломал слово посреди буквы вместо явного переноса.
   Забрано у двух мест с реальным запасом (замерено, не на глаз):
   Заголовок 44->42% (реальным именам тегов не нужно и близко 462px),
   actions 11->10% (двум иконкам 32px+32px+отступ+паддинг хватает ~92px,
   10%=105px). Создано/Изменено НЕ трогал - у них самих впритык под
   "13.09.2026 23:11 · admin" (замерено 173px нужно против 168px
   сейчас) - уменьшение их только усугубило бы ту же проблему там же. */
#vp-tags-table th:nth-child(1), #vp-tags-table td:nth-child(1) { width: 5%; }
#vp-tags-table th:nth-child(2), #vp-tags-table td:nth-child(2) { width: 42%; }
#vp-tags-table th:nth-child(3), #vp-tags-table td:nth-child(3) { width: 11%; }
#vp-tags-table th:nth-child(4), #vp-tags-table td:nth-child(4) { width: 16%; }
#vp-tags-table th:nth-child(5), #vp-tags-table td:nth-child(5) { width: 16%; }
#vp-tags-table th:nth-child(6), #vp-tags-table td:nth-child(6) { width: 10%; }

/* 14.09.2026: тот же баг, что владелец нашёл на Тегах ("Публикаций"
   сплющило) - тут тот же текст в том же узком 8% (84px против нужных
   106px, замерено так же живьём). Взято у Байлайна (28%->25%, там
   294px было при реальной надобности ~79px - без потерь). */
#vp-contributors-table th:nth-child(1), #vp-contributors-table td:nth-child(1) { width: 5%; }
#vp-contributors-table th:nth-child(2), #vp-contributors-table td:nth-child(2) { width: 19%; }
#vp-contributors-table th:nth-child(3), #vp-contributors-table td:nth-child(3) { width: 25%; }
#vp-contributors-table th:nth-child(4), #vp-contributors-table td:nth-child(4) { width: 11%; }
#vp-contributors-table th:nth-child(5), #vp-contributors-table td:nth-child(5) { width: 14%; }
#vp-contributors-table th:nth-child(6), #vp-contributors-table td:nth-child(6) { width: 14%; }
#vp-contributors-table th:nth-child(7), #vp-contributors-table td:nth-child(7) { width: 12%; }

#vp-products-table th:nth-child(1), #vp-products-table td:nth-child(1) { width: 5%; }
#vp-products-table th:nth-child(2), #vp-products-table td:nth-child(2) { width: 18%; }
#vp-products-table th:nth-child(3), #vp-products-table td:nth-child(3) { width: 11%; }
#vp-products-table th:nth-child(4), #vp-products-table td:nth-child(4) { width: 20%; }
#vp-products-table th:nth-child(5), #vp-products-table td:nth-child(5) { width: 9%; }
#vp-products-table th:nth-child(6), #vp-products-table td:nth-child(6) { width: 13%; }
#vp-products-table th:nth-child(7), #vp-products-table td:nth-child(7) { width: 13%; }
#vp-products-table th:nth-child(8), #vp-products-table td:nth-child(8) { width: 11%; }

#vp-map-table th:nth-child(1), #vp-map-table td:nth-child(1) { width: 5%; }
#vp-map-table th:nth-child(2), #vp-map-table td:nth-child(2) { width: 27%; }
#vp-map-table th:nth-child(3), #vp-map-table td:nth-child(3) { width: 13%; }
#vp-map-table th:nth-child(4), #vp-map-table td:nth-child(4) { width: 19%; }
#vp-map-table th:nth-child(5), #vp-map-table td:nth-child(5) { width: 14%; }
#vp-map-table th:nth-child(6), #vp-map-table td:nth-child(6) { width: 13%; }
#vp-map-table th:nth-child(7), #vp-map-table td:nth-child(7) { width: 9%; }

.vp-admin table th:nth-last-child(3), .vp-admin table td:nth-last-child(3) { width: 140px; }
.vp-admin table th:nth-last-child(2), .vp-admin table td:nth-last-child(2) { width: 140px; }
.vp-admin table th:last-child, .vp-admin table td:last-child { width: 96px; }
/* Владелец (13.09.2026): доменные колонки (Категория/Файл/Покупка и
   т.п.) под table-layout:fixed делили место ПОРОВНУ с Заголовком, что
   при равном разделе давало слишком узкую колонку для длинных значений
   ("нет (скачать бесплатно)", имена файлов) - формально ячейки не
   перехлёстывались (проверено getBoundingClientRect), но текст
   вплотную упирался в границу строки. Заголовку теперь гарантирована
   доля 24% отдельно, остальные доменные колонки делят то, что осталось
   после неё и трёх запиненных справа - им теперь заметно свободнее. */
.vp-admin table td { word-break: break-word; }
/* 14.09.2026: узкие таблицы (Настройки, Теги/Страницы - там весь стол ~800px, не 1052px) - заголовок колонки типа Публикаций наезжал на соседний, когда auto-колонке не хватало места; перенос строк решает то же, что уже решает word-break у td. */
.vp-admin table th { word-break: break-word; }
/* Корзина - только 4 колонки (Тип/Заголовок/Удалено/действия), не через
   общий vp_admin_list_row() - "Удалено" тут ОДНА колонка перед
   действиями, не пара Создано+Изменено. Сбрасываем 3-с-конца обратно
   на гибкую ширину для этой конкретной таблицы. */
/* Владелец: "правый край контента явно шире чем на остальных страницах" -
   у Корзины только 4 колонки (не 6, как у остальных списков), поэтому
   общее правило ":first-child{width:24%}" доставалось короткому "Тип"
   (Автор/Тег/...), а не самому "Заголовку" - тот получал ВСЮ свободную
   ширину (auto), визуально растягиваясь заметно шире, чем колонка
   заголовка на любой другой списковой странице. Меняем местами: "Тип"
   узкий фиксированный, "Заголовок" получает те же 24%, что и везде. */
/* Реальная причина (измерено getBoundingClientRect, не на глаз): смешение
   % и px в table-layout:fixed - когда общая сумма не даёт 100%, браузер
   пропорционально ДОБИРАЕТ px-колонки остатком места, отчего "Удалено"
   (140px) и кнопки (96px) реально рендерились как ~306px/~210px, а не
   заданные значения. Все 4 колонки - только в %, сумма ровно 100%. */
/* 14.09.2026: добавлена колонка "Кем удалено" - пересчитаны проценты
   под реальные 5 колонок (было 4). */
#vp-trash-table th:first-child, #vp-trash-table td:first-child { width: 12%; }
#vp-trash-table th:nth-child(2), #vp-trash-table td:nth-child(2) { width: 35%; }
#vp-trash-table th:nth-child(3), #vp-trash-table td:nth-child(3) { width: 15%; }
#vp-trash-table th:nth-child(4), #vp-trash-table td:nth-child(4) { width: 15%; }
#vp-trash-table th:nth-child(5), #vp-trash-table td:nth-child(5) { width: 23%; }
/* 14.09.2026: media.php переведена на общий vp_admin_list_row(), но
   колонка "Заголовок (для поиска)" держит инпут+кнопку рядом (flex) - авто-
   раскладка таблицы не учитывает min-width flex-дочернего элемента при
   расчёте ширины колонки (измерено - колонка оставалась ~141px, кнопка
   реально наезжала на "Размер" на ~106px). Те же явные проценты (сумма
   ровно 100%), что уже применялись у #vp-trash-table по той же причине. */
#vp-media-table th:nth-child(1), #vp-media-table td:nth-child(1) { width: 9%; }
#vp-media-table th:nth-child(2), #vp-media-table td:nth-child(2) { width: 15%; }
#vp-media-table th:nth-child(3), #vp-media-table td:nth-child(3) { width: 30%; }
#vp-media-table th:nth-child(4), #vp-media-table td:nth-child(4) { width: 10%; }
#vp-media-table th:nth-child(5), #vp-media-table td:nth-child(5) { width: 14%; }
#vp-media-table th:nth-child(6), #vp-media-table td:nth-child(6) { width: 12%; }
#vp-media-table th:nth-child(7), #vp-media-table td:nth-child(7) { width: 10%; }
/* Переключатель видимости раздела на сайте (перенесён с дашборда рядом
   с подсказкой, владелец 13.09.2026) - тот же визуальный язык, что у
   .vp-hint-icon (круглая 22px кнопка), но своя пара классов, чтобы не
   путать смысл (это toggle, не всплывающая подсказка). */
/* Владелец (13.09.2026): даже после vertical-align:middle на кнопке,
   внешний <span class="vp-icon-toggle"> сам отчитывался высотой 58px
   при том, что единственный потомок (<form>) - 26px. Три уровня
   вложенности (span > form > button) дают неоднозначный inline-
   baseline расчёт браузера. display:contents на form убирает у него
   собственный inline-box из расчёта (тот же приём, что и для
   вложенных <a> ранее в этой сессии) - button становится прямым
   потомком span по разметке лэйаута, vertical-align:middle на кнопке
   работает предсказуемо. */
.vp-icon-toggle { display: inline-block; vertical-align: middle; }
.vp-icon-toggle form { display: contents; }
.vp-icon-toggle .vp-icon-toggle-btn {
	vertical-align: middle;
	display: inline-flex; align-items: center; justify-content: center;
	width: 22px; height: 22px; padding: 0; border-radius: 50%; flex-shrink: 0;
	border: 1px solid #ccc; background: #fff; color: #999; cursor: pointer;
}
.vp-icon-toggle .vp-icon-toggle-btn:hover { border-color: #7a2e2e; color: #7a2e2e; }
.vp-icon-toggle .vp-admin-icon { width: 14px; height: 14px; }

.vp-admin th, .vp-admin td { text-align: left; padding: 8px 10px; border-bottom: 1px solid #eee; font-size: 14px; }
.vp-admin form { margin: 16px 0; }
.vp-admin label { display: block; margin: 12px 0 4px; font-size: 13px; font-weight: 600; color: #444; }
/* Владелец (13.09.2026): "поля фильтров у таблицы тоже должны быть
   единообразно крупными, как у модуля Карты" - type=search не входил
   в этот общий список, поэтому поля поиска на разных списковых
   страницах стилизовались вразнобой (кто инлайн-стилем на свой лад,
   кто вообще без стиля - крошечные браузерные дефолты). */
.vp-admin input[type=text], .vp-admin input[type=password], .vp-admin input[type=number], .vp-admin input[type=search],
.vp-admin textarea, .vp-admin select {
  width: 100%; padding: 8px 10px; border: 1px solid #ccc; border-radius: 4px; font: inherit; font-size: 14px; box-sizing: border-box;
}
.vp-admin textarea { min-height: 100px; }

.vp-tabs { display: flex; gap: 4px; border-bottom: 2px solid #e2e2e2; margin: 0 0 24px; }
.vp-tabs a {
  padding: 10px 18px; color: #666; text-decoration: none; font-weight: 600; font-size: 14px;
  border-bottom: 2px solid transparent; margin-bottom: -2px; display: flex; align-items: center; gap: 6px;
}
.vp-tabs a:hover { color: #7a2e2e; }
.vp-tabs a.vp-tab-active { color: #7a2e2e; border-bottom-color: #7a2e2e; }
/* 14.09.2026 (владелец): "не звонит место, столбцы не влазят - сделай таблицы в табах на всю ширину, как на страницах с такими же таблицами" - max-width:640px тут был реальной причиной (Теги/Страницы теснились в трети обычной ширины .vp-admin, ~1100px, которую получают Статьи/Точки карты и т.п.) - убран, вкладки Настроек теперь той же ширины, что и всё остальное. */

/* Владелец (13.09.2026): "поля фильтров... в одну строчку, и справа!" -
   .vp-admin select { width:100% } (общее правило выше) тянуло <select>
   на всю ширину ряда, из-за чего кнопка "Категории" после него не
   помещалась и переносилась на вторую строку. Общий контрол строки
   фильтров списковых страниц - переиспользуемый на будущее, не только
   для Точек карты. */
.vp-list-filter-row {
  display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin: 12px 0 20px;
}
.vp-list-filter-row input[type=search] { flex: 1 1 200px; width: auto; }
.vp-list-filter-row select { flex: 0 1 auto; width: auto; min-width: 170px; }
.vp-list-filter-row .vp-list-filter-action { flex-shrink: 0; margin-left: auto; }
.vp-admin button, .vp-admin .vp-btn {
  font: inherit; font-size: 14px; padding: 8px 16px; border-radius: 4px;
  border: 1px solid #7a2e2e; background: #7a2e2e; color: #fff; cursor: pointer; text-decoration: none; display: inline-block;
}
.vp-admin .vp-btn-secondary { background: #fff; color: #7a2e2e; }
.vp-admin .vp-btn-danger { background: #fff; color: #a13a2e; border-color: #a13a2e; }
/* Владелец (13.09.2026): кнопки Править/Удалить в списках - иконками,
   всегда в один ряд (раньше текстовые кнопки переносились на отдельные
   строки в узкой колонке действий). display:flex + white-space:nowrap
   на самой ячейке гарантируют это независимо от ширины столбца. */
.vp-admin table td:last-child { white-space: nowrap; }
.vp-list-actions { display: flex; gap: 6px; flex-wrap: nowrap; }
.vp-list-actions form { margin: 0; }
/* Реальная причина смещения (не на глаз - измерено getBoundingClientRect,
   13.09.2026): базовое ".vp-admin button, .vp-admin .vp-btn" (padding:
   8px 16px; display:inline-block) специфичнее (класс+элемент) простого
   ".vp-list-action-btn" (один класс) и перебивало padding:0/display:flex
   здесь для САМОЙ КНОПКИ (form>button у "Удалить") - ссылка "Править"
   не страдала, т.к. это <a>, не <button>. Специфичность поднята явным
   ".vp-list-actions .vp-list-action-btn" (2 класса > 1 класс+1 элемент). */
.vp-list-actions .vp-list-action-btn {
	display: inline-flex; align-items: center; justify-content: center;
	width: 32px; height: 32px; padding: 0; flex-shrink: 0;
	border: 1px solid #7a2e2e; border-radius: 4px; background: #fff; color: #7a2e2e;
	cursor: pointer; font: inherit;
}
.vp-list-actions .vp-list-action-btn:hover { background: #7a2e2e; color: #fff; }
.vp-list-actions .vp-list-action-btn--danger { border-color: #a13a2e; color: #a13a2e; }
.vp-list-actions .vp-list-action-btn--danger:hover { background: #a13a2e; color: #fff; }
/* Владелец 07.10.2026: переключатели видимости "Страниц" по устройству
   (десктоп/мобильный) - та же кнопка, что и остальные действия списка,
   плюс маленькая буква-подпись (Д/М), т.к. отдельных иконок "монитор"/
   "телефон" в наборе нет. Притушенная (opacity), когда пункт сейчас
   скрыт для этого устройства - визуально понятно без чтения title. */
.vp-list-action-btn--device { position: relative; }
.vp-list-action-device-label {
  position: absolute; bottom: 1px; right: 2px; font-size: 8px; font-weight: 700;
  line-height: 1; pointer-events: none;
}
.vp-list-action-btn--hidden { opacity: 0.45; }
/* Голая иконка-триггер (не кнопка из колонки действий) - напр. значок
   предпросмотра точки рядом с текстом координат в списке. */
.vp-admin .vp-inline-icon-btn {
  border: none; background: none; padding: 0; display: inline-flex; vertical-align: middle;
  color: #7a2e2e; cursor: pointer;
}
.vp-admin .vp-inline-icon-btn:hover { color: #a13a2e; }
.vp-admin .vp-inline-icon-btn .vp-admin-icon { width: 16px; height: 16px; }
.vp-sortable { cursor: pointer; user-select: none; white-space: nowrap; }
.vp-sortable::after { content: ''; display: inline-block; width: 0; margin-left: 4px; }
.vp-sortable.vp-sort-asc::after { content: '\2191'; }
.vp-sortable.vp-sort-desc::after { content: '\2193'; }

/* Владелец (13.09.2026): текстовые пояснения под заголовками списковых
   страниц - в подсказку по иконке, не статичным абзацем. Свой попап
   (не браузерный [title]), тон в цвет топбара (#3a1414) для узнаваемого
   "это подсказка админки", не системная белая плашка. */
/* Владелец (13.09.2026): "не должны дёргаться заголовки по высоте
   при переходе по пунктам меню" - причина та же, что уже чинили на
   карандаше статьи (article.php): display:flex на самом h1 заставляет
   его текстовый узел стать flex-item'ом, который при недостатке места
   (два значка рядом - подсказка + переключатель видимости) переносится
   на 2 строки, раздувая высоту h1 (54px вместо обычных ~25px) - и
   именно ЭТО расхождение между страницами ощущалось как "прыжок"
   при переходе между пунктами меню. Убрано flex целиком - значки
   просто обычный инлайн-контент после текста, как везде на сайте. */
.vp-title-with-hint { }
.vp-title-with-hint .vp-hint { margin-left: 10px; }
/* Владелец (13.09.2026): "иконка и текст на разных уровнях" - измерено
   (getBoundingClientRect): центр кружка-иконки был на ~3.6px ниже
   геометрического центра самого заголовка (vertical-align:middle
   выравнивает не по видимому центру текста, а по метрике шрифта). */
.vp-title-with-hint .vp-hint .vp-hint-icon { transform: translateY(-3.6px); }
.vp-title-with-hint .vp-icon-toggle { margin-left: 6px; }
/* Иконка "добавить" рядом с заголовком списка (владелец, 14.09.2026) -
   тот же масштаб, что и у заголовка, вместо отдельной кнопки-абзаца. */
/* Тот же специфичностный баг, что уже правился несколько раз этой сессией
   (.vp-admin button/.vp-admin .vp-btn - класс+элемент - перебивает один
   класс) - .vp-admin .vp-title-add-btn (2 класса) побеждает независимо
   от порядка в файле. */
.vp-admin .vp-title-add-btn {
  display: inline-flex; align-items: center; justify-content: center; vertical-align: middle;
  margin-left: 10px; border: none; background: none; color: #7a2e2e; cursor: pointer; padding: 0;
  transform: translateY(-3.8px);
  transition: color .15s ease, transform .15s ease;
}
.vp-admin .vp-title-add-btn .vp-admin-icon { width: 22px; height: 22px; }
.vp-admin .vp-title-add-btn:hover { color: #a13a2e; transform: translateY(-3.8px) scale(1.15); }
/* Владелец: "поднасрать на соседнюю" - фикс центрирования ⓘ применялся
   только к .vp-hint-icon, а не к соседней .vp-icon-toggle-btn - тот же
   сдвиг на -3.6px нужен обеим, иначе они расходятся друг с другом
   (измерено: без этой строки toggleCenter=108.92 против h1Center=105.29,
   то есть та же ошибка, что была у ⓘ). */
.vp-title-with-hint .vp-icon-toggle .vp-icon-toggle-btn { transform: translateY(-3.6px); }
/* Владелец (13.09.2026): иконки подсказки и переключателя видимости
   стояли на разной высоте рядом друг с другом - inline-flex на самих
   обёртках даёт каждой свой независимый baseline-расчёт, и vertical-
   align:middle на РАЗНЫХ inline-flex контейнерах не гарантирует общую
   линию. Убрано display:inline-flex с обёрток - обычный inline-block,
   а vertical-align:middle перенесён на сами кнопки (одинаковый способ
   выравнивания для обеих). */
.vp-hint { position: relative; display: inline-block; vertical-align: middle; }
/* Тот же специфичностный баг, что и у .vp-list-action-btn (13.09.2026):
   ".vp-admin button" (класс+элемент) перебивает один класс
   ".vp-hint-icon" - scoping через родителя ".vp-hint" поднимает
   специфичность (2 класса) выше базового правила. */
.vp-hint .vp-hint-icon {
	vertical-align: middle;
	display: inline-flex; align-items: center; justify-content: center;
	width: 22px; height: 22px; padding: 0; border-radius: 50%; flex-shrink: 0;
	border: 1px solid #ccc; background: #fff; color: #999; cursor: help;
}
.vp-hint-icon .vp-admin-icon { width: 14px; height: 14px; }
.vp-hint:hover .vp-hint-icon, .vp-hint:focus-within .vp-hint-icon { border-color: #7a2e2e; color: #7a2e2e; }
.vp-hint-bubble {
	position: absolute; top: 30px; left: 0; z-index: 30;
	width: 360px; max-width: 80vw; max-height: 320px; overflow-y: auto; box-sizing: border-box;
	background: #3a1414; color: #f3e8e6; font-size: 13px; line-height: 1.5; font-weight: 400;
	padding: 12px 14px; border-radius: 8px; box-shadow: 0 8px 24px rgba(0,0,0,0.25);
	opacity: 0; visibility: hidden; transform: translateY(-4px); pointer-events: none;
	transition: opacity .15s ease, transform .15s ease;
}
.vp-hint-bubble code {
	background: rgba(255,255,255,0.15); color: #f3e8e6; padding: 1px 5px; border-radius: 3px; font-size: 12px;
}
.vp-hint-bubble a { color: #8ad6f2; }
/* Владелец: маленькая цифра в ячейке таблицы - не заголовок, значок
   подсказки рядом с ней должен быть меньше и по-настоящему центрирован
   (flex, не vertical-align на глаз). */
.vp-count-hint { display: inline-flex; align-items: center; gap: 4px; }
.vp-count-hint .vp-hint-icon { width: 16px; height: 16px; }
.vp-count-hint .vp-hint-icon .vp-admin-icon { width: 10px; height: 10px; }
/* Владелец: "сделай кастомный скролл - узкий и в цвете дизайна" - для
   длинных списков публикаций внутри тёмной подсказки. */
.vp-hint-bubble { scrollbar-width: thin; scrollbar-color: rgba(255,255,255,0.35) transparent; }
.vp-hint-bubble::-webkit-scrollbar { width: 6px; }
.vp-hint-bubble::-webkit-scrollbar-track { background: transparent; }
.vp-hint-bubble::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.35); border-radius: 3px; }
.vp-hint-bubble::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.55); }
.vp-hint-bubble::before {
	content: ''; position: absolute; top: -6px; left: 10px;
	border: 6px solid transparent; border-bottom-color: #3a1414;
}
.vp-hint:hover .vp-hint-bubble, .vp-hint:focus-within .vp-hint-bubble {
	opacity: 1; visibility: visible; transform: translateY(0); pointer-events: auto;
}
.vp-block-editor { border: 1px solid #ddd; border-radius: 6px; padding: 16px; margin: 12px 0; background: #fff; }
.vp-block-editor .vp-block-type { font-size: 12px; text-transform: uppercase; letter-spacing: 0.05em; color: #999; }
.vp-block-editor-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.vp-block-editor-head form { margin: 0; }
/* A block toggled off via the eye icon stays in the DB (content intact,
   just not rendered on the public site) - dashed border + light tint so
   it reads as "off" at a glance in the list, distinct from a block that
   was simply never filled in. */
.vp-block-editor--hidden { border-style: dashed; background: #fafafa; opacity: 0.75; }
.vp-block-hidden-badge {
  display: inline-block; margin-left: 8px; padding: 2px 7px;
  background: #f1e9e4; color: #a13a2e; border-radius: 3px;
  font-size: 11px; text-transform: none; letter-spacing: normal; font-weight: 600;
}

/* Dashboard: the simple one-button sections (Карта/Лавка/Медиа/...) used
   to stack full-width one per row, leaving most of the page's horizontal
   space empty - a grid of compact cards uses it instead. */
.vp-admin-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: 16px;
  margin: 20px 0;
}
.vp-admin-card {
  position: relative;
  border: 1px solid #eee; border-radius: 8px; padding: 16px;
  display: flex; flex-direction: column; gap: 8px;
}
.vp-admin-card h2 { margin: 0; padding-right: 34px; font-size: 16px; }
.vp-admin-card p { margin: 0; color: #777; font-size: 13px; flex: 1; }
.vp-admin-card .vp-btn { text-align: center; }
/* Pinned to the card's own corner instead of sitting inline next to the
   heading - inline made short titles like "Карта" wrap awkwardly above
   the icon and left a lot of dead space under it. */
.vp-admin-card-eye { position: absolute; top: 14px; right: 14px; margin: 0; }
/* A feature toggled off (Карта/Лавка - the whole public page 404s, see
   map.php/shop.php) reads as "off" the same way a hidden constructor
   block does - dashed border + tint + badge, not just the icon state. */
.vp-admin-card--hidden { border-style: dashed; background: #fafafa; opacity: 0.75; }

/* Pages list: same "don't waste the wide dashboard on a single narrow
   column" fix as the section cards above, applied to a plain row list
   instead of a table (a table's columns don't reflow into two side by
   side, a grid's do). */
.vp-pages-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 4px 20px;
  margin: 16px 0;
}
.vp-pages-row {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 10px 0; border-bottom: 1px solid #eee;
  font-size: 14px;
}
.vp-pages-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vp-pages-title code { font-size: 12px; color: #999; }
/* Владелец (14.09.2026): Страницы переехали в Настройки - "оформи в
   сортируемый список", не в 3-колоночную сетку дашборда (.vp-pages-grid
   выше) - одна колонка, .vp-pages-row уже сам по себе строка списка. */
.vp-pages-list { margin: 16px 0; max-width: 640px; }
.vp-pages-actions { display: flex; gap: 6px; flex-shrink: 0; }
/* Icon-only action buttons - a real inline SVG (Feather-style outline
   icons), not an emoji glyph: Windows browsers can't reliably render
   emoji as color glyphs (same issue already hit and fixed for the
   language-flag icons), so anything meant to read as an icon on this
   site goes through an actual image/SVG, never emoji. */
/* .vp-admin button's generic rule (border/background/padding, meant for
   normal text buttons) is one type-selector more specific than a bare
   class, so it was winning over .vp-btn-icon on the <button> version of
   this (the block-visibility toggle) and rendering it as a full-size
   maroon button instead of a small icon - scope to .vp-admin explicitly
   so it wins regardless of element type (<a> or <button>). */
.vp-admin .vp-btn-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; padding: 0; border-radius: 4px;
  border: 1px solid #ccc; background: #fff; color: #7a2e2e;
  text-decoration: none; flex-shrink: 0; cursor: pointer;
}
.vp-admin .vp-btn-icon:hover { border-color: #7a2e2e; background: #f9f2f0; }
@media (max-width: 900px) {
  .vp-pages-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 560px) {
  .vp-pages-grid { grid-template-columns: 1fr; }
}

.vp-admin-collapsible { margin: 12px 0; }
.vp-admin-collapsible summary { cursor: pointer; font-size: 13px; font-weight: 600; color: #7a2e2e; }
.vp-admin-inline-form {
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: 12px; margin-top: 10px !important;
}
.vp-admin-inline-form > div { flex: 1 1 180px; }
.vp-admin-inline-form label { margin: 0 0 4px !important; }
.vp-admin-inline-form button { flex-shrink: 0; }

/* Big admin lists (articles, media, snippets) open in a modal instead of
   sitting inline on the dashboard - kept out of the page flow until
   asked for, with a search box to jump straight to one row. */
.vp-modal {
  border: 1px solid #ddd; border-radius: 8px; padding: 0;
  width: min(900px, 92vw); max-height: 82vh;
  box-shadow: 0 16px 40px rgba(0,0,0,0.2);
  /* 13.09.2026: <dialog> сам по себе тоже скроллился (UA-стили), плюс
     свой скролл был у .vp-modal-body - два скролла одновременно на одном
     окне. Только внутренний должен скроллиться - flex-колонка, а не
     фиксированный calc(82vh-60px) на body (тот подгон "на глаз" реально
     срезал последнюю строку/кнопку, если шапка была чуть выше 60px). */
  overflow: hidden;
}
/* РЕАЛЬНЫЙ БРАК (13.09.2026): display:flex ставился прямо на .vp-modal
   без разбора - авторский display ВСЕГДА побеждает UA-стиль браузера
   `dialog:not([open]) { display: none }` независимо от специфичности
   (порядок каскада: origin важнее specificity). Поэтому .close() исправно
   снимал [open] и ::backdrop (он зависит от настоящего open-состояния,
   моя правка его не касалась - отсюда "серый фон пропадает"), а сам
   диалог как был display:flex, так и оставался видимым навсегда.
   display/flex-direction обязаны идти ТОЛЬКО через [open], иначе .close()
   перестаёт визуально закрывать диалог. */
.vp-modal[open] {
  display: flex;
  flex-direction: column;
}
.vp-modal::backdrop { background: rgba(20,20,20,0.45); }
.vp-modal-head {
  display: flex; align-items: center; gap: 10px; justify-content: space-between;
  padding: 14px 16px; border-bottom: 1px solid #eee;
  position: sticky; top: 0; background: #fff;
  flex-shrink: 0;
}
.vp-modal-head input[type=search] {
  flex: 1; padding: 7px 10px; border: 1px solid #ccc; border-radius: 4px; font: inherit; font-size: 14px;
}
/* Вечерние тезисы 4.9.26 п.10: literally 3x (22 -> 66px) and bolder -
   the owner's own explicit ask, emphasized ("три!") specifically against
   rounding it down to something more modest. .vp-modal-head is a plain
   flex row with align-items:center, so it simply grows taller to fit
   this - nothing to clip or break.
   Selector is `.vp-admin .vp-modal-close` (two classes), NOT the plainer
   `.vp-modal-close` - this button is always a real <button> inside
   `.vp-admin`, and `.vp-admin button` (a class+element combo, see above)
   otherwise wins the cascade over a single-class selector regardless of
   which rule comes later in the file, silently reverting font-size/
   background/color back to the generic admin button look (confirmed
   live - this bit me once already before adding the extra class here). */
.vp-admin .vp-modal-close {
  border: none; background: none; color: #999; cursor: pointer; padding: 4px;
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
}
/* Владелец (13.09.2026): "зачем такой огромный отступ... сделай сам
   крестик меньше" - было 32px иконка + 6px padding (44px хитбокс);
   уменьшено до того же масштаба, что и иконки-действия в списках
   (.vp-list-action-btn: 18px иконка внутри 32px квадрата). */
.vp-admin .vp-modal-close .vp-admin-icon { width: 18px; height: 18px; }
.vp-admin .vp-modal-maximize {
  border: none; background: none; color: #999; cursor: pointer; padding: 4px;
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
}
.vp-admin .vp-modal-maximize .vp-admin-icon { width: 18px; height: 18px; }
.vp-admin .vp-modal-maximize:hover { color: #7a2e2e; }
.vp-admin .vp-modal-maximize:focus { outline: none; }
.vp-admin .vp-modal-maximize:focus-visible { outline: 2px solid #7a2e2e; outline-offset: 1px; border-radius: 6px; }
.vp-modal-head-actions { display: flex; align-items: center; gap: 4px; }
/* !important - у модалок разная ширина по умолчанию (инлайн style=""
   width:min(...)), только так переключатель класса гарантированно
   перебивает любую из них. */
.vp-modal.vp-modal-maximized {
  width: 96vw !important; max-width: 96vw !important;
  height: 92vh !important; max-height: 92vh !important;
}
.vp-modal-maximized #vp-point-preview-map { height: 70vh !important; }
/* Владелец: просмотр изображений (media.php) - без ручного ресайза,
   окно само сжимается/растёт под размер картинки (не фиксированные
   min(900px,92vw) как у остальных модалок), с разумным минимумом и
   потолком в размер экрана. */
.vp-modal.vp-modal-lightbox {
  width: fit-content; max-width: 96vw; min-width: 280px;
  max-height: 92vh; min-height: 160px;
}
.vp-modal-lightbox .vp-modal-body {
  display: flex; align-items: center; justify-content: center;
  padding: 0 16px 16px; overflow: hidden;
}
.vp-modal-lightbox img {
  display: block; max-width: calc(96vw - 32px); max-height: calc(92vh - 60px);
  width: auto; height: auto;
}
.vp-admin .vp-modal-close:hover { color: #a13a2e; }
/* Владелец (13.09.2026): "уродская кнопка закрытия" - на самом деле это
   был не сам крестик, а браузерный чёрный focus-outline: showModal() по
   умолчанию фокусирует первый фокусируемый элемент диалога, а крестик в
   разметке идёт первым - на скриншоте сразу после открытия окна вокруг
   него всегда была стандартная browser-default рамка фокуса. Убираем
   дефолтную и ставим свою, фирменную, а не просто outline:none (совсем
   без индикации фокуса - плохо для доступности с клавиатуры). */
.vp-admin .vp-modal-close:focus { outline: none; }
.vp-admin .vp-modal-close:focus-visible { outline: 2px solid #7a2e2e; outline-offset: 1px; border-radius: 6px; }

/* Тезисы 13.09.2026: визуальный пикер картинок (превью + "Выбрать...")
   вместо голого <select> с id/именами файлов - inc/image.php
   vp_media_picker_field()/vp_media_picker_modal_and_script(). */
.vp-media-picker {
  display: flex; align-items: center; gap: 12px; margin: 6px 0 14px;
  border: 1px solid #ddd; border-radius: 6px; padding: 8px;
}
.vp-media-picker-preview {
  width: 70px; height: 70px; flex-shrink: 0; border-radius: 4px; overflow: hidden;
  background: #f4f4f4; display: flex; align-items: center; justify-content: center;
}
.vp-media-picker-preview img { width: 100%; height: 100%; object-fit: cover; display: block; }
.vp-media-picker-placeholder { font-size: 11px; color: #999; text-align: center; padding: 4px; }
.vp-media-picker-info { display: flex; flex-direction: column; gap: 6px; min-width: 0; flex: 1; }
.vp-media-picker-label {
  font-size: 13px; color: #555; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.vp-media-picker-actions { display: flex; gap: 8px; }
.vp-media-picker-actions .vp-btn-secondary { padding: 5px 12px; font-size: 13px; }
.vp-media-picker-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); gap: 10px;
}
.vp-media-picker-cell {
  border: 1px solid #ddd; border-radius: 4px; padding: 4px; background: #fff; cursor: pointer;
  display: flex; flex-direction: column; align-items: center; gap: 4px;
}
.vp-media-picker-cell:hover { border-color: #7a2e2e; }
.vp-media-picker-cell img { width: 100%; height: 90px; object-fit: cover; border-radius: 3px; }
.vp-media-picker-cell span { font-size: 11px; color: #999; }
.vp-media-picker-empty { grid-column: 1 / -1; color: #999; font-size: 13px; }
#vp-media-picker-search { width: 100%; padding: 8px 10px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; }

/* 13.09.2026 (владелец): пары полей формы статьи в 2 колонки на широком
   экране (дата+место, автор-текст+автор-контрибьютор, обе иллюстрации) -
   раньше все поля шли одно под другим на всю ширину формы. */
.vp-field-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 0 24px; }

/* 13.09.2026: «Читайте также» - собственный виджет вместо textarea со
   слагами (admin/article-edit.php, vp-related-picker). */
.vp-related-list { list-style: none; margin: 6px 0; padding: 0; border: 1px solid #ddd; border-radius: 6px; }
.vp-related-row {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 8px 12px; border-bottom: 1px solid #eee;
}
.vp-related-row:last-child { border-bottom: none; }
.vp-related-info { display: flex; flex-direction: column; min-width: 0; }
.vp-related-title { font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vp-related-title--missing { color: #a13a2e; }
.vp-related-slug { font-size: 12px; color: #999; font-family: ui-monospace, monospace; }
.vp-related-actions { display: flex; gap: 4px; flex-shrink: 0; }
.vp-related-actions button {
  width: 26px; height: 26px; padding: 0; border: 1px solid #ccc; border-radius: 4px;
  background: #fff; cursor: pointer; color: #7a2e2e; font-size: 13px; line-height: 1;
}
.vp-related-actions button:hover { border-color: #7a2e2e; background: #f9f2f0; }
.vp-related-empty { padding: 10px 12px; color: #999; font-size: 13px; }
.vp-related-modal-list { display: flex; flex-direction: column; gap: 2px; }
.vp-related-modal-row {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 8px 10px; border: none; border-bottom: 1px solid #eee; background: #fff;
  text-align: left; cursor: pointer; font: inherit;
}
.vp-related-modal-row:hover { background: #f9f2f0; }
.vp-related-modal-row .vp-related-title { flex: 1; }

/* 13.09.2026: заголовок (alt_ru) редактируемый прямо в списке медиатеки
   + просмотр картинки в модальном окне по клику на миниатюру. */
.vp-media-title-row { display: flex; gap: 6px; align-items: center; }
/* 14.09.2026: колонка теперь в общей auto-раскладке переиспользуемой таблицы (не своя фиксированная ширина, как раньше) - без min-width инпут+кнопка внутри flex-строки схлопывались до ~20px. */
.vp-media-title-input { width: 100%; min-width: 140px; padding: 5px 8px; border: 1px solid #ccc; border-radius: 4px; font: inherit; font-size: 13px; }
.vp-media-title-save { padding: 5px 10px; font-size: 12px; flex-shrink: 0; white-space: nowrap; }
.vp-field-pair > * { min-width: 0; }
@media (max-width: 700px) {
  .vp-field-pair { grid-template-columns: 1fr; }
}
.vp-modal-body { padding: 4px 16px 16px; overflow-y: auto; flex: 1 1 auto; min-height: 0; }
.vp-modal-body table { width: 100%; }
.vp-lang-switch-admin { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0 20px; }
.vp-lang-switch-admin .vp-btn, .vp-lang-switch-admin .vp-btn-secondary { padding: 5px 10px; font-size: 13px; }

/* ==================== Фотогалерея (10.09.2026) ====================
   The article photo gallery (article_gallery table, rendered by
   vp_render_article_gallery() in inc/blocks.php, enlarged by
   assets/gallery.js). Appended as one self-contained block at the end of
   the file on purpose: every selector here is new and prefixed
   .vp-gallery-* / .vp-lightbox-*, so it cannot collide with - or be
   collided with by - the врезки (.page-block/.page-caption), share-row,
   tag-list, footer or `.vp-article-body img` rules being edited
   elsewhere in this file at the same time.

   The gallery is a sibling of .vp-article-body, NOT a child, so none of
   that column's `img` sizing applies to it - it carries the same
   925px/right-flush geometry itself so the page keeps one left edge.
   (See десктопные тезисы 8.9.26 п.2/п.4 for where that number is from.) */
.vp-gallery {
  width: 925px; max-width: 100%; margin-left: auto; margin-right: 25px;
  margin-top: 28px; margin-bottom: 34px;
}
/* Владелец 18.09.2026: Галерея на месте главного фото публикации -
   уже 925px как у остального текста, а должна быть уже, 900px. Обычная
   Галерея/Подборка внутри текста (не в этой обёртке) не трогается. */
.vp-gallery-hero .vp-gallery { width: 900px; }
.vp-gallery-heading {
  font-size: 22px; margin: 0 0 14px; font-weight: 700; color: #000;
}
/* The photo count, small and grey next to the heading - the same
   restrained treatment the byline/caption greys use elsewhere. */
.vp-gallery-count {
  font-family: system-ui, sans-serif; font-size: 14px; font-weight: 400; color: #999;
}
/* auto-fill + minmax gives a tidy grid that reflows on its own at every
   width instead of needing a breakpoint per column count. minmax()'s
   floor is what stops the cells collapsing; `1fr` as the ceiling is what
   keeps the row flush to both edges. */
.vp-gallery-grid {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 16px;
}
.vp-gallery-item { margin: 0; min-width: 0; }
/* Straight corners, thin grey rule, no card shadow - the site's own
   visual language (see .vp-card / .vp-contributor-card above). */
.vp-gallery-thumb {
  display: block; width: 100%; padding: 0; margin: 0; border: 1px solid #e2e2e2;
  background: #f2f2f2; cursor: zoom-in; line-height: 0; border-radius: 0;
}
.vp-gallery-thumb:hover { border-color: #999; }
.vp-gallery-thumb:focus-visible { outline: 2px solid #7a2e2e; outline-offset: 2px; }
/* A fixed 3:2 box keeps the rows aligned whatever the originals' shapes
   are (these are scans and archive photos - portrait, panorama and
   square all appear in the same set). The uncropped image is one click
   away in the lightbox. */
.vp-gallery-thumb img {
  display: block; width: 100%; height: auto; aspect-ratio: 3 / 2; object-fit: cover; margin: 0;
}
/* Two lines max on the thumbnail so the rows stay level; the lightbox
   shows the caption in full. -webkit-box + line-clamp is still the only
   cross-browser way to do that (the standard `line-clamp` shorthand is
   not shipped widely enough to rely on alone). */
.vp-gallery-caption {
  margin-top: 7px;
  font-family: system-ui, sans-serif; font-size: 13px; line-height: 1.4; color: #777;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.vp-gallery--collapsed .vp-gallery-item--extra { display: none; }
.vp-gallery--collapsed.vp-gallery--expanded .vp-gallery-item--extra { display: block; }
.vp-gallery-more {
  display: block; margin: 16px 0 0; padding: 9px 18px;
  border: 1px solid #cfcfcf; background: #fff; border-radius: 0;
  font-family: system-ui, sans-serif; font-size: 14px; color: #333; cursor: pointer;
}
.vp-gallery-more:hover { border-color: #7a2e2e; color: #7a2e2e; }

/* ---- Lightbox ---- */
html.vp-lightbox-open { overflow: hidden; }
.vp-lightbox {
  position: fixed; inset: 0; z-index: 9000;
  background: rgba(20, 20, 20, 0.94);
  display: flex; align-items: center; justify-content: center;
  padding: 56px 16px 76px; box-sizing: border-box;
}
.vp-lightbox[hidden] { display: none; }
.vp-lightbox-figure {
  margin: 0; max-width: 100%; max-height: 100%;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px;
}
.vp-lightbox-img {
  display: block; max-width: 100%; max-height: calc(100vh - 190px);
  width: auto; height: auto; object-fit: contain; background: #111;
}
.vp-lightbox-caption {
  max-width: 760px; text-align: center;
  font-family: system-ui, sans-serif; font-size: 14.5px; line-height: 1.45; color: #ddd;
}
.vp-lightbox-count {
  position: absolute; left: 0; right: 0; bottom: 22px; text-align: center;
  font-family: system-ui, sans-serif; font-size: 13px; color: #999;
}
.vp-lightbox-close {
  position: absolute; top: 8px; right: 12px;
  border: none; background: none; color: #ddd; font-size: 44px; line-height: 1;
  padding: 0 8px; cursor: pointer;
}
.vp-lightbox-nav {
  position: absolute; top: 50%; transform: translateY(-50%);
  border: none; background: rgba(0, 0, 0, 0.35); color: #fff;
  font-size: 40px; line-height: 1; padding: 10px 16px; cursor: pointer;
}
.vp-lightbox-nav[hidden] { display: none; }
.vp-lightbox-prev { left: 6px; }
.vp-lightbox-next { right: 6px; }
.vp-lightbox-close:hover, .vp-lightbox-nav:hover { color: #fff; background: rgba(0, 0, 0, 0.6); }

@media (max-width: 780px) {
  .vp-gallery-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px; }
}
@media (max-width: 600px) {
  /* Same reasoning (and the same breakpoint) as the .vp-article-body
     reset in the mobile block above: the right margin that keeps the
     desktop column right-flush has no free white field to be flush WITH
     at this width and would push the grid past the container edge. The
     gallery deliberately mirrors that column's geometry exactly, so it
     mirrors this reset exactly too. */
  .vp-gallery { width: auto; margin: 22px 0 26px; }
  /* Two real columns at phone width (a single column wastes it, three is
     too small to see). minmax's floor must NOT exceed half the viewport
     minus the gap, or the grid overflows: at 390px the container is
     ~358px wide, so 2 x 130 + 10 = 270 fits with room to spare. */
  .vp-gallery-grid { grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); gap: 10px; }
  .vp-gallery-heading { font-size: 20px; }
  .vp-gallery-caption { font-size: 12px; }
  .vp-gallery-more { width: 100%; text-align: center; }
  .vp-lightbox { padding: 48px 8px 64px; }
  .vp-lightbox-img { max-height: calc(100vh - 170px); }
  .vp-lightbox-nav { font-size: 32px; padding: 8px 10px; }
}

/* Владелец 01.10.2026 (поворот смартфона в горизонтальное положение):
   реальный телефон, а не узкое окно десктопа или планшет - поэтому
   кроме ориентации и небольшой высоты экрана проверяем ещё и то, что у
   устройства нет мыши и указатель грубый (палец). 500px по высоте с
   запасом берёт любой телефон (самый высокий из массовых, iPhone 14
   Pro Max, в альбомной ориентации - 430px) и не задевает iPad в
   альбомной ориентации (768px высотой).
   п.2/3: номер иллюстрации и подпись скрыты - видно только по наличию
   стрелки слева/справа, первая сейчас иллюстрация или последняя (логика
   в assets/gallery.js: step()/show() перестают закольцовывать список
   в этом режиме).
   п.4: паддингов сверху/снизу не остаётся - иллюстрация любой
   ориентации упирается в верх и низ экрана, как сейчас слева/справа
   в портретном положении. */
@media (orientation: landscape) and (max-height: 500px) and (hover: none) and (pointer: coarse) {
  .vp-lightbox { padding: 0; }
  .vp-lightbox-figure { max-height: 100%; gap: 0; }
  .vp-lightbox-img { max-height: 100vh; max-height: 100dvh; }
  .vp-lightbox-caption,
  .vp-lightbox-count { display: none; }
  .vp-lightbox-close { top: 4px; right: 4px; background: rgba(0, 0, 0, 0.35); }
  .vp-lightbox-nav { font-size: 30px; padding: 8px 10px; }
}

/* Фотогалерея, экран редактора (admin/article-edit.php). */
.vp-admin .vp-gallery-admin { width: 100%; border-collapse: collapse; margin: 6px 0 14px; }
.vp-admin .vp-gallery-admin th,
.vp-admin .vp-gallery-admin td { border-bottom: 1px solid #e2e2e2; padding: 6px 8px; vertical-align: middle; text-align: left; }
.vp-admin .vp-gallery-admin th { font-size: 13px; color: #777; font-weight: 400; }
.vp-admin .vp-gallery-admin td input[type="text"] { width: 100%; margin: 0; }
.vp-admin .vp-gallery-admin td input[type="number"] { margin: 0; }

/* Фотогалерея, доводка после проверки в браузере: the default focus ring
   the lightbox's own close button gets (it is focused programmatically on
   open, so the ring always shows) reads as a solid white block against
   the dark overlay - a thin grey outline is the same affordance without
   the block. */
.vp-lightbox-close:focus-visible,
.vp-lightbox-nav:focus-visible { outline: 1px solid #999; outline-offset: 2px; }

/* ============ Галерея / Подборка, вторая версия (10.09.2026) ============
   The owner's two kinds of illustration block (vp_render_gallery_block()
   in inc/blocks.php). Appended as its own self-contained block at the end
   of the file for the same reason the first version was: every selector
   here is new and prefixed .vp-gallery-*, so it can neither collide with
   nor be collided with by the врезки (.page-block/.page-caption), the
   share row, the tag list, the footer or `.vp-article-body img` rules
   being edited elsewhere in this file at the same time.

   The first version's .vp-gallery-grid / --collapsed / -more rules above
   are left in place untouched but now match nothing: the 1+4 markup has
   no grid and no "показать все" button (the slider holds everything, and
   the count next to the title is what announces how many there are).

   Geometry, which is the whole difference between the two kinds:
   - «Галерея» is full HERO width, and reproduces the hero's own
     proportions exactly - illustration at 86.4% with the caption column
     taking the rest (see .vp-article-hero / .vp-article-caption above),
     so the opening illustration it replaces keeps its measurements.
   - «Подборка» is as wide as the TEXT column. It renders INSIDE
     .vp-article-body, which is already that column, so it just fills its
     parent - the base .vp-gallery rule's own 925px + 25px right margin
     would overflow that parent by exactly those 25px. */
.vp-gallery--gallery { width: auto; margin-left: 0; margin-right: 0; }
.vp-article-body .vp-gallery--set {
  width: 100%; margin-left: 0; margin-right: 0;
  /* врезки float beside the running text; a full-column block must start
     below them rather than beside them. */
  clear: both;
}
/* .vp-article-body img { margin: 24px 0 0 } would otherwise push the
   thumbnails apart inside a Подборка (same specificity as the first
   version's .vp-gallery-thumb img, so re-stated one class more specific
   here - deliberately not by editing that rule, which another session
   owns). */
.vp-article-body .vp-gallery img { margin: 0; }

/* ---- the 1+4 block ---- */
.vp-gallery-tile { display: flex; align-items: flex-start; gap: 24px; }
.vp-gallery-tile-main { min-width: 0; width: 100%; }
/* The large first illustration. It inherits the thumbnail's frame and its
   fixed 3:2 crop from .vp-gallery-thumb / .vp-gallery-thumb img above -
   the same 3:2 the site's featured cards use - which is what keeps the
   large image and the row of four beneath it visually one block whatever
   shape the originals are (these are scans and archive photos: portrait,
   panorama and square all turn up in one set). The uncropped original is
   one click away in the slider. */
.vp-gallery-thumb--lead { margin-bottom: 10px; }
.vp-gallery-strip {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px;
}
/* Exactly four columns, not auto-fill: a set of three illustrations must
   show two thumbnails at the SAME size as a set of thirty does, or the
   block stops reading as one recognisable component. */
.vp-gallery-item { position: relative; }
/* "+N" over the last thumbnail when the set holds more than the five
   tiles shown. pointer-events:none so the click still reaches the button
   underneath and opens the slider at that illustration. */
.vp-gallery-badge {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  background: rgba(20, 20, 20, 0.5); color: #fff; pointer-events: none;
  font-family: system-ui, sans-serif; font-size: 51px; line-height: 1;
}
/* Слайдер-only illustrations past the fifth tile: real buttons the slider
   can reach, with no image and no box (the [hidden] attribute already
   does this - restated so a stray `[hidden]{display:block}` elsewhere
   could not resurrect them as empty frames). */
.vp-gallery-thumb--rest { display: none; }

/* Same breakpoint as .vp-article-hero's own stack above, for the same
   reason: below it there is no free field to the right for a caption
   column to sit in. */
@media (max-width: 700px) {
  .vp-gallery-tile { flex-direction: column; gap: 12px; }
}
@media (max-width: 600px) {
  /* Two thumbnails per row rather than four. At 390px the container is
     ~358px, so four would be ~82px wide each - too small to tell one
     archive photo from another; two are ~175px, which reads. The block
     stays "one large + the same four", just wrapped 2x2. */
  .vp-gallery-strip { grid-template-columns: repeat(2, 1fr); gap: 8px; }
  .vp-gallery-badge { font-size: 45px; }
}

/* Галерея / Подборка, экран редактора (admin/article-edit.php). */
.vp-admin .vp-gset { border: 1px solid #ddd; padding: 12px 14px; margin: 0 0 18px; background: #fafafa; }
.vp-admin .vp-gset > h3 { margin: 0 0 10px; font-size: 16px; }
.vp-admin .vp-gset-row { display: flex; flex-wrap: wrap; gap: 14px; align-items: flex-end; }
.vp-admin .vp-gset-row > div { flex: 1 1 180px; min-width: 0; }
.vp-admin .vp-gset-row label { margin-top: 0; }
.vp-admin .vp-gset-token { font-family: ui-monospace, monospace; background: #fff; border: 1px solid #cfcfcf; padding: 3px 6px; }

/* A «Подборка» renders INSIDE .vp-article-body, where that column's own
   `h2` rule (27px, 41.6px top margin) outranks .vp-gallery-heading and
   gave the two kinds visibly different headings on the same page. One
   class+element more specific here, deliberately rather than by editing
   that rule (another session owns it), and repeated inside the phone
   breakpoint because it now outranks the 20px override there too. */
.vp-article-body h2.vp-gallery-heading { font-size: 22px; margin: 0 0 14px; }
@media (max-width: 600px) {
  .vp-article-body h2.vp-gallery-heading { font-size: 20px; }
}

/* Верхняя панель навигации админки (13.09.2026, владелец: "хедер в одну
   строку с главным верхним меню"). Full-bleed - вызывается ДО .vp-admin
   (у которого max-width:1100px), сама панель растягивается на всю
   ширину, а её внутренний .vp-admin-topbar-inner центрируется тем же
   max-width, что и остальной контент, для визуального выравнивания. */
.vp-admin-topbar { background: #3a1414; color: #f3e8e6; border-bottom: 3px solid #7a2e2e; }
/* 13.09.2026, владелец: было 1100px - уже дашборда (.vp-admin-dashboard
   сам растягивается до min(1900px,97vw), см. style.css) - несовпадение
   ширин и было причиной внутреннего скролла в шапке и обрезанной кнопки
   "Выйти". Ширина и паддинги теперь буквально те же значения, что у
   .vp-admin-dashboard, чтобы шапка и контент были вровень по краям. */
/* Владелец: "схуяль ты свернул меню на широком экране?" - шапка и
   контент страницы не обязаны буквально совпадать по ширине (это была
   разовая узкая правка только под старый растянутый дашборд); ширина
   шапки нужна САМОЙ шапке - под её собственные пункты меню без ухода в
   компактный (только иконки) режим на обычных широких экранах.
   Дашборд теперь как все страницы (1100px), а шапка - снова широкая,
   как до той правки. */
.vp-admin-topbar-inner {
	max-width: min(1900px, 97vw); margin: 0 auto; padding: 0 12px;
	display: flex; align-items: center; gap: 28px; height: 58px;
	flex-wrap: nowrap;
}
.vp-admin-brand {
	display: flex; align-items: center; gap: 9px; color: #fff; text-decoration: none;
	font-weight: 700; font-size: 17px; white-space: nowrap; flex-shrink: 0;
}
.vp-admin-brand .vp-admin-icon { color: #e0a898; width: 22px; height: 22px; }
.vp-admin-nav { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }
/* Владелец (14.09.2026): "в середине показывай имя пользователя" - меню
   (.vp-admin-nav) перестало растягиваться на всё доступное место (это и
   было причиной пустоты посередине), вместо этого растягивается сам блок
   с именем - текст по центру этого блока, так имя оказывается в
   реальной середине оставшегося пространства. */
.vp-admin-topbar-user {
	display: flex; align-items: center; justify-content: center; gap: 6px;
	flex: 1 1 auto; color: #e0c9a6; text-decoration: none; font-size: 14px;
	padding: 8px 12px; border-radius: 6px; transition: background .15s ease;
}
.vp-admin-topbar-user:hover { background: rgba(255,255,255,.08); color: #fff; }
.vp-admin-topbar-user .vp-admin-icon { width: 16px; height: 16px; }
.vp-admin-nav a {
	display: flex; align-items: center; gap: 7px; padding: 10px 14px; border-radius: 7px;
	color: #d8c3bf; text-decoration: none; font-size: 15px; white-space: nowrap;
}
.vp-admin-nav a:hover { background: rgba(255,255,255,0.08); color: #fff; }
.vp-admin-nav a.active { background: #7a2e2e; color: #fff; }
.vp-admin-topbar-actions { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }
.vp-admin-topbar-actions a {
	display: flex; align-items: center; gap: 7px; padding: 10px 14px; border-radius: 7px;
	color: #d8c3bf; text-decoration: none; font-size: 15px; white-space: nowrap;
}
.vp-admin-topbar-actions a:hover { background: rgba(255,255,255,0.08); color: #fff; }
.vp-admin-icon { flex-shrink: 0; width: 19px; height: 19px; }

/* Владелец (13.09.2026): "логотип можно убрать, тексты можно спрятать,
   но меню должно всё влезать" - раньше это решалось пиксельным
   брейкпоинтом (@media max-width:860px), который устарел, как только
   добавился пункт "Корзина" (измерено: при 1400px нужно 1522px, есть
   1358px - меню уже перехлёстывало на самой обычной десктопной ширине,
   870px тут ни при чём). Теперь JS (см. vp_admin_topbar()) измеряет
   РЕАЛЬНЫЙ overflow (scrollWidth > clientWidth) и включает эти классы
   по очереди - не зависит от того, сколько пунктов меню будет в будущем. */
.vp-topbar-compact .vp-admin-nav a span,
.vp-topbar-compact .vp-admin-nav-dropdown-trigger span,
.vp-topbar-compact .vp-admin-topbar-user span,
.vp-topbar-compact .vp-admin-topbar-actions a span { display: none; }
.vp-topbar-compact-2 .vp-admin-brand span { display: none; }

/* Форма входа (13.09.2026, владелец: "сделай симпатичной и ровной") -
   раньше сидела прижатой в левый верхний угол без центрирования, теперь
   настоящая карточка по центру экрана. body уже display:flex;flex-
   direction:column (общее правило для публичного сайта, но действует и
   тут) - .vp-login-page просто занимает оставшееся место и центрирует
   карточку внутри себя, без отдельного 100vh-расчёта поверх этого. */
.vp-login-page { flex: 1; display: flex; align-items: center; justify-content: center; background: #ece6e4; padding: 24px; }
.vp-login-card {
	background: #fff; border-radius: 12px; box-shadow: 0 4px 28px rgba(58,20,20,0.14);
	padding: 40px 36px; width: 100%; max-width: 360px;
}
.vp-login-brand { display: flex; align-items: center; gap: 8px; color: #7a2e2e; font-weight: 700; font-size: 15px; margin-bottom: 20px; }
.vp-login-brand .vp-admin-icon { width: 22px; height: 22px; }
.vp-login-card h1 { margin: 0 0 18px; }
.vp-login-error { color: #a13a2e; background: #fdeceb; border: 1px solid #f3c8c4; border-radius: 6px; padding: 8px 12px; font-size: 13px; margin: 0 0 14px; }
.vp-login-card form { margin: 0; }
.vp-login-card button { width: 100%; margin-top: 10px; padding: 11px 16px; font-size: 15px; }

/* Дашборд как настоящий дашборд (13.09.2026, владелец: "причеши, чтоб
   на дашборд была похожа") - сводка чисел сверху + последние статьи,
   а не просто список ссылок на разделы админки. */
.vp-dashboard-stats {
	display: grid; grid-template-columns: repeat(6, 1fr); gap: 12px;
	margin: 20px 0 24px;
}
.vp-stat-tile {
	display: flex; flex-direction: column; align-items: center; gap: 6px;
	background: #fff; border: 1px solid #e2dcda; border-radius: 10px;
	padding: 16px 10px; text-decoration: none; color: #3a1414;
	transition: border-color .15s, box-shadow .15s;
}
.vp-stat-tile:hover { border-color: #7a2e2e; box-shadow: 0 2px 10px rgba(58,20,20,0.08); }
.vp-stat-tile .vp-admin-icon { color: #7a2e2e; width: 22px; height: 22px; }
.vp-stat-num { font-size: 26px; font-weight: 700; line-height: 1; }
/* 15.09.2026: "Точки на карте" (было "Точек карты") длиннее и теперь
   переносится на 2 строки - flex align-items:center на .vp-stat-tile
   центрирует сам span как единый блок, но НЕ центрирует текст ВНУТРИ
   него построчно - без явного text-align вторая строка читалась влево.
   Число сверху выглядело центрированным только потому, что оно всегда
   было одной короткой строкой, где разницы не видно. */
.vp-stat-label { font-size: 12.5px; color: #777; text-align: center; }

/* 14.09.2026: дашборд-виджеты (последние статьи/изменения/популярное)
   теперь стоят в ряд по три, а не друг под другом - .vp-dashboard-row
   переиспользует ту же .vp-dashboard-recent карточку, только в узкой
   колонке, поэтому вложенный .vp-recent-list внутри ряда - в один
   столбец (2 колонки были рассчитаны на полную ширину). */
/* 14.09.2026 (владелец, зло: "чего постоянно лесенки рисуешь" -
   "запиши, что всё проверять на ровность по высоте и ширине") - тремя
   виджетами в ряд управляет их РЕАЛЬНОЕ число строк (6/4/6), значит
   карточки сами по себе неизбежно разной высоты - предыдущий фикс
   (одинаковый LIMIT) решал только "разное число строк из-за разных
   LIMIT", но не "разное число строк из-за разного реального контента".
   Вместо того чтобы гнаться за одинаковым числом строк (недостижимо -
   у "Последних изменений" реально может быть меньше 6 записей),
   растягиваем сами карточки (фон+рамка) на одинаковую высоту:
   align-items:stretch на ряду + flex-column на каждой карточке + flex:1
   на списке внутри - короткий список просто оставляет пустое место
   ВНУТРИ своей же карточки, а не даёт ей закончиться раньше соседних. */
/* Дашборд, 14.09.2026: график "Просмотры страниц - 30 дней" (см.
   vp_render_pageviews_chart() в inc/admin-layout.php). Своя карточка -
   тот же визуальный язык (белый фон, .vp-recent-list border/radius),
   но не .vp-dashboard-row (это full-width один элемент, не колонка). */
.vp-dashboard-chart { margin: 0 0 28px; }
.vp-dashboard-chart h2 { margin: 0 0 10px; font-size: 16px; }
.vp-chart-wrap {
	position: relative; background: #fff; border: 1px solid #e2dcda; border-radius: 10px;
	padding: 12px 12px 4px;
}
.vp-chart-svg { display: block; width: 100%; height: 220px; }
.vp-chart-grid { stroke: #eee; stroke-width: 1; }
.vp-chart-grid--axis { stroke: #e2dcda; }
.vp-chart-axis-label { font-size: 9px; fill: #999; font-family: system-ui, sans-serif; }
.vp-chart-crosshair { stroke: #ccc; stroke-width: 1; pointer-events: none; }
.vp-chart-dot { pointer-events: none; }
.vp-chart-hit { cursor: crosshair; }
.vp-chart-tooltip {
	position: absolute; pointer-events: none; background: #fff; border: 1px solid #e2dcda;
	border-radius: 6px; padding: 6px 10px; box-shadow: 0 4px 14px rgba(0,0,0,0.1);
	font-size: 12.5px; line-height: 1.4; white-space: nowrap; z-index: 5;
}
.vp-chart-tooltip-date { color: #999; }
.vp-chart-tooltip-val { color: #1a1a1a; font-weight: 700; }
.vp-chart-table-toggle { margin-top: 6px; }
.vp-chart-table-toggle summary { cursor: pointer; font-size: 13px; color: #7a2e2e; padding: 6px 2px; }
.vp-chart-table-scroll { max-height: 220px; overflow-y: auto; margin-top: 6px; }
.vp-chart-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.vp-chart-table th, .vp-chart-table td { text-align: left; padding: 5px 10px; border-bottom: 1px solid #f0ece9; }
.vp-chart-table th { color: #999; font-weight: 600; }

.vp-device-bar-svg { display: block; width: 100%; height: 32px; }
.vp-device-legend {
	list-style: none; margin: 12px 0 0; padding: 0;
	display: flex; flex-wrap: wrap; gap: 8px 20px;
	font-size: 13px; color: #1a1a1a;
}
.vp-device-legend li { display: flex; align-items: center; gap: 7px; }
.vp-device-swatch { width: 11px; height: 11px; border-radius: 3px; flex-shrink: 0; }
.vp-device-pct { color: #999; }

/* Настройки -> Логи (14.09.2026): простая таблица без бесконечной
   подгрузки/поиска - тот же принцип, что уже применён к Корзине
   (admin/trash.php): это плоский, в основном read-only, список
   событий, а не редактируемая сущность с CRUD - не тот кейс, для
   которого строилась vpAdminInfiniteList. */
.vp-log-table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.vp-log-table th, .vp-log-table td { text-align: left; padding: 8px 12px; border-bottom: 1px solid #f0ece9; vertical-align: top; }
.vp-log-table th { color: #999; font-weight: 600; font-size: 12.5px; }
.vp-log-uri { color: #999; font-size: 12px; margin-top: 3px; }
.vp-log-file { color: #777; white-space: nowrap; font-family: ui-monospace, monospace; font-size: 12px; }
.vp-log-level { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 11.5px; font-weight: 600; text-transform: uppercase; white-space: nowrap; }
.vp-log-level--warning { background: #fff4e5; color: #a1651a; }
.vp-log-level--error { background: #fdeceb; color: #a13a2e; }
.vp-log-level--exception { background: #fbe0dd; color: #8a231a; }
.vp-log-level--fatal { background: #a13a2e; color: #fff; }

/* Владелец (15.09.2026): "увеличенные маркеры-кружки с цифрами" - вторым
   фирменным цветом сайта (#2eaadc) - тут же, для OSM/Leaflet-резервного
   провайдера карты (сейчас не активен - выбран Yandex, см. map.php - но
   тот же принцип "не чинить только видимый сейчас случай", что и везде в
   этом проекте). Переопределяет цвета по умолчанию из вендорского
   MarkerCluster.Default.css (жёлто-оранжевые), который грузится раньше
   этого файла - обычный каскад побеждает без доп. specificity. */
.marker-cluster-small, .marker-cluster-medium, .marker-cluster-large {
	background-color: rgba(46, 170, 220, 0.4);
}
.marker-cluster-small div, .marker-cluster-medium div, .marker-cluster-large div {
	background-color: rgba(46, 170, 220, 0.75);
	color: #fff;
}

.vp-dashboard-row {
	display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 20px;
	align-items: stretch;
}
.vp-dashboard-recent { margin: 0 0 28px; }
.vp-dashboard-row .vp-dashboard-recent { display: flex; flex-direction: column; }
.vp-dashboard-recent h2 { margin: 0 0 10px; font-size: 16px; }
.vp-recent-list {
	background: #fff; border: 1px solid #e2dcda; border-radius: 10px; overflow: hidden;
	display: grid; grid-template-columns: repeat(2, 1fr);
}
.vp-dashboard-row .vp-recent-list { grid-template-columns: 1fr; flex: 1; align-content: start; }
.vp-dashboard-row .vp-recent-row:nth-child(odd) { border-right: none; }
.vp-dashboard-empty { padding: 14px 16px; margin: 0; font-size: 13.5px; color: #999; }
/* 14.09.2026 (владелец: "чего опять за край всё вылазит"): классическая
   ловушка min-width:auto - .vp-recent-row (grid-item в .vp-recent-list)
   и .vp-recent-title (flex-item внутри .vp-recent-row) по умолчанию
   отказываются сжиматься уже своего контента, так что text-overflow:
   ellipsis ниже никогда не срабатывал - просто не было заметно, пока
   колонка "Последние статьи" занимала всю ширину дашборда (там же и
   родился баг: узкие 1/3-колонки .vp-dashboard-row оголили его - длинные
   заголовки/URL в "Популярное" реально выезжали за правый край экрана).
   min-width:0 на обоих уровнях - стандартное лекарство. */
.vp-recent-row {
	display: flex; align-items: center; justify-content: space-between; gap: 12px;
	padding: 12px 16px; text-decoration: none; color: #1a1a1a;
	border-bottom: 1px solid #f0ece9;
	min-width: 0;
}
.vp-recent-row:nth-child(odd) { border-right: 1px solid #f0ece9; }
.vp-recent-row:hover { background: #faf6f5; }
.vp-recent-title { font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.vp-recent-date { font-size: 12.5px; color: #999; flex-shrink: 0; }

/* Иконки в заголовках карточек-инструментов - та же щепотка, что в
   топбаре, для единого визуального языка. */
.vp-admin-card h2 { display: flex; align-items: center; gap: 8px; }
.vp-admin-card h2 .vp-admin-icon { color: #7a2e2e; flex-shrink: 0; }
.vp-admin-back-link { display: inline-flex; vertical-align: middle; margin-right: 6px; color: #7a2e2e; }
.vp-admin-back-link:hover { opacity: .6; }

@media (max-width: 900px) {
	.vp-dashboard-stats { grid-template-columns: repeat(3, 1fr); }
	.vp-recent-list { grid-template-columns: 1fr; }
	.vp-recent-row:nth-child(odd) { border-right: none; }
	.vp-dashboard-row { grid-template-columns: 1fr; }
}

/* Владелец (14.09.2026): "Списки" - новый выпадающий пункт меню
   (Статьи/Медиа/Авторы/Карта/Лавка/Калейдоскоп переехали внутрь него). */
.vp-admin-nav-dropdown { position: relative; display: inline-flex; }
.vp-admin-nav-dropdown-trigger {
	display: flex; align-items: center; gap: 6px; background: none; border: none;
	font: inherit; color: inherit; cursor: pointer; padding: inherit;
}
.vp-admin-nav-dropdown-trigger .vp-admin-icon:last-child { width: 14px; height: 14px; margin-left: -2px; }
.vp-admin-nav-dropdown.active > .vp-admin-nav-dropdown-trigger,
.vp-admin-nav-dropdown-trigger:hover { color: inherit; }
.vp-admin-nav-dropdown-menu {
	display: none; position: absolute; top: 100%; left: 0; margin-top: 8px;
	background: #3a1414; border: 1px solid rgba(255,255,255,.12); border-radius: 8px;
	min-width: 180px; padding: 6px; flex-direction: column; gap: 2px;
	box-shadow: 0 8px 24px rgba(0,0,0,.35); z-index: 60;
}
.vp-admin-nav-dropdown--open .vp-admin-nav-dropdown-menu { display: flex; }
.vp-admin-nav-dropdown-menu a { padding: 8px 10px; border-radius: 6px; white-space: nowrap; }
.vp-admin-nav-dropdown-menu a:hover { background: rgba(255,255,255,.08); }

/* Владелец (14.09.2026): "на высоких страницах кнопочку прокрутить в
   самый верх" - общая для всех admin-страниц (рендерится в
   vp_admin_topbar()), справа внизу, появляется только после прокрутки. */
.vp-scroll-top-btn {
	position: fixed; right: 24px; bottom: 24px; z-index: 55;
	width: 44px; height: 44px; border-radius: 50%;
	background: #3a1414; border: 1px solid rgba(255,255,255,.15);
	color: #e0c9a6; display: flex; align-items: center; justify-content: center;
	cursor: pointer; box-shadow: 0 4px 16px rgba(0,0,0,.3);
	opacity: 0; pointer-events: none; transform: translateY(8px);
	transition: opacity .2s ease, transform .2s ease, background .15s ease;
}
.vp-scroll-top-btn--visible { opacity: 1; pointer-events: auto; transform: translateY(0); }
.vp-scroll-top-btn:hover { background: #7a2e2e; color: #fff; }
.vp-scroll-top-btn .vp-admin-icon { width: 20px; height: 20px; }


/* Владелец 27.09.2026: страница «Архив» - подменю (Карты/Книги/Прочее)
   вместо продублированного заголовка раздела (nav уже говорит «Архив»,
   второй h1 с тем же текстом ниже был лишним) + сетка представлений
   доступных для скачивания артефактов. Подменю визуально повторяет
   .vp-tabs (admin/settings.php), но это КНОПКИ с client-side фильтром,
   не ссылки со сменой страницы - список маленький, серверный round-trip
   не нужен. */
/* Владелец 27.09.2026: подменю строится по образцу главного меню в
   хэдере (см. .vp-nav a) - тот же шрифт/кегль/начертание, только СВОИ
   состояния (пассивное - подчёркнуто всегда, активное - второй цвет
   хэдера #2eaadc, подчёркивание сохраняется, как у .vp-nav a.active).
   3 колонки: десктоп - по центру (grid + justify-content:center на
   max-content колонках), мобильная - repeat(3,1fr) выключка влево, тот
   же паттерн, что уже есть у .vp-nav в мобильной версии. */
.vp-archive-tabs {
  display: grid; grid-template-columns: repeat(3, max-content); justify-content: center;
  gap: 6.65px 29.925px; margin: 0 0 28px;
}
.vp-archive-tab {
  padding: 0; background: none; border: none; cursor: pointer; font: inherit;
  font-family: 'LatoBold', 'OpenSansBold', 'Open Sans Bold', sans-serif; font-size: 14px; font-weight: 700;
  letter-spacing: -0.1px; text-transform: uppercase; text-decoration: underline; text-underline-offset: 4px;
  color: #919191;
}
.vp-archive-tab:hover { color: #2eaadc; }
.vp-archive-tab.vp-tab-active { color: #2eaadc; }
@media (max-width: 600px) {
  .vp-archive-tabs { grid-template-columns: repeat(3, 1fr); justify-content: normal; gap: 10px 8px; }
  .vp-archive-tab { font-size: 13px; }
}

.vp-archive-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; }
@media (max-width: 600px) {
  .vp-archive-grid { grid-template-columns: 1fr; }
}

/* "в высоту как иллюстрации на стандартных карточках-анонсах" - тот же
   .vp-card-img (section.php/index.php) сейчас рендерится ровно 150px
   высотой в 3-колоночной сетке при ширине контента 1100px - фиксируем то
   же число здесь, а не изобретаем своё. */
.vp-archive-item {
  display: flex; align-items: stretch; height: 150px;
  background: #fff; border: 1px solid #eee;
}
.vp-archive-item:hover { border-color: #d9d9d9; }
/* Владелец 06.10.2026 (слияние Архива и Лавки в Киоск): раньше вся
   карточка была одной <a> (во всю площадь, скачивание по клику в любом
   месте) - кнопка "Приобрести" из бывшей Лавки не может лежать ВНУТРИ
   <a> (невалидный HTML, button внутри a). Ссылка-скачивание теперь
   отдельный флекс-элемент (вся прежняя область, кроме кнопки), сама
   кнопка - второй флекс-элемент той же строки. */
.vp-archive-item-link {
  display: flex; align-items: stretch; flex: 1; min-width: 0; text-decoration: none; color: inherit;
}
.vp-archive-buy {
  align-self: center; flex-shrink: 0; margin: 0 14px 0 10px; white-space: nowrap;
}
/* Владелец 27.09.2026: превью НЕ вплотную к левому краю карточки -
   14px отступ, та же величина, что уже была у текста сверху/снизу
   (.vp-archive-body's padding:14px) - один общий отступ-константа. */
.vp-archive-thumb {
  flex-shrink: 0; width: 100px; height: 150px; object-fit: cover; display: block; margin-left: 14px;
}
.vp-archive-thumb--placeholder { background: linear-gradient(135deg, #f1e9e4, #e6d9cd); }
/* Владелец 27.09.2026: текст выравнивается по верхнему краю (было по
   центру) - см. .vp-archive-download ниже для нижнего края. */
.vp-archive-body {
  flex: 1; min-width: 0; display: flex; flex-direction: column; justify-content: flex-start;
  gap: 6px; padding: 14px 18px;
}
.vp-archive-title {
  font-family: 'Bitter', Georgia, "Times New Roman", serif; font-size: 17px; font-weight: 700; margin: 0;
  color: #1a1a1a; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.vp-archive-meta { margin: 0; font-family: system-ui, sans-serif; font-size: 13px; color: #777; }
.vp-archive-meta .vp-archive-author::before { content: " · "; }
.vp-archive-download {
  font-family: 'Lato', sans-serif; font-size: 12px; font-weight: 600; color: #7a2e2e;
  text-transform: uppercase; letter-spacing: 0.03em; margin-top: auto;
}
.vp-archive-empty { color: #777; }

@media (max-width: 600px) {
  .vp-archive-item { height: 120px; }
  .vp-archive-thumb { width: 80px; height: 120px; }
  .vp-archive-title { font-size: 15px; }
  .vp-archive-buy { font-size: 11px; padding: 5px 8px; margin: 0 8px 0 4px; }
}

/* Владелец 28.09.2026 ("сохранение в редакторе не должно менять вид
   страницы"): правила для round-trip старой (не-Quill) разметки через
   редактор - см. vpInstallLegacyMatchers() в admin/article-edit.php.
   Каждое число ниже - не подобрано, а замерено в браузере на реальной
   вёрстке: конструкция "как в старой разметке" против конструкции "как
   её представляет Quill" должна давать одну и ту же высоту. */

/* Базовый межабзацный интервал - раньше жил на браузерном дефолте (1em),
   а в редакторе его перебивало quill.snow (.ql-editor p {margin:0}) -
   явное правило одной специфичности с ним, чтобы редактор показывал
   те же интервалы, что и сайт. */
.vp-article-body p { margin-top: 16px; margin-bottom: 16px; }

/* padding-left:30px старой разметки -> формат indent Quill (30px за уровень;
   quill.snow ставит 3em, поэтому в редакторе то же число закреплено ещё и
   через #vp-quill-ru, см. article-edit.php). */
.vp-article-body .ql-indent-1 { padding-left: 30px; }
.vp-article-body .ql-indent-2 { padding-left: 60px; }

/* <br> внутри выноса/подзаголовка/пункта списка: Quill обязан разрезать
   их на отдельные блоки - "мягкий перенос" (nogap) и на них должен
   выглядеть как перенос строки, а не как новый блок. */
.vp-article-body h2.vp-p-nogap-true,
.vp-article-body h3.vp-p-nogap-true,
.vp-article-body h4.vp-p-nogap-true { margin-top: 0; }
.vp-article-body blockquote.vp-p-nogap-true { margin-top: 0; padding-top: 0; }
.vp-article-body blockquote:has(+ blockquote.vp-p-nogap-true) { margin-bottom: 0; padding-bottom: 0; }
.vp-article-body li.vp-p-nogap-true { list-style: none; }

/* Абзац с картинкой имеет внешние отступы 24px (p:has(img) выше). Когда
   старый абзац "картинка<br>подпись" режется на блоки, строки после
   картинки остаются его частью визуально: последняя из них несёт его
   нижний отступ (24, а не 16), а строка ПЕРЕД картинкой - верхний. */
/* Строки такого абзаца помечены редактором классом vp-p-imgrun-true
   (сколько бы их ни было - цепочка соседних селекторов не нужна):
   первая (не nogap) несёт верхний отступ, последняя (за ней не nogap) -
   нижний. */
.vp-article-body p.vp-p-imgrun-true:not(.vp-p-nogap-true) { margin-top: 24px; }
.vp-article-body p.vp-p-imgrun-true:not(:has(+ p.vp-p-nogap-true)) { margin-bottom: 24px; }

/* Заголовки внутри текста статьи, у которых нет своих правил у сайта
   (h1/h4/h5/h6 - в отличие от h2/h3 выше): при просмотре их отступы брались
   из значений браузера по умолчанию, а Quill в редакторе обнуляет отступы у
   ВСЕХ заголовков (.ql-editor h1..h6 { margin: 0 }) - из-за этого блок после
   <h4> в редакторе стоял на 5-11px выше, чем на странице (замерено на статье
   "Взморье (Гросс Хайдекруг)", подзаголовок "Справочно"). Задаём те же
   величины явно - на странице ничего не меняется (значения ровно те же,
   0.67/1.33/1.67/2.33em), а редактор перестаёт расходиться. Правило для
   .vp-p-nogap-true выше специфичнее, поэтому мягкий перенос по-прежнему
   убирает верхний отступ. */
.vp-article-body h1 { margin-top: 0.67em; margin-bottom: 0.67em; }
.vp-article-body h4 { margin-top: 1.33em; margin-bottom: 1.33em; }
.vp-article-body h5 { margin-top: 1.67em; margin-bottom: 1.67em; }
.vp-article-body h6 { margin-top: 2.33em; margin-bottom: 2.33em; }

/* Кегль основного текста, владелец 29.09.2026: три размера вместо одного.
   Обычный - 16px (без класса), мелкий - 14px (как подпись к иллюстрации,
   "на 2 пункта меньше основного"), крупный - 19px (кегль выноса). Класс
   ставится на сам абзац из админки (формат "Кегль"), межстрочное
   расстояние пересчитывается само: line-height у .vp-article-body задан
   множителем 1.5. На старом сайте мелкий набирался инлайновым
   style="font-size: small" - при переносе контента такие вставки не
   сохранились, заново их размечают здесь.
   Владелец 09.10.2026: кнопка "Аа" (html[data-fontsize="large"/"xlarge"]
   ниже) увеличивает ТОЛЬКО обычный текст - абсолютные px здесь не
   участвовали в масштабировании (абсолютное значение всегда побеждает
   унаследованный множитель, никакой em/множитель его не трогает).
   Переведено в em от обычного (16px - база, от которой .vp-main и
   отсчитывает свой 1.1/1.22 множитель): 14/16=0.875em, 19/16=1.1875em -
   то же визуальное отношение к обычному тексту при стандартном размере,
   но теперь пропорционально растёт вместе с ним. */
.vp-article-body .vp-text-small { font-size: 0.875em; }
.vp-article-body .vp-text-large { font-size: 1.1875em; }

/* Владелец 04.10.2026: страница 404 - иллюстрация вместо голого абзаца
   текста. Владелец 06.10.2026: убрал отдельный серый заголовок
   (.vp-section-title) - теперь одно сообщение крупным засечным шрифтом
   (Bitter, как в основном тексте статей), ссылка "На главную" ниже. */
.vp-404 { max-width: 620px; margin: 40px auto 64px; text-align: center; }
/* Владелец 10.10.2026: рамка SVG обрезана ровно по зданию (центр здания =
   центр страницы / строки "На главную"); рисунок стал уже, 320px * 391/482
   = 260px - здание того же размера, что и раньше. */
.vp-404-illustration { display: block; width: 100%; max-width: 260px; height: auto; margin: 0 auto 24px; }
/* Владелец 10.10.2026: акцент на первой строке - полужирная (Bitter Bold),
   "На главную" - без выделения. */
.vp-404-message { font-family: 'Bitter', Georgia, "Times New Roman", serif; font-size: 24px; line-height: 1.4; color: #1a1a1a; margin: 0 0 20px; font-weight: 700; }
.vp-404 p { font-size: 17px; color: #1a1a1a; }
.vp-404 a { color: #7a2e2e; font-weight: 400; }
.vp-404 a:hover { color: #2eaadc; }

/* Владелец 06.10.2026: кнопка "Посмотреть на сайте" в редакторе статьи -
   обычная DOM-кнопка (не формат Quill, см. комментарий у vpInitPreviewUI в
   admin/article-edit.php), добавлена последним ребёнком прямо в
   .ql-toolbar - float вправо, чтобы остаться в той же строке. Цвета - под
   стиль остальных кнопок тулбара Quill (serый/#444, синий hover/#06c). */
.ql-toolbar.ql-snow .vp-quill-preview-btn {
	float: right;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 28px;
	height: 24px;
	margin: 2px 2px 2px 8px;
	padding: 0;
	border: 1px solid #ccc;
	border-radius: 3px;
	background: #fff;
	color: #444;
	cursor: pointer;
}
.ql-toolbar.ql-snow .vp-quill-preview-btn:hover {
	color: #06c;
	border-color: #06c;
}

/* Владелец 08.10.2026: "в мобильной версии на страницах типа Публикация
   в самом нижнем блоке Читайте также все шесть карточек примыкают
   справа к краю экрана" - .vp-related-articles (2281) красит фон
   edge-to-edge через классический приём width:100vw +
   margin:calc(50% - 50vw) - приём в принципе рабочий на десктопе, но
   `vw` в мобильных браузерах не всегда равен РЕАЛЬНОЙ видимой ширине
   экрана (известная расхлёстка `100vw` vs `document.documentElement.
   clientWidth` из-за резерва под scrollbar-gutter, который движок
   учитывает в vw, даже когда скроллбар реально не рисуется на
   touch-устройстве) - подтверждено живьём: innerWidth=390,
   но clientWidth=375, 15px разницы ровно и объясняют сдвиг/обрезание.
   На мобильном это не нужно: .vp-main (родитель) и так уже ровно
   шириной в экран (не центрируется в более широком контейнере, как на
   десктопе) - значит "выйти на edge-to-edge" равно просто "скомпенсировать
   padding родителя" (24px, см. .vp-main), без каких-либо vw вообще. */
@media (max-width: 600px) {
  .vp-related-articles {
    width: auto;
    margin-left: -24px;
    margin-right: -24px;
  }
}
/* 08.10.2026: та же латентная мобильная проблема, что нашлась у
   .vp-related-articles (см. комментарий там) - .vp-load-more
   использует тот же width:100vw/calc(50% - 50vw) приём, и страдает тем
   же 15px расхождением vw/clientWidth. Визуально почти незаметно (сам
   .vp-load-more не имеет внутренней -inner обёртки с max-width+auto, в
   отличие от .vp-related-articles, поэтому переполнение почти
   симметрично по ~7-8px с обеих сторон и прячется под body's
   overflow-x:hidden) - но раз причина та же и фикс тот же, чиним
   заодно, не дожидаясь отдельной жалобы. */
@media (max-width: 600px) {
  .vp-load-more {
    width: auto;
    margin-left: -24px;
    margin-right: -24px;
  }
}
/* ===== Владелец 20.10.2026: порция правок ===== */
/* Публикация без лида: место под главной иллюстрацией не пустует - верхний
   отступ первого элемента текста (обычно подзаголовок, 48px) обнуляется,
   остаётся только отступ под иллюстрацией, как за обычной иллюстрацией. */
.vp-article-body--nolead > :first-child { margin-top: 0; }
/* Плашки дат на карточках-анонсах: на 10% светлее (#000 -> #1a1a1a) и на 10%
   прозрачнее (0.6 -> 0.54) - даты не главное в анонсе. 20.10 (следом): ещё на
   15% светлее (#1a1a1a -> #404040). */
.vp-card-date-badge { background: rgba(64,64,64,0.54); }
/* Счётчик в футере мобильной версии: цифры на 20% крупнее (40 -> 48px). */
@media (max-width: 860px) {
  .vp-footer-days { font-size: 48px; }
}
/* Выбор языка: при наведении на пункт списка квадратик становится
   треугольником PLAY (цвет тот же, серый). */
.vp-lang-menu a:hover .vp-lang-mark { width: .82em; height: .7em; clip-path: polygon(0 0, 100% 50%, 0 100%); }
/* Пункт меню «Карта» выделен, как «Сувениры», но одним тёмным цветом - 75% чёрного
   (владелец 20.10: было 90% - «перечернили»). */
.vp-nav a.vp-nav-map { color: #919191; font-family: "Arial Black", "Arial", sans-serif; font-weight: 900; font-size: 16px; line-height: 20px; }
.vp-nav a.vp-nav-map:hover, .vp-nav a.vp-nav-map.active { color: #2eaadc; }
@media (max-width: 600px) { .vp-nav a.vp-nav-map { font-size: 15px; line-height: 18px; } }
.vp-nav { align-items: baseline; } /* владелец 20.10: «Карта» крупнее остальных, но низ букв на одной линии */
/* На Главной «Аа» ни на что не влияет - скрыта; «Наверх» остаётся. */
body:not(.vp-has-aa) .vp-aa-float { display: none; } /* владелец 20.10 вечер: «Аа» только на «Публикациях» (article.php) */
/* Владелец 21.10.2026: «строчки активировались бы под курсором - очень
   далеко друг от друга название позиции и кнопка Редактировать». Строка
   любого спискового контрола (vp_admin_list_row: tr[data-id]) подсвечивается
   целиком под курсором - глаз ведёт от названия к кнопкам по полосе. */
.vp-admin table tr[data-id] > td { transition: background-color .08s; }
.vp-admin table tr[data-id]:hover > td { background: #efe6dc; }

/* RBAC 21.10.2026: роль рядом с именем в шапке, полоса-подсказка для
   Guest / Section editor, Пользователи, Личная страница, Гостевая книга. */
.vp-admin-role-badge { display: inline-block; margin-left: 4px; padding: 1px 6px; border-radius: 3px; font-size: 11px; font-weight: 600; background: rgba(255,255,255,.14); color: inherit; vertical-align: 1px; }
.vp-admin .vp-admin-role-badge { background: #efe6dc; color: #7a2e2e; }
.vp-admin-role-strip { background: #f6efe6; border-bottom: 1px solid #e2d9cc; color: #5a3a2a; font: 13px/1.4 system-ui, sans-serif; padding: 7px 16px; text-align: center; }
.vp-admin-role-strip a { color: #7a2e2e; }
.vp-admin-narrow { max-width: 640px; }
.vp-admin .vp-admin-notice { background: #edf6ee; border: 1px solid #b9dcbc; color: #2e5d32; padding: 8px 12px; border-radius: 4px; }
.vp-admin input[type=tel], .vp-admin input[type=email] { width: 100%; padding: 8px 10px; border: 1px solid #ccc; border-radius: 4px; font: inherit; font-size: 14px; box-sizing: border-box; }
.vp-user-form { margin: 0 0 28px; padding: 4px 20px 12px; border: 1px solid #e2d9cc; border-radius: 6px; background: #fcfaf7; }
.vp-user-sections { margin: 14px 0 0; padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; }
.vp-user-sections legend { font-size: 13px; font-weight: 600; color: #444; padding: 0 4px; }
.vp-admin .vp-user-sections label { display: inline-flex; align-items: center; gap: 6px; margin: 4px 16px 4px 0; font-weight: normal; }
.vp-admin .vp-user-sections input { width: auto; margin: 0; }
.vp-admin table tr.vp-user-inactive > td { color: #999; }
.vp-guestbook-form { margin-bottom: 24px; }
.vp-guestbook-entry { border-top: 1px solid #eee; padding: 12px 0; }
.vp-guestbook-meta { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; font-size: 13px; color: #666; }
.vp-guestbook-meta br { display: none; }
.vp-guestbook-body { margin-top: 6px; white-space: normal; line-height: 1.5; }

/* Владелец 21.10.2026: деактивированные статьи (hidden_at) - плашка на
   самой статье (видна только вошедшим в админку) и приглушённая строка в
   списке статей; 4 кнопки действий в строке статьи. */
.vp-hidden-article-notice { max-width: 1100px; margin: 16px auto 0; padding: 10px 16px; box-sizing: border-box; background: #fff4e5; border: 1px solid #f0c48a; border-radius: 4px; color: #7a4a10; font: 14px/1.4 system-ui, sans-serif; }
.vp-admin table tr.vp-row-hidden > td { color: #999; }
#vp-articles-table th:last-child, #vp-articles-table td:last-child { width: 170px; }

/* Владелец 21.10.2026: отключение отдельных переводов статьи (редактор статьи, раздел «Переводы») */
.vp-lang-off { margin: 0 0 16px; padding: 10px 14px; background: #f6f1ea; border: 1px solid #e2d6c6; }
.vp-lang-off__title { font-weight: bold; margin-right: 12px; }
.vp-lang-off label { display: inline-flex; align-items: center; gap: 6px; margin: 0 16px 0 0; font-weight: normal; }
.vp-lang-off input[type="checkbox"] { width: auto; margin: 0; }
.vp-lang-off__hint { margin: 8px 0 0; font-size: 13px; color: #555; }

/* Владелец 22.10.2026: в мобильной версии футера копирайт и строчки под
   счётчиком на 2 кегля крупнее (11 -> 13px). */
@media (max-width: 780px) {
  .vp-footer-autograph { font-size: 13px; }
  .vp-footer-days-label { font-size: 13px; }
}
