/* Оформление всей системы.

   Раньше эти правила лежали блоком <style> внутри base.html и уезжали в
   браузер заново на КАЖДОЙ странице: 110 КБ на каждый переход между
   разделами. На «Сводке» это было 90% веса страницы, на «Карте» — 87%.
   Браузер не мог переиспользовать стили с прошлой страницы, потому что
   они были частью изменчивого HTML.

   Теперь это отдельный файл. Ссылка на него несёт метку времени (см.
   ``asset`` в app/api/deps.py), а /static отдаётся с годовым
   Cache-Control immutable: после первой загрузки стили не ездят вовсе, а
   правка доезжает мгновенно — у изменённого файла другой адрес.

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

/* Liquid Glass: элементы не плоские, а полупрозрачные слои со своей
     толщиной. Каждый складывается из четырёх вещей:
       1) размытие фона под собой (backdrop-filter) — сквозь панель видно,
          что под ней, но неразборчиво;
       2) полупрозрачная заливка — даёт материалу плотность;
       3) светлая кромка сверху (inset-тень) — блик на скруглении, будто
          стекло поймало свет;
       4) мягкая тень снизу — панель висит над фоном, а не лежит в нём.

     Заливка держится довольно плотной (0.72 и выше): на прозрачном стекле
     цифры теряют контраст, а это отчётная система, а не витрина. */
  :root {
    --ink: #1d1d1f;
    --ink-secondary: #424245;
    --ink-muted: #636368;
    --accent: var(--sys-blue-text);
    --accent-hover: var(--sys-blue-text);
    --accent-fill: var(--sys-blue);
    --good: #1f7a35;
    --critical: #d70015;
    --glass: var(--lg-fill);
    --glass-strong: var(--lg-fill-strong);
    --glass-quiet: #ffffff;
    --glass-edge: transparent;
    --edge-top: transparent;
    --edge-bottom: transparent;
    --glass-border: var(--lg-rim);
    --hairline: rgba(60, 60, 67, .12);
    --blur: none;
    /* Тень оставлена только всплывающим слоям. У обычной карточки её
    нет: граница честнее и не пачкает белое. */
    --lift: 0 12px 32px rgba(16,16,19,.10), 0 2px 6px rgba(16,16,19,.06);
    --lift-soft: none;
    --mesh-1: transparent;
    --mesh-2: transparent;
    --mesh-3: transparent;
    --glow-page: transparent;
    --page: #f0f1f6;
    --surface-solid: #ffffff;
    --icon-search: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="none" stroke="black" stroke-width="1.8" stroke-linecap="round" d="M7 1.9a5.1 5.1 0 1 0 0 10.2A5.1 5.1 0 0 0 7 1.9zm3.7 8.8 3.4 3.4"/></svg>');
    --paper: #ffffff;
    --glass-bg: var(--lg-fill-strong);
    color-scheme: light;
    --r-s: 8px;
    --r-m: 10px;
    --r-l: 14px;
    --year-0: #4a5ea4;
    --year-1: #823c00;
    --year-2: #5dade6;
    --year-3: #3a874f;
    --year-4: #c79e47;
    --depth-rest: var(--lg-shadow);
    --depth-hover: var(--lg-shadow-hover);
    --depth-press: 0 1px 1px rgba(20, 20, 30, .06), 0 3px 10px -6px rgba(20, 20, 30, .14);
    --mac-chrome: rgba(250, 250, 253, .62);
    --mac-track: rgba(118, 118, 128, .12);
    --mac-thumb: rgba(255, 255, 255, .96);
    --mac-thumb-shadow: 0 3px 8px rgba(0,0,0,.12), 0 3px 1px rgba(0,0,0,.04), inset 0 .5px 0 #fff;
    --mac-button: linear-gradient(180deg, rgba(255,255,255,.9), rgba(255,255,255,.66));
    --mac-button-edge: rgba(0, 0, 0, .10);
    --mac-button-shadow: inset 0 1px 0 rgba(255,255,255,.95), 0 .5px 1px rgba(0,0,0,.10), 0 3px 10px -2px rgba(30,40,80,.10);
    --mac-card-shadow: var(--lg-shadow);
    --mac-card-hover: var(--lg-shadow-hover);
    --mac-head: rgba(248, 248, 251, .82);
    --mac-verdict: rgba(255, 255, 255, .55);
    --mac-main: linear-gradient(180deg, #1a86f5, var(--sys-blue-btn));
    --sys-blue: #007aff;
    --sys-blue-text: #0066cc;
    --sys-blue-btn: #0071e3;
    --sys-green: #34c759;
    --sys-red: #ff3b30;
    --sys-orange: #ff9500;
    --sys-indigo: #5856d6;
    --sys-teal: #30b0c7;
    --lg-fill: rgba(255, 255, 255, .74);
    --lg-fill-strong: rgba(255, 255, 255, .86);
    --lg-edge: rgba(255, 255, 255, .9);
    --lg-rim: rgba(0, 0, 0, .06);
    --lg-blur: blur(28px) saturate(180%);
    --lg-blur-btn: blur(20px) saturate(180%);
    --lg-blur-nav: blur(40px) saturate(190%);
    --lg-shadow: inset 0 1px 0 var(--lg-edge), 0 .5px 1px rgba(0,0,0,.05), 0 10px 30px -10px rgba(30, 40, 80, .16);
    --lg-shadow-hover: inset 0 1px 0 var(--lg-edge), 0 1px 2px rgba(0,0,0,.06), 0 18px 40px -12px rgba(30, 40, 80, .24);
    --focus-ring: 0 0 0 3px rgba(0, 122, 255, .45);
    --chart-fill-top: rgba(0, 122, 255, .26);
    --chart-fill-bottom: rgba(0, 122, 255, 0);
    --blob-1: rgba(0, 122, 255, .20);
    --blob-2: rgba(175, 82, 222, .16);
    --blob-3: rgba(48, 176, 199, .18);
    --blob-4: rgba(255, 149, 0, .10);
    --lg-pop: rgba(255, 255, 255, .92);
    --lg-pop-shadow: 0 18px 44px -8px rgba(30,40,80,.22), 0 2px 6px rgba(0,0,0,.06);
    --sel-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%236e6e73' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    --amber: #b26a00;
  }
  :root:not([data-look="glass"]) {
    --ink-muted: #6e6e73;
    --page: #f5f5f7;
    --mac-chrome: #ececf0;
    --mac-thumb: #ffffff;
    --mac-button: #ffffff;
    --mac-button-shadow: 0 .5px 1px rgba(0,0,0,.10), 0 1px 3px rgba(0,0,0,.06);
    --mac-head: #ffffff;
    --mac-verdict: #ffffff;
    --lg-fill: #ffffff;
    --lg-fill-strong: #ffffff;
    --lg-edge: transparent;
    --lg-rim: rgba(0, 0, 0, .08);
    --lg-blur: none;
    --lg-blur-btn: none;
    --lg-blur-nav: none;
    --lg-shadow: 0 .5px 1px rgba(0,0,0,.05), 0 4px 14px -6px rgba(0,0,0,.08);
    --lg-shadow-hover: 0 1px 2px rgba(0,0,0,.06), 0 10px 24px -8px rgba(0,0,0,.14);
    --blob-1: transparent;
    --blob-2: transparent;
    --blob-3: transparent;
    --blob-4: transparent;
  }
  @media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
      --ink: #e5e5ea;
      --ink-secondary: #c7c7cc;
      --ink-muted: #98989d;
      --good: #30d158;
      --critical: #ff6961;
      --glass: #1c1c1e;
      --glass-strong: #1c1c1e;
      --glass-quiet: #2a2927;
      --glass-border: rgba(255, 255, 255, .10);
      --hairline: rgba(235, 235, 245, .12);
      --lift: 0 12px 32px rgba(0,0,0,.55), 0 2px 6px rgba(0,0,0,.35);
      --page: #1c1c1e;
      --surface-solid: #2c2c2e;
      /* Значок поиска рисуется чёрным контуром — на тёмном листе его
      не видно вовсе. */
      --icon-search: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="none" stroke="%23b0aea6" stroke-width="1.8" stroke-linecap="round" d="M7 1.9a5.1 5.1 0 1 0 0 10.2A5.1 5.1 0 0 0 7 1.9zm3.7 8.8 3.4 3.4"/></svg>');
      --paper: #2c2c2e;
      color-scheme: dark;
      --year-0: #6176bb;
      --year-1: #ad6638;
      --year-2: #61ade3;
      --year-3: #3e8651;
      --year-4: #c59e4d;
      --depth-rest: 0 1px 2px rgba(0, 0, 0, .35), 0 10px 28px -12px rgba(0, 0, 0, .6);
      --depth-hover: 0 2px 4px rgba(0, 0, 0, .4), 0 18px 40px -14px rgba(0, 0, 0, .7);
      --depth-press: 0 1px 1px rgba(0, 0, 0, .4), 0 3px 10px -6px rgba(0, 0, 0, .5);
      --mac-chrome: rgba(30, 30, 32, .58);
      --mac-track: rgba(118, 118, 128, .24);
      --mac-thumb: rgba(99, 99, 102, .92);
      --mac-thumb-shadow: 0 3px 8px rgba(0,0,0,.35), inset 0 .5px 0 rgba(255,255,255,.18);
      --mac-button: linear-gradient(180deg, rgba(255,255,255,.085), rgba(255,255,255,.05));
      --mac-button-edge: rgba(255, 255, 255, .10);
      --mac-button-shadow: inset 0 1px 0 rgba(255,255,255,.07), 0 .5px 1px rgba(0,0,0,.4);
      --mac-card-shadow: none;
      --mac-card-hover: 0 8px 24px rgba(0,0,0,.5);
      --mac-head: rgba(40, 40, 42, .86);
      --mac-verdict: rgba(58, 58, 60, .5);
      --sys-blue: #0a84ff;
      --sys-blue-text: #409cff;
      --sys-green: #30d158;
      --sys-red: #ff453a;
      --sys-orange: #ff9f0a;
      --sys-indigo: #5e5ce6;
      --sys-teal: #40c8e0;
      --lg-fill: rgba(44, 44, 46, .84);
      --lg-fill-strong: rgba(44, 44, 46, .92);
      --lg-edge: rgba(255, 255, 255, .10);
      --lg-rim: rgba(255, 255, 255, .08);
      --lg-blur: blur(28px) saturate(180%);
      --lg-blur-btn: blur(20px) saturate(180%);
      --lg-blur-nav: blur(40px) saturate(190%);
      --lg-shadow: inset 0 1px 0 var(--lg-edge), 0 0 0 .5px rgba(0,0,0,.4), 0 14px 36px -12px rgba(0,0,0,.6);
      --lg-shadow-hover: inset 0 1px 0 rgba(255,255,255,.14), 0 0 0 .5px rgba(0,0,0,.4), 0 20px 44px -12px rgba(0,0,0,.7);
      --focus-ring: 0 0 0 3px rgba(10, 132, 255, .55);
      --chart-fill-top: rgba(10, 132, 255, .34);
      --chart-fill-bottom: rgba(10, 132, 255, 0);
      --blob-1: rgba(10, 132, 255, .11);
      --blob-2: rgba(191, 90, 242, .08);
      --blob-3: rgba(64, 200, 224, .12);
      --blob-4: rgba(255, 159, 10, .07);
      --lg-pop: rgba(44, 44, 46, .94);
      --lg-pop-shadow: 0 18px 44px -8px rgba(0,0,0,.7), 0 0 0 .5px rgba(0,0,0,.5);
      --sel-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23a1a1a6' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
      --amber: #ffb340;
    }
  }
  :root[data-theme="dark"] {
    --ink: #e5e5ea;
    --ink-secondary: #c7c7cc;
    --ink-muted: #98989d;
    --accent: #f0eee7;
    --accent-hover: #ffffff;
    --accent-fill: #b8b2a4;
    --good: #30d158;
    --critical: #ff6961;
    --glass: #1c1c1e;
    --glass-strong: #1c1c1e;
    --glass-quiet: #2a2927;
    --glass-border: rgba(255, 255, 255, .10);
    --hairline: rgba(235, 235, 245, .12);
    --lift: 0 12px 32px rgba(0,0,0,.55), 0 2px 6px rgba(0,0,0,.35);
    --page: #1c1c1e;
    --surface-solid: #2c2c2e;
    --icon-search: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="none" stroke="%23b0aea6" stroke-width="1.8" stroke-linecap="round" d="M7 1.9a5.1 5.1 0 1 0 0 10.2A5.1 5.1 0 0 0 7 1.9zm3.7 8.8 3.4 3.4"/></svg>');
    --paper: #2c2c2e;
    color-scheme: dark;
    --year-0: #6176bb;
    --year-1: #ad6638;
    --year-2: #61ade3;
    --year-3: #3e8651;
    --year-4: #c59e4d;
    --depth-rest: 0 1px 2px rgba(0, 0, 0, .35), 0 10px 28px -12px rgba(0, 0, 0, .6);
    --depth-hover: 0 2px 4px rgba(0, 0, 0, .4), 0 18px 40px -14px rgba(0, 0, 0, .7);
    --depth-press: 0 1px 1px rgba(0, 0, 0, .4), 0 3px 10px -6px rgba(0, 0, 0, .5);
    --mac-chrome: rgba(30, 30, 32, .58);
    --mac-track: rgba(118, 118, 128, .24);
    --mac-thumb: rgba(99, 99, 102, .92);
    --mac-thumb-shadow: 0 3px 8px rgba(0,0,0,.35), inset 0 .5px 0 rgba(255,255,255,.18);
    --mac-button: linear-gradient(180deg, rgba(255,255,255,.085), rgba(255,255,255,.05));
    --mac-button-edge: rgba(255, 255, 255, .10);
    --mac-button-shadow: inset 0 1px 0 rgba(255,255,255,.07), 0 .5px 1px rgba(0,0,0,.4);
    --mac-card-shadow: none;
    --mac-card-hover: 0 8px 24px rgba(0,0,0,.5);
    --mac-head: rgba(40, 40, 42, .86);
    --mac-verdict: rgba(58, 58, 60, .5);
    --sys-blue: #0a84ff;
    --sys-blue-text: #409cff;
    --sys-green: #30d158;
    --sys-red: #ff453a;
    --sys-orange: #ff9f0a;
    --sys-indigo: #5e5ce6;
    --sys-teal: #40c8e0;
    --lg-fill: rgba(44, 44, 46, .84);
    --lg-fill-strong: rgba(44, 44, 46, .92);
    --lg-edge: rgba(255, 255, 255, .10);
    --lg-rim: rgba(255, 255, 255, .08);
    --lg-blur: blur(28px) saturate(180%);
    --lg-blur-btn: blur(20px) saturate(180%);
    --lg-blur-nav: blur(40px) saturate(190%);
    --lg-shadow: inset 0 1px 0 var(--lg-edge), 0 0 0 .5px rgba(0,0,0,.4), 0 14px 36px -12px rgba(0,0,0,.6);
    --lg-shadow-hover: inset 0 1px 0 rgba(255,255,255,.14), 0 0 0 .5px rgba(0,0,0,.4), 0 20px 44px -12px rgba(0,0,0,.7);
    --focus-ring: 0 0 0 3px rgba(10, 132, 255, .55);
    --chart-fill-top: rgba(10, 132, 255, .34);
    --chart-fill-bottom: rgba(10, 132, 255, 0);
    --blob-1: rgba(10, 132, 255, .11);
    --blob-2: rgba(191, 90, 242, .08);
    --blob-3: rgba(64, 200, 224, .12);
    --blob-4: rgba(255, 159, 10, .07);
    --lg-pop: rgba(44, 44, 46, .94);
    --lg-pop-shadow: 0 18px 44px -8px rgba(0,0,0,.7), 0 0 0 .5px rgba(0,0,0,.5);
    --sel-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23a1a1a6' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    --amber: #ffb340;
  }

  /* Выделение текста. Браузер по умолчанию красит его системным синим —
     единственным чужим цветом на всём листе: в системе нет ни одного
     синего пикселя, а выделенная строка вспыхивает им во всю ширину.

     Здесь выделение сделано чернилами через прозрачность: цвет свой, а
     текст под ним остаётся читаемым — важно, потому что цифры чаще
     всего выделяют, чтобы скопировать в письмо партнёру, и надо видеть,
     что именно захвачено. */
  /* Служебные значки в выделение не попадают. Стрелка «↳» показывает
     вложенность строки разбора, «→» и «↑» — направление; при копировании
     таблицы в письмо они превращаются в мусор посреди чисел. */
  .delta i, .rung .arrow, .nest { user-select: none; }

  /* Два имени, которые везде использовались и нигде не объявлялись.
     Свойство с неизвестной переменной браузер выбрасывает целиком —
     молча, без единой ошибки в консоли, — и компонент теряет вид.
     Проверено: ``--paper`` стоит в четырёх местах, ``--glass-bg`` в двух.

     Что от этого ломалось:

     * выбранная вкладка в переключателях (``.views a.on``, ``.pick a.on``)
       оставалась без заливки и отличалась от невыбранных только весом
       шрифта — на «Учёте», «Услугах», «Барберах», «Карте», «Показателях»
       было не понять, какой срез включён;
     * у крайнего значения на графиках не рисовалась светлая обводка, и
       число ложилось прямо на линию сетки;
     * полоса разделов в карточке филиала оставалась прозрачной, и под
       ней просвечивало содержимое;
     * полоса объединения услуг была кромкой без фона.

     Объявляются здесь, в общем корне, и берут значения от поверхности —
     значит переключаются вместе с темой сами. */

  /* Тёмные значения объявлены дважды: под системной настройкой и под
     ручным выбором. Так переключатель побеждает систему в обе стороны —
     светлую тему можно включить на тёмной macOS и наоборот. */

  /* Начертаний ровно четыре, и это не про вкус, а про то, что видно.

     Системный шрифт (SF Pro на macOS) имеет насыщенности 400, 500, 600 и
     700. Всё, что между, браузер округляет к ближайшей: 450, 480 и 520
     превращаются в одно и то же начертание, 620, 630, 640 и 660 — тоже.
     В системе таких значений накопилось четырнадцать, и тринадцать
     различий из четырнадцати человек не видел вовсе — а вёрстка при этом
     выглядела набранной наугад.

     Осталось: 400 — текст, 500 — метка и подпись, 600 — заголовок и
     число, 700 — только там, где нужен настоящий контраст веса. */
  /* шкала типографики и ритма
     Инвентаризация показала двадцать разных размеров шрифта и
     четырнадцать отступов. Это не система, а накопление: каждый новый
     экран приносил свои 12,5 и 13,5 пикселя, и вместе они дают ощущение
     черновика — глаз не находит правил, по которым устроена страница.

     Здесь шесть ступеней текста и шесть шага отступа. Шесть — не ради
     красоты числа: меньше не хватает на заголовок-подзаголовок-подпись,
     больше глаз перестаёт различать.

     Размер идёт в паре с весом, трекингом и интерлиньяжем. Порознь их
     задавать нельзя: крупная светлая надпись читается как объявление, а
     та же крупная плотная — как титул. Иерархию держит не размер, а
     сочетание. Это и есть разница между панелью и продуктом. */
  :root {
    /* Одно крупное число на экран: прогноз, итог, место в рейтинге. */
    --t-hero-size: 34px;     /* главное число листа (24.09.2026) */
    --t-display-size: 30px;  --t-display-weight: 600;
    --t-display-track: -0.032em; --t-display-lead: 1.05;
    /* Та же ступень для длинных сумм. Выручка сети — девять значащих
       цифр со знаком рубля, и в плитку шириной в пятую часть листа она
       при тридцати пикселях не помещается: знак срезает краем. Двадцать
       шесть — самый крупный кегль, при котором «272 233 286 ₽» влезает
       целиком. Отдельная ступень, а не правка display: display стоит
       ещё в десятке мест, где числа короче и мельчить их незачем. */
    --t-display-long: 26px;
    /* Заголовок страницы. */
    --t-title-size: 21px;    --t-title-weight: 600;
    --t-title-track: -0.028em;  --t-title-lead: 1.18;
    /* Заголовок раздела и таблицы. */
    --t-head-size: 16px;     --t-head-weight: 600;
    --t-head-track: -0.022em;   --t-head-lead: 1.28;
    /* Основной текст и содержимое таблиц. */
    --t-body-size: 13.5px;   --t-body-weight: 400;
    --t-body-track: 0;          --t-body-lead: 1.45;
    /* Пояснения, подписи, второстепенное. */
    --t-small-size: 12.5px;  --t-small-weight: 400;
    --t-small-track: 0;         --t-small-lead: 1.5;
    /* Прописная метка над числом: «ВЫРУЧКА», «МЕСТО В СЕТИ». */
    --t-micro-size: 11.5px;  --t-micro-weight: 500;
    --t-micro-track: .045em;    --t-micro-lead: 1.3;

    /* Ритм отступов кратен четырём. Четыре — предел, ниже которого глаз
       не отличает соседние значения, а разнобой в один-два пикселя
       читается как небрежность. */
    --s1: 4px; --s2: 8px; --s3: 12px; --s4: 16px; --s5: 24px; --s6: 32px;

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

       Два имени эту клятву нарушали: ``--f-body`` объявлял свои 13
       пикселей, ``--f-card`` — свои 15. Ступеней в шкале шесть, а на
       странице их насчитывалось четырнадцать, и половина отличалась на
       полпикселя — глаз такую разницу не читает как иерархию, он читает
       её как небрежность. Теперь оба указывают на ступени шкалы. */
    --f-cap: var(--t-micro-size); --f-note: var(--t-small-size);
    --f-body: var(--t-small-size); --f-strong: var(--t-body-size);
    --f-card: var(--t-head-size); --f-block: var(--t-head-size);
    --f-page: var(--t-title-size); --f-num: var(--t-display-size);

    /* Тени: три уровня на всю систему. */
    --shadow-1: 0 1px 2px rgba(0, 0, 0, .08);
    --shadow-2: 0 2px 10px rgba(0, 0, 0, .10);
    --shadow-3: 0 10px 32px rgba(0, 0, 0, .18);
  }

  /* --- переключатель темы --- */
  .theme-switch {
    display: inline-flex; align-items: center; gap: 7px;
    font-size: var(--t-body-size); font-weight: 500; letter-spacing: -0.006em;
    color: var(--ink); background: var(--glass-strong);
    /* Кнопка обязана быть видна в любой теме: она единственный способ
       вернуться назад, если тема выбрана неудачно. */
    border-color: var(--glass-border);
    border: 1px solid var(--glass-border); border-radius: var(--r-s);
    box-shadow: none;
    cursor: pointer; transition: .18s ease;
    height: 36px;
    padding: 0 12px;
  }
  .theme-switch:hover {
    filter: brightness(1.05); background: var(--glass-strong);
    transform: translateY(-1px);
    box-shadow:
      0 0 0 1px color-mix(in srgb, var(--accent) 38%, transparent),
      0 4px 12px color-mix(in srgb, var(--accent) 22%, transparent),
      0 12px 26px color-mix(in srgb, var(--accent) 20%, transparent),
      inset 0 1.5px 0 var(--glass-edge);
  }
  .theme-switch:active {
    transform: translateY(1px) scale(.985);
    box-shadow: inset 0 3px 6px rgba(0,0,0,.16), inset 0 -1px 0 var(--glass-edge);
  }
  .theme-switch:focus-visible {
    outline: none;
    box-shadow:
      0 0 0 3px color-mix(in srgb, var(--accent) 60%, transparent),
      inset 0 1px 0 var(--glass-edge);
  }
  .theme-switch .glyph { font-size: var(--t-body-size); line-height: 1; }

  * { box-sizing: border-box; }
  html {
    -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
    /* iPhone (критики 24.09.2026): страница под вырезом и полосой «домой»
    не уходит — поля безопасной зоны; серой вспышки при касании нет. */
    -webkit-tap-highlight-color: transparent;
  }
  /* ``overflow-x: hidden`` здесь стояло — и было ошибкой, хоть и
     починило то, ради чего ставилось.

     Ставилось оно против всплывающей подсказки старого графика: она
     вылезала на семь пикселей за правый край и давала полосу прокрутки
     на всю страницу. Побочный итог оказался дороже: любое
     ``overflow`` на корне превращает его в область прокрутки, и
     ``position: sticky`` внутри перестаёт держаться за окно. Боковая
     панель уезжала вверх вместе со страницей — при том, что была
     прописана прилипающей.

     Подсказки той больше нет: старая рисовалка графиков заменена общей,
     где подсказка — средство самого SVG и за холст не выходит. Значит,
     и затычка не нужна. Страница вбок не ездит потому, что вылезать
     нечему; таблицы возят внутри своих ``.scroll``. */
  body {
    margin: 0; min-height: 100vh;
    color: var(--ink);
    font: 400 15px/1.5 -apple-system, "SF Pro Text", system-ui, "Segoe UI", sans-serif;
    letter-spacing: -0.011em;
    font-feature-settings: "kern" 1, "liga" 1, "lnum" 1;
    text-rendering: optimizeLegibility;
    padding-left: env(safe-area-inset-left);
    padding-right: env(safe-area-inset-right);
    padding-bottom: env(safe-area-inset-bottom);
  }
  /* Подложки нет. Цветные пятна и свечение под полупрозрачными
     карточками были нужны, чтобы стекло не выглядело серым пластиком.
     Стекла больше нет — не нужны и они. Ровный лист. */

  /* Ссылки набраны цветом текста, и под курсором тоже.

     Цвет — плохой способ пометить ссылку в плотном интерфейсе: он
     расходуется на каждое второе слово и перестаёт что-либо значить.
     Двести цветных названий подряд читаются как гирлянда — глаз
     цепляется за цвет, а не за содержание.

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

     Ссылка узнаётся по подчёркиванию: тонкое и приглушённое в покое,
     сплошное под курсором. Ни одного цветного пикселя. */
  a { color: inherit; text-decoration: none; }
  a:hover {
    color: var(--accent-hover);
    text-decoration: underline;
    text-decoration-thickness: 1px;
    text-underline-offset: 3px;
  }
  /* Внутри таблиц — так же. Подчёркивание под курсором достаточно
     говорит, что строка кликабельна. */
  tbody a { color: inherit; text-decoration: none; }
  tbody a:hover {
    color: var(--accent-hover);
    text-decoration: underline;
    text-decoration-thickness: 1px;
    text-underline-offset: 3px;
  }
  /* Каркас: слева панель разделов, справа контент. Панель — колонка
     сетки, а не отдельный слой: страницы по-прежнему кладут свои блоки
     подряд, сетка сама разводит их — навигацию в первую колонку,
     всё остальное во вторую. */
  .wrap {
    max-width: var(--wrap-max); margin: 0 auto;
    padding: 0 var(--wrap-pad) 84px;
    display: grid; grid-template-columns: var(--nav-width) minmax(0, 1fr);
    column-gap: 32px; align-items: start;
    /* Панель убрана: уезжает за край целиком, контент занимает весь экран.
    Панель при этом выходит из сетки — в схлопнутой колонке она стала бы
    нулевой ширины, и её пункты полезли бы поверх таблицы. */
    transition: grid-template-columns .2s ease, column-gap .2s ease;
  }
  .wrap > * { grid-column: 2; min-width: 0; }
  .wrap > nav.tabs { grid-column: 1; grid-row: 1 / span 200; }

  /* Листы без панели разделов: вход, «не найдено», «барбер не найден».
     Сетка отводила колонку под панель, которой на этих страницах нет, —
     и содержимое сидело сдвинутым вправо на её ширину, с пустотой слева
     во весь экран. Первое, что видит человек утром, начиналось с этой
     пустоты. Колонку убираем, а кнопку сворачивания панели прячем:
     сворачивать нечего. */
  .wrap:not(:has(nav.tabs)) {
    grid-template-columns: minmax(0, 1fr);
    column-gap: 0;
  }
  .wrap:not(:has(nav.tabs)) > * { grid-column: 1; }
  body:has(.wrap:not(:has(nav.tabs))) .nav-toggle { display: none; }

  /* Знак сети по теме — правило общее, а не только для панели. На
     странице входа логотип живёт вне ``nav.tabs``, и оба изображения
     рисовались разом: чёрное поверх белого. */
  .logo-dark { display: none; }
  @media (prefers-color-scheme: dark) {
    :root:where(:not([data-theme="light"])) .logo-dark { display: block; }
    :root:where(:not([data-theme="light"])) .logo-light { display: none; }
  }
  [data-theme="dark"] .logo-dark { display: block; }
  [data-theme="dark"] .logo-light { display: none; }
  [data-theme="light"] .logo-dark { display: none; }
  [data-theme="light"] .logo-light { display: block; }

  /* --- стеклянная панель --- */
  /* Карточка — плита стекла, а не серый прямоугольник.

     Стекло читается стеклом, когда у него видно три вещи: толщину
     (светлая кромка по верхнему краю), вес (тень под нижним) и то, что
     свет по нему скользит (мягкий блик сверху вниз). Раньше здесь была
     только заливка и еле заметная тень — получался просто фон другого
     оттенка, и вся страница выглядела плоской.

     Блик сделан отдельным слоем ``::before``, а не тенью: тень внутри
     карточки перекрывала бы содержимое, а слой лежит под ним. */
  .card {
    /* Лист на листе. Граница и цвет — основа; глубина задаётся ниже,
       в блоке «Глубина карточек» (03.09.2026, решение владельца: карточки
       глубокие, с нажатием под курсором). Здесь тени нет намеренно —
       тот блок стоит в конце файла и переопределяет её один раз. */
    background: var(--glass);
    border: 1px solid var(--glass-border);
    border-radius: var(--r-l);
    box-shadow: none;
  }

  /* --- шапка --- */
  /* Титул страницы стоит прямо на листе, без коробки: раму вокруг
     заголовка носят объявления, а не документы. Панель слева уже
     отвечает за «где я», шапке остаётся сказать «что передо мной». */
  header.top {
    display: flex; align-items: flex-end; justify-content: space-between;
    gap: var(--s5); flex-wrap: wrap;
    margin-bottom: var(--s4); padding: 26px 2px 0;
    background: transparent; border: 0; border-radius: 0;
    box-shadow: none;
  }
  /* Заголовок держится на весе и плотности, а не на размере. Крупная
     светлая надпись выглядит как объявление; тяжёлая и сжатая — как
     титул документа. Подзаголовок уводим на два шага вглубь: меньше,
     тише и с большим межстрочным просветом, чтобы он читался как
     подпись, а не как второй заголовок. */
  header.top > div:first-child { min-width: 0; }
  /* Правая половина шапки — тоже ряд, а не поток. Там стоят полоса
     сети и кнопки листа, и без этого правила они вставали в два этажа:
     ``.views.brands`` — блочный флекс шириной по содержимому, а
     ``.btn`` — инлайновые, между ними анонимный блок, то есть перенос.
     На «Сегодня», «Оборотах» и «Клиентах» это не показывалось: там
     кнопок нет вовсе. Первой попалась карточка барбершопа — правая
     половина выросла с 36 px до 74,75 и обогнала левую (46,7), и вся
     шапка стала выше на 28 px. То же ждало «Точки», «К кому идти» и
     ручной ввод: они уже передают кнопки в плашку.

     Правило существовало и раньше, но только внутри мобильного
     ``@media`` — там же, ниже. Здесь оно общее, потому что причина
     общая: два соседа разной природы в одном контейнере. */
  header.top > div:last-child {
    display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap;
  }
  header.top h1 {
    margin: 0 0 var(--s1); font-size: var(--t-title-size);
    font-weight: 700; letter-spacing: -0.032em;
    line-height: 1.1;
    font-family: -apple-system, "SF Pro Display", system-ui, "Segoe UI", sans-serif;
  }
  header.top .meta {
    /* Второстепенный, но не бледный: подпись под заголовком и отметка
       «данные по такое-то число» — то, что читают каждый раз. Приглушать
       их до серого шума значит прятать важное ради красоты. */
    color: var(--ink-secondary); font-size: var(--t-body-size); letter-spacing: -0.004em;
    line-height: 1.45;
  }

  /* Боковая панель. Никакого материала — воздух и волосяная черта
     справа: панель это поле листа, а не ещё одна карточка. Держится
     на месте при прокрутке; служебное прижато к низу. */
  nav.tabs {
    position: sticky; top: 0; align-self: start;
    height: 100vh; margin: 0; padding: 26px 20px 20px 2px;
    display: flex; flex-direction: column; gap: 2px;
    border: 0;
    border-radius: 0;
    box-shadow: inset -1px 0 0 rgba(255,255,255,.35);
  }
  /* Фирменный знак. В файле логотипа большие прозрачные поля — отрицательные
     отступы срезают их, чтобы знак стоял по левому краю колонки, а не
     плавал внутри собственной рамки.

     Из-за этих же отступов область наведения не совпадала со знаком:
     ссылка занимала прямоугольник вместе со срезанными полями, и
     подсветка вспыхивала там, где ничего не нарисовано. Теперь ссылка
     обжимает картинку: сдвиг делает обёртка, а не изображение внутри
     неё, и щёлкается ровно то, что видно. */
  nav.tabs .brand {
    padding: 0 0 18px; margin: -12px 0 0 -18px;
    overflow: hidden; width: 176px; line-height: 0;
  }
  /* Поля срезаны долями, а не пикселями. В файле 2480 × 867, знак
     занимает строки 212–654 — то есть сверху 8,548 % ширины пустоты, снизу
     8,589 %. Процентный отступ считается от ширины обёртки, поэтому крой
     остаётся точным при любой ширине колонки. Пиксельные −16 и −14 были
     подобраны на глаз и срезали верхушку букв: при ширине 176 пустота
     сверху ровно 15,04 пикселя, а срезалось 16. */
  nav.tabs .brand img { display: block; width: 100%; margin: -8.548% 0 -8.589%; }
  nav.tabs .brand .logo-dark { display: none; }
  @media (prefers-color-scheme: dark) {
    nav.tabs .brand .logo-dark { display: block; }
    nav.tabs .brand .logo-light { display: none; }
  }
  [data-theme="dark"] nav.tabs .brand .logo-dark { display: block; }
  [data-theme="dark"] nav.tabs .brand .logo-light { display: none; }
  [data-theme="light"] nav.tabs .brand .logo-dark { display: none; }
  [data-theme="light"] nav.tabs .brand .logo-light { display: block; }
  nav.tabs .row { display: flex; flex-direction: column; gap: 2px; }
  nav.tabs a {
    display: block; padding: 9px 12px;
    font-size: var(--t-body-size); font-weight: 500; color: var(--ink-secondary);
    text-align: left; white-space: nowrap;
    text-decoration: none;
    border-radius: 8px;
  }  /* Выбранный раздел — вес и чернильная черта слева. Без плашек-таблеток
     и свечения: место в колонке само по себе достаточно заметно. */  /* Служебное — у нижнего края, тише разделов. */
  nav.tabs .util {
    margin-top: auto; padding-top: 14px;
    border-top: 1px solid var(--hairline);
  }
  nav.tabs .util a, nav.tabs .util .theme-switch {
    display: flex; justify-content: flex-start; width: 100%;
    padding: 8px 12px; min-height: 0; height: auto;
    font-size: var(--t-body-size); font-weight: 500;
    color: var(--ink-secondary);
    background: transparent; border: 0; border-radius: var(--r-s);
    box-shadow: none;
  }
  nav.tabs .util a:hover, nav.tabs .util .theme-switch:hover {
    background: color-mix(in srgb, var(--ink) 6%, transparent);
    color: var(--ink); transform: none; filter: none; box-shadow: none;
    text-decoration: none;
  }
  nav.tabs .util .btn-exit:hover { color: var(--critical); }

  /* Знак — ссылка на главную. Под курсором не подсвечивается, а чуть
     проваливается: логотип не кнопка, светиться ему незачем. */
  /* Знак — не пункт меню.

     Подсветка разделов цепляла и его: логотип это такая же ссылка
     внутри ``nav.tabs``, и общее правило вешало на него скруглённый
     светлый прямоугольник. Получался ободок вокруг знака — и стоял он
     не там, где знак, из-за срезанных полей картинки.

     Ободка нет. Осталось единственное движение — знак чуть проваливается
     под нажатием, как настоящая клавиша. */
  nav.tabs a.brand {
    display: block; text-decoration: none;
    transition: transform .12s ease, opacity .12s ease;
  }
  nav.tabs a.brand:hover,
  nav.tabs a.brand:focus-visible {
    background: transparent; text-decoration: none; box-shadow: none;
    opacity: .82;
  }
  nav.tabs a.brand:active { transform: translateY(1px) scale(.985); opacity: 1; }

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

     Раньше у неё было жёсткое ``left: 196px`` — отсчёт от края окна. Но
     лист центрирован и шире 1560 пикселей не растёт: на широком экране
     он отъезжает от края, панель уезжает вместе с ним, а кнопка
     остаётся на месте и наезжает на пункты меню.

     Теперь она считается от самого листа: отступ листа + ширина панели,
     минус половина кнопки — тогда она садится ровно на волосяную черту
     между панелью и содержимым, где бы лист ни оказался. */
  .nav-toggle {
    position: fixed; top: 30px; z-index: 50;
    left: calc(max(0px, (100vw - var(--wrap-max)) / 2) + var(--wrap-pad)
               + var(--nav-width) - 12px);
    width: 24px; padding: 0;
    border-radius: 50%; font-size: 0; line-height: 1;
    background: var(--glass-strong); border: 1px solid var(--glass-border);
    box-shadow: 0 1px 3px rgba(0,0,0,.22);
    /* Тише кнопку делает цвет знака, а не общая прозрачность.
       Прозрачность гасила и знак, и фон под ним разом: замер давал
       2,09 : 1 на светлой теме и 2,57 : 1 на тёмной при норме 4,5.
       То есть кнопку, убирающую панель, человек с обычным зрением
       находил по памяти, а не глазами.

       Тем же приглушённым цветом набраны подписи плиток: 5,49 : 1 на
       светлой теме и 5,17 : 1 на тёмной. Кнопка осталась тихой и стала
       различимой — одно другому не мешает. */
    color: var(--ink-muted);
    transition: color .15s ease, left .2s ease, border-color .15s ease;
    /* Круглая кнопка панели — единственная, у кого свой размер: это
    значок, а не кнопка с надписью. */
    height: 24px;
    min-height: 24px;
  }
  /* Выгрузка принята и собирается. Ссылка не уходит со страницы и на
     нажатие ничем не отвечает — человек нажимает второй раз, и на
     сервере заводится вторая сборка той же книги. Пока первая идёт,
     ссылка помечена занятой: видно, что нажатие принято, и второе
     нажатие мимо неё не проходит. Метку ставит и снимает
     ``app/static/download.js``. */
  a[aria-busy="true"] { pointer-events: none; color: var(--ink-muted); }

  .nav-toggle::before { content: "‹"; font-size: var(--t-head-size); }
  .nav-toggle:hover { color: var(--ink); background: var(--glass-strong); }
  .nav-toggle:focus-visible { color: var(--ink); }

  html.nav-off .wrap { grid-template-columns: 0 minmax(0, 1fr); column-gap: 0; }
  html.nav-off nav.tabs {
    position: fixed; left: 0; top: 0; z-index: 45;
    width: 208px; height: 100vh; padding-left: 22px;
    overflow: hidden; transform: translateX(-100%);
    background: var(--page); border-right-color: transparent;
    transition: transform .2s ease;
  }
  /* Панель убрана — кнопка садится к левому краю листа, там же, где
     была её граница. К краю окна её тянуть незачем: на широком экране
     она оказалась бы в двухстах пикселях от содержимого. */
  html.nav-off .nav-toggle {
    left: calc(max(0px, (100vw - var(--wrap-max)) / 2) + 12px);
  }
  html.nav-off .nav-toggle::before { content: "›"; }

  /* «Назад» — в шапке страницы, слева от заголовка. У нижнего края она
     налезала на «Выйти» в панели разделов: две кнопки в одном углу, и
     обе нажимаются наугад. Здесь она рядом с тем, к чему относится, —
     с текущей страницей. */
  .go-back {
    height: 30px; min-height: 30px; padding: 0 12px;
    font-size: var(--t-small-size); font-weight: 600;
    margin: 0 0 8px; opacity: .7; transition: opacity .15s ease;
  }
  .go-back:hover { opacity: 1; }
  @media print { .go-back, .nav-toggle { display: none !important; } }
  /* Узкий экран: панель складывается в закреплённую строку сверху —
     та же, что будет на телефоне. */
  @media (max-width: 940px) {
    .wrap { display: block; padding: 0 14px 64px; }
    nav.tabs {
      position: sticky; top: 0; z-index: 30; height: auto;
      flex-direction: row; align-items: center; gap: 2px;
      background: var(--page);
      border-bottom: 1px solid var(--hairline);
    }
    nav.tabs .brand { display: none; }
    nav.tabs .row { flex-direction: row; align-items: center; }
    nav.tabs a { padding: 8px 11px; font-size: var(--t-body-size); }
    nav.tabs a.on { background: transparent; box-shadow: inset 0 -2px 0 var(--ink); border-radius: 0; }
    nav.tabs .util { padding: 0; border: 0; }
    nav.tabs .util a, nav.tabs .util .theme-switch { width: auto; white-space: nowrap; }
    /* Панель уже сложена в строку сверху — сворачивать нечего, и кнопка
       на узком экране просто висела бы поверх содержимого: левой
       колонки, у границы которой она стоит, здесь нет. */
    .nav-toggle { display: none; }
  }

  /* Кнопки шапки: настройки и выход. Они не про данные, поэтому и цвет
     у них не акцентный — серо-стеклянный, а выход чуть теплее: это
     действие, после которого придётся вводить пароль заново. */
  .btn-plain, .btn-exit {
    display: inline-flex; align-items: center; justify-content: center;
    font: inherit; font-size: var(--t-body-size); font-weight: 500; letter-spacing: -0.006em;
    border-radius: var(--r-s); text-decoration: none; cursor: pointer;
    background: transparent; color: var(--ink);
    border: 1px solid var(--glass-border);
    box-shadow: none;
    transition: background .15s ease, border-color .15s ease;
    /* Тихая кнопка отличается цветом и кромкой, но не ростом. Своя
    высота в 34 пикселя выдавала себя в первом же ряду, где рядом
    стояла обычная: два пикселя разницы глаз замечает раньше, чем
    успевает понять, в чём дело. Ровно это и вылезло на «Разборе»,
    «Разговоре» и «Схеме», где тихая кнопка стоит рядом с обычной. */
    height: 36px;
    min-height: 36px;
    padding: 0 14px;
  }
  .btn-plain:hover, .btn-exit:hover {
    text-decoration: none; transform: none;
    background: var(--hairline); border-color: var(--ink-muted);
  }
  .btn-plain:active, .btn-exit:active { transform: none; }
  /* «Выйти» — обычная второстепенная кнопка, а не красное слово в
     воздухе. Красным она была заметнее, чем всё остальное в шапке, хотя
     это самое редкое действие на экране. Цвет появляется только под
     курсором, когда уже понятно, что сейчас произойдёт. */
  .btn-exit { color: var(--ink-secondary); }
  .btn-exit:hover {
    color: var(--critical);
    background: color-mix(in srgb, var(--critical) 7%, transparent);
    border-color: color-mix(in srgb, var(--critical) 45%, transparent);
  }

  /* Переключатель вида внутри раздела «Показатели». Не вкладки и не фильтры:
     полоса ссылок над фильтрами, где выбранный вид залит, остальные —
     просто текст. Похожесть на вкладки была бы обманом — вкладки ведут в
     другие разделы, а это одни и те же данные в другой форме. */
  /* Сегмент-контрол — один на всю систему: утопленная капсула,
     выбранное приподнято карточкой. Тот же вид, что у фильтров на
     «Клиентах»: раньше переключателей было три разных. */
  .views {
    display: flex; flex-wrap: wrap; align-items: center;
    margin: -8px 0 16px;
    width: max-content; max-width: 100%;
    background: var(--mac-track);
    border: 0;
    border-radius: 9px;
    padding: 2px;
    gap: 0;
  }
  .views a {
    padding: 5px 13px; border-radius: var(--r-s); white-space: nowrap;
    font-size: var(--f-body); font-weight: 500; color: var(--ink-secondary);
    text-decoration: none; border: 1px solid transparent;
    transition: color .14s, background .14s;
  }
  .views a:hover { color: var(--ink); text-decoration: none; }
  .views a.on {
    color: var(--ink); font-weight: 600; background: var(--paper);
    border-color: var(--hairline); box-shadow: var(--shadow-1);
  }

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

     Была ступенью вровень: 11,5 px прописными, тот же цвет #6b6962, что
     и у подписи плитки, — разница только в весе (600 против 500) и
     разрядке (.08em против .045em). На одиннадцати с половиной пикселях
     прописными такую разницу глаз не читает как иерархию, и «НАШИ
     ДЕНЬГИ» выглядело шестой плиткой в ряду из пяти.

     Теперь отличие видно, не приглядываясь: строчные вместо прописных,
     12,5 px вместо 11,5, цвет #565550 вместо #6b6962 и линия до конца
     ряда — она же отбивает один ряд от другого. */

  /* Переключатель внутри блока — тише и мельче полосы подпунктов.

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

     Теперь у каждой полосы свой класс, и это можно проверить тестом:
     ``.jump`` — подпункты раздела, ``.brands`` — сеть, ``.panes`` —
     развороты карточки барбершопа, ``.inner`` — вот эта. Внутриблочных
     в системе ровно две: «Окно возврата» на «Клиентах» и «Про кого» в
     разговоре с партнёром.

     Отличие сделано весом и размером, а не цветом: цвет в системе
     занят отклонениями (правило 5), и красить им устройство страницы
     нельзя. */
  .views.inner {
    padding: 2px; gap: 1px; margin: -4px 0 12px;
    background: color-mix(in srgb, var(--ink) 3.5%, transparent);
  }
  .views.inner a { padding: 3px 10px; font-size: var(--f-cap); }

  /* Переключатель сети живёт внутри шапки, а не над содержимым, и
     отступы ему нужны другие. Шапка выравнивает половины по низу
     (`align-items: flex-end`), а нижнее поле в 16 px поднимало полосу
     над этой линией: замер 31.07.2026 — низ переключателя на 101 px,
     низ подписи под заголовком на 117. Шестнадцать пикселей ни к чему
     не привязаны, и глаз читает их как перекос. */
  header.top .views.brands { margin: 0; }

  /* --- фильтры --- */
  /* Сетка, а не поток. Раньше поля стояли в ряд с переносом, и строки
     выходили рваными: пять полей сверху, четыре снизу, каждое своей
     ширины, а «Только действующие» и «К предыдущему периоду» не влезали
     в отведённые сто девяносто пикселей и обрезались на полуслове.

     Теперь колонки одинаковые и тянутся до края карточки: сколько их
     поместилось — столько и будет, но все поля встают строго друг под
     другом, а ширина колонки берётся не с потолка, а от самой длинной
     подписи в списках. */
  .filters {
    display: grid; gap: 14px 16px; align-items: end;
    grid-template-columns: repeat(auto-fill, minmax(215px, 1fr));
    padding: 18px 20px; margin-bottom: 24px;
    background: var(--glass-quiet);
    border: 1px solid var(--glass-border); border-radius: var(--r-l);
    box-shadow: none;
  }
  .filters label {
    display: flex; flex-direction: column; gap: 6px; min-width: 0;
    font-size: var(--t-small-size); font-weight: 500; color: var(--ink-secondary);
  }
  /* Поиск шире прочих: в него вводят название целиком. */
  .filters label.wide { grid-column: span 2; }
  .filters label select,
  .filters label input { width: 100%; min-width: 0; }
  /* Кнопка не встаёт в общий ряд с полями, а занимает отдельную строку и
     прижимается к правому краю карточки. В клетке наравне с полями она
     читалась как ещё один фильтр; отдельной строкой — как действие,
     которым фильтры применяются. */
  /* «Показать» и ссылки-действия («Скачать Excel») стоят в ряд, каждая в
     своей клетке сетки. Раньше кнопка занимала всю строку и прижималась
     вправо, а ссылка уезжала на строку ниже и оставалась одна слева —
     два действия выглядели как два разных этапа. */
  /* Кнопка в фильтрах — по содержимому, у нижней кромки ряда. Кнопка на
     всю ячейку сетки выглядела как ещё одно поле ввода, только с текстом
     по центру, — и ряд читался вразнобой. */
  .filters button, .filters .btn {
    width: auto; justify-self: start; align-self: end;
    max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  /* Раскрытие всегда с новой строки: оно шире одной клетки. */
  /* Редкие фильтры прячутся под раскрытие. Двенадцать полей одного вида
     занимали треть первого экрана, хотя каждый день трогают три: период,
     месяц и сеть. Раскрытие — штатный ``<details>``: работает без единой
     строки скрипта, помнит состояние в пределах страницы и открывается с
     клавиатуры.

     Внутри — та же сетка, что снаружи, поэтому раскрытый блок выглядит
     продолжением строки фильтров, а не отдельным окном. */
  .filters .more {
    grid-column: 1 / -1; margin: 0; padding: 0; border: 0;
  }
  .filters .more > summary {
    display: inline-flex; align-items: center; gap: 7px;
    padding: 7px 13px; border-radius: 999px; cursor: pointer;
    font-size: var(--t-small-size); font-weight: 500; color: var(--ink-secondary);
    background: color-mix(in srgb, var(--ink) 5%, transparent);
    border: 1px solid var(--glass-border);
    list-style: none; width: max-content; transition: .18s ease;
  }
  .filters .more > summary::-webkit-details-marker { display: none; }
  .filters .more > summary:hover { color: var(--ink); }
  .filters .more > summary::after {
    content: "▾"; font-size: var(--t-micro-size); opacity: .7; transition: transform .18s ease;
  }
  .filters .more[open] > summary::after { transform: rotate(180deg); }
  .filters .more .fields {
    display: grid; gap: 14px 16px; margin-top: 14px;
    grid-template-columns: repeat(auto-fill, minmax(215px, 1fr));
  }
  @media (max-width: 720px) {
    .filters { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
    .filters label.wide { grid-column: span 2; }
  }
  /* Поле поиска перечислено наравне с остальными: без него браузер
     рисовал системный серый прямоугольник посреди стеклянных полей. */
  select, textarea,
  input[type="date"], input[type="text"], input[type="search"],
  input[type="number"], input[type="password"] {
    font: inherit; font-size: var(--t-body-size); padding: 9px 13px; min-height: 38px; height: 38px;
    color: var(--ink);
    background: var(--glass-quiet);
    border: 1px solid var(--glass-border); border-radius: var(--r-s);
    box-shadow: none;
    appearance: none; -webkit-appearance: none;
  }
  /* Стрелка у списка (владелец 23.09.2026: «Оборот» и «Вся сеть»
     читались как поля ввода — не видно, что их можно раскрыть). */  textarea { height: auto; min-height: 84px; line-height: 1.45; resize: vertical; }
  /* Кольцо фокуса — 60 % чернил, а не 32 %. Замер 22.09.2026: при 32 %
     кольцо давало 2,01:1 к светлому листу и 2,85:1 к тёмному, а видимая
     граница обязана держать 3:1 (WCAG 1.4.11). Порог перехода — 36 % на
     тёмной теме и 50 % на светлой, поэтому взято 60 %: 3,6:1 на светлой,
     5,3:1 на тёмной. */
  input:focus, select:focus, textarea:focus {
    outline: 3px solid color-mix(in srgb, var(--accent) 60%, transparent);
    outline-offset: 1px;
  }
  /* Подсказка внутри поля — не серый шум системного цвета, а тот же
     приглушённый чернильный, что и остальные второстепенные надписи. */
  input::placeholder { color: var(--ink-muted); opacity: 1; }
  /* Браузер рисует внутри поиска свой крестик и лупу — чужие по стилю.
     Убираем и ставим собственную лупу фоном, оставляя место под неё. */
  input[type="search"] { padding-left: 36px; }
  input[type="search"]::-webkit-search-decoration,
  input[type="search"]::-webkit-search-cancel-button,
  input[type="search"]::-webkit-search-results-button { -webkit-appearance: none; display: none; }
  label.search { position: relative; }
  /* Свой выпадающий список подсказок. Браузерный отключён: он сравнивал
     строки строже, чем сам поиск, и «беломорская» строчными не находила
     «Беломорскую». Подробности — в app/static/search.js. */
  .suggest {
    position: absolute; z-index: 20; left: 0; right: 0; top: 100%;
    margin: 6px 0 0; padding: 5px; list-style: none;
    max-height: 280px; overflow-y: auto;
    background: var(--surface-solid);
    border: 1px solid var(--glass-border); border-radius: var(--r-m);
    box-shadow: var(--lift);
  }
  .suggest li {
    padding: 7px 11px; border-radius: var(--r-m); font-size: var(--t-body-size);
    cursor: pointer; white-space: nowrap; overflow: hidden;
    text-overflow: ellipsis;
  }
  .suggest li:hover, .suggest li.on {
    background: color-mix(in srgb, var(--accent) 14%, transparent);
    color: var(--ink);
  }
  label.search::after {
    content: ""; position: absolute; left: 13px; bottom: 12px;
    width: 14px; height: 14px; pointer-events: none; opacity: .6;
    background: var(--ink-secondary);
    /* Лупа нарисована маской, а не картинкой: политика безопасности
       запрещает внешние адреса, а SVG-файл ради одной иконки — лишний. */
    -webkit-mask: var(--icon-search) center / contain no-repeat;
    mask: var(--icon-search) center / contain no-repeat;
  }

  /* Кнопки.
     Сплошная тушь отвлекала всё внимание: самая контрастная вещь на
     экране — кнопка, а не цифры. Теперь кнопка из того же материала, что
     и карточки, только чуть выпуклая: световая кромка по верху и тонкая
     тень под низом. Под курсором — заметно светлеет. Лаконично: кнопку
     видно, когда ищешь, и не видно, когда читаешь таблицу. */
  button, .btn {
    font: inherit; font-size: var(--t-body-size); letter-spacing: -0.008em;
    text-decoration: none;
    display: inline-flex; align-items: center; justify-content: center;
  }
  button:hover, .btn:hover {
    text-decoration: none; transform: none;
  }
  button:active, .btn:active {
    background: color-mix(in srgb, var(--ink) 6%, var(--glass-strong));
  }
  button:disabled, .btn[disabled] {
    background: transparent; border-color: var(--glass-border);
    color: var(--ink-muted); cursor: not-allowed; box-shadow: none;
  }

  /* Необратимое действие: перенос года в архив, переписывание базы,
     удаление услуг ушедшей точки. Кромка и текст — цветом беды, но без
     заливки: кнопка должна насторожить, а не закричать. Раньше такие
     кнопки выглядели ровно как «Показать» и «Сохранить», а «Сохранить»
     ещё и заметнее — самое опасное на листе было самым тихим. */
  button.btn-danger, .btn.btn-danger {
    color: var(--critical);
    border-color: color-mix(in srgb, var(--critical) 45%, var(--glass-border));
  }
  button.btn-danger:hover, .btn.btn-danger:hover {
    background: color-mix(in srgb, var(--critical) 10%, var(--glass-strong));
    border-color: var(--critical);
  }

  /* Главное действие листа. Ставится там, где кнопка одна и очевидна:
     «Войти» на входе. Заливка чернилами, а не цветом, — акцент в этой
     системе делается тоном, а не краской. */
  button.btn-main, .btn.btn-main {
    font-weight: 600;
    background: var(--mac-main);
    color: var(--page);
  }
  button.btn-main:hover, .btn.btn-main:hover {
    background: color-mix(in srgb, var(--ink) 88%, var(--page));
    border-color: color-mix(in srgb, var(--ink) 88%, var(--page));
  }
  button.btn-main:active, .btn.btn-main:active {
    background: color-mix(in srgb, var(--ink) 78%, var(--page));
  }

  button:focus-visible, .btn:focus-visible {
    outline: none;
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 60%, transparent);
  }

  /* Второстепенная кнопка: только волосяная рамка, текст тушью.
     Синим она быть не должна — синий в системе один и означает
     «главное действие»; если им покрасить каждую вторую кнопку, он
     перестаёт что-либо значить. */
  .btn-quiet {
    color: var(--ink);
    border: 1px solid var(--glass-border);
    font-weight: 500;
    background: transparent;
    box-shadow: none;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  .btn-quiet:hover {
    border-color: var(--ink-muted);
    color: var(--ink); filter: none; transform: none; box-shadow: none;
    text-decoration: none;
  }
  .btn-quiet:active { transform: none; box-shadow: none; }

  @media (prefers-reduced-motion: reduce) {
    button, .btn, nav.tabs a, .theme-switch { transition: none; }
  }

  /* --- плитки показателей --- */
  /* Порог 200px подобран так, чтобы пять плиток вставали в один ряд на
     обычном ноутбуке: пятая плитка, свисающая на вторую строку одна,
     выглядит забытой. */
  .tiles { display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); margin-bottom: 14px; }
  .tile .value { white-space: nowrap; /* Нижняя строка плитки прижата к низу: у одних есть «· было 1 471 190»,
     у других только «н/д», и без этого числа стояли на разной высоте. */
font-size: var(--t-display-size); font-weight: var(--t-display-weight); letter-spacing: var(--t-display-track); line-height: var(--t-display-lead); }

  /* Ни одной плитки в одиночестве во втором ряду.
     ``auto-fit`` раскладывает столько колонок, сколько влезло, и на
     любом наборе, где число плиток на единицу больше числа колонок,
     последняя встаёт одна — читается как забытая. Замерено на карточке
     барбершопа: при 1280 пять плиток давали 4 + 1, при 1600 семь давали
     6 + 1.
     Число колонок задаётся по числу плиток — его видно селектором
     ``:has(> :nth-child(N):last-child)``. Значения подобраны замером на
     1280: пять плиток по 187 пикселей вмещают самую длинную сумму
     карточки, семь по 238 — тем более. Шире экран — колонки просто
     растут. */
  .tiles:has(> :nth-child(5):last-child) { grid-template-columns: repeat(5, minmax(0, 1fr)); }
  .tiles:has(> :nth-child(6):last-child) { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .tiles:has(> :nth-child(7):last-child) { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .tiles:has(> :nth-child(8):last-child) { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  /* На узком экране столько колонок не помещается — возвращаем набор
     из трёх и четырёх, они делятся без остатка на те же наборы. */
  @media (max-width: 1100px) {
    .tiles:has(> :nth-child(5):last-child),
    .tiles:has(> :nth-child(6):last-child) { grid-template-columns: repeat(3, minmax(0, 1fr)); }
    .tiles:has(> :nth-child(7):last-child),
    .tiles:has(> :nth-child(8):last-child) { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  }
  @media (max-width: 760px) {
    .tiles:has(> :nth-child(n+5):last-child) { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  }

  /* --- доски первых мест ---
     Не плитка: в плитке одно число, здесь их десять со строкой имени и
     филиала. Ширина 380 подобрана замером на 1280: имя барбера плюс
     самое длинное название точки сети — «FIDEL Электрозаводская ЖК
     "Резиденция Архитекторов"» — требуют 356 пикселей, а при 300 они
     сворачивались в три строки, и доска вырастала вдвое. */
  /* Досок четыре, и раскладка обязана быть чётной.
     ``auto-fit`` давал на обычном ноутбучном окне три колонки, и
     четвёртая доска вставала во втором ряду одна — читается как
     забытая. Поэтому ступени заданы явно: четыре в ряд, когда места
     хватает всем, иначе две на две. Одинокой доски не бывает ни на
     одной ширине. */
  .boards { display: grid; gap: 14px; grid-template-columns: repeat(4, minmax(0, 1fr)); margin-bottom: 14px; }
  @media (max-width: 1700px) { .boards { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
  @media (max-width: 860px) { .boards { grid-template-columns: minmax(0, 1fr); } }
  .boards .tile { text-align: left; }
  /* Имя доски — одна строка. У обычной плитки имени зарезервированы две
     строки (``.tile .name { min-height }``): в ряду плиток имена разной
     длины иначе сдвигали бы числа. У доски под именем не число, а
     пояснение, и резерв оставлял под «ПО СРЕДНЕМУ ЧЕКУ» пустую полосу
     высотой в строку — замер по снимку владельца 03.09.2026. */
  .boards .tile .name { min-height: 0; }
  /* Досок три (доска по выручке наружу не отдаётся), и в сетке на две
     колонки третья стояла одна, в сетке на четыре — оставляла пустую
     четверть. Три доски — три колонки, пока хватает ширины. */
  @media (min-width: 1100px) {
    .boards:has(> :nth-child(3):last-child) { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  }
  /* Пояснение доски идёт сразу под её именем.
     У обычной плитки ``.cmp`` прижата к низу (``margin-top: auto``) —
     там это верно: у одних плиток есть «· было 1 471 190», у других
     нет, и без прижатия числа стояли бы на разной высоте. Здесь ниже
     стоит таблица на десять строк, и то же правило выталкивало
     пояснение в середину доски, оставляя под именем пустую полосу в
     треть экрана. */
  .boards .tile .cmp { margin-top: 0; padding-top: 0; }
  /* Таблица — гибкий ребёнок плитки, и по умолчанию она не ужимается
     ниже своего содержимого: ``min-width`` у элемента флекса равен
     ``auto``. На телефоне это стоило прокрутки вбок — доске нужно было
     411 пикселей при карточке в 351, и весь лист ехал вправо. Ноль
     возвращает ей право сжиматься, а колонки заданы долями и поделят
     то, что есть. */
  .boards table { margin-top: 10px; min-width: 0; }
  /* Свёрток с остальными местами. Подпись — тихая строка, а не кнопка:
     это продолжение той же доски, а не отдельное действие. Вторая
     таблица под ней начинается без верхнего отступа — иначе между
     пятым и шестым местом появлялась щель, которой между другими
     местами нет. */
  .boards .more-places { margin-top: 2px; }
  .boards .more-places > summary {
    cursor: pointer; padding: 6px 6px 2px;
    font-size: var(--t-small-size); color: var(--ink-muted);
    list-style: none;
    /* «Первые десять барберов»: «▾ 5 мест ниже» стояло в 6 px правее
    заголовка доски (поле свёртки 6 px) — теперь на одной линии с ним. */
    padding-left: 0;
    padding-right: 0;
  }
  .boards .more-places > summary::-webkit-details-marker { display: none; }
  .boards .more-places > summary::before { content: "▸ "; }
  .boards .more-places[open] > summary::before { content: "▾ "; }
  .boards .more-places > summary:hover { color: var(--ink); }
  .boards .more-places table { margin-top: 0; }
  /* На узком экране опора сжимается вместе с филиалом: иначе «270
     визитов» держит колонку шире, чем весь телефон. */
  @media (max-width: 560px) {
    .boards td .hint .sup {
      min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    }
  }
  .boards td { padding: 3px 6px; vertical-align: baseline; }
  /* Крайние ячейки — те же 6 px, а не общие 24 у ``td:first-child`` и
     ``td:last-child``: у доски отступ уже даёт сама плитка, а второй
     слой отступа съедал колонку числа. Замер 03.09.2026, окно 1440, три
     доски в ряд: ячейка 98 px, из них 40 — поля, «10 207 ₽» жирным —
     59 px, и число обрезалось многоточием на один пиксель. */
  .boards td:first-child { padding-left: 6px; }
  .boards td:last-child { padding-right: 6px; white-space: nowrap; overflow: visible; text-overflow: clip; min-width: 11ch; }
  /* Сумма места не режется никогда — ни многоточием, ни переносом:
     «148 00…» на доске товаров читалась как другая сумма. */
  .boards col.sum-col { width: 12ch; }

  /* Вторая строка места: филиал и опора. Филиал сжимается многоточием,
     опора — никогда: «10 552 ₽ при 48 визитах» и «10 552 ₽» — это два
     разных утверждения, и второе без первого хвастливо. */
  .boards td.who { overflow: hidden; }
  .boards td.who > a {
    display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .boards td .hint {
    display: flex; gap: 6px; align-items: baseline; font-size: var(--t-small-size);
  }
  .boards td .hint .where {
    min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .boards td .hint .sup { flex: none; color: var(--ink-muted); }
  .boards td .hint .sup::before { content: "·"; margin-right: 6px; }
  /* Первое место выделено весом, а не цветом: цвет означает «хуже»
     (правило 5), и красить лидера зелёным значило бы завести второй
     язык на том же листе. */
  .boards tr.lead td { font-weight: 600; }

  /* Ряд главных показателей: пять плиток в строку.

     Здесь была ошибка, которую видно только на самой большой цифре
     системы. Выручка сети — девять значащих цифр плюс знак рубля;
     «272 233 286 ₽» требует 190 пикселей текста, а в плитке шириной 212
     их оставалось 162, и знак рубля срезало краем карточки.

     Первая попытка — расширить плитки до 240 — вышла хуже: пятая
     переставала помещаться и вставала одна во втором ряду, как
     забытая. Правильно другое: оставить пять в строке, а кегль главного
     числа взять на две ступени меньше и ужать боковые поля. Цифра
     чуть мельче, зато целая и ряд ровный. */
  .tiles.kpi { grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }
  .tiles.kpi .tile { padding-left: 18px; padding-right: 18px; }
  .tiles.kpi .tile .value { font-size: var(--t-display-long); }
  .tiles.kpi .tile .value .unit { font-size: var(--t-head-size); margin-left: 3px; }
  /* На окне уже полутора тысяч ряд снова разваливался: содержимому
     оставалось около 950 пикселей, пять плиток по 190 не помещались, и
     пятая уходила во второй ряд одна — та самая «забытая», против
     которой всё это и писалось. Замер на 1280: 4 + 1.
     Лечится тем же приёмом, что и в первый раз, только на ступень
     дальше: плитка чуть уже, поля чуть теснее, число на ступень мельче.
     Проверено на 1280 — пять в ряд, ни одна сумма не обрезана. */
  @media (max-width: 1440px) {
    .tiles.kpi { grid-template-columns: repeat(auto-fit, minmax(176px, 1fr)); }
    .tiles.kpi .tile { padding-left: 14px; padding-right: 14px; }
    .tiles.kpi .tile .value { font-size: var(--t-display-long); }
  }
  /* Ещё ступень — для узкого ноутбучного окна. */
  @media (max-width: 1260px) {
    .tiles.kpi { grid-template-columns: repeat(auto-fit, minmax(158px, 1fr)); }
    .tiles.kpi .tile { padding-left: 12px; padding-right: 12px; }
    .tiles.kpi .tile .value { font-size: var(--t-title-size); }
  }
  /* Вспомогательный ряд — «Лидер периода», «219 точек» — не крупнее
     главного. Главный ряд пришлось ужать до 26 пикселей ради девятизначной
     выручки, а вспомогательный остался на 30, и глаз читал «Лидера»
     важнее оборота сети: 3 857 200 ₽ крупнее 199 104 546 ₽. Замер
     22.09.2026 на окне 1680. Ступени те же, что у главного ряда, — на
     узком окне они опускаются вместе. */
  .tiles.second .tile .value { font-size: var(--t-display-long); }
  @media (max-width: 1440px) { .tiles.second .tile .value { font-size: var(--t-display-long); } }
  @media (max-width: 1260px) { .tiles.second .tile .value { font-size: var(--t-title-size); } }
  /* Два ровных ряда по три: для наборов из шести плиток с длинными
     суммами. */
  @media (max-width: 860px) {
  }
  .tile {
    padding: var(--s5);
    transition: transform .2s ease, box-shadow .2s ease;
    /* Точка отсчёта для стрелки «→» у плиток-ссылок. Без неё стрелка
       считает координаты от начала документа и под курсором вспыхивает
       в правом верхнем углу страницы — за версту от плитки, на которую
       навели. */
    position: relative;
    display: flex;
    flex-direction: column;
  }
  /* Плитка не приподнимается под курсором: она не кнопка. Реагируют
     только те, по которым щёлкают, — у них меняется граница. */
  a.tile:hover { border-color: var(--ink-muted); }
  /* Подпись показателя — мельче и тише, чем была: на плитке читают
     число, а подпись только называет его. Разница в весе между ними и
     создаёт ту самую иерархию, без которой всё выглядит одинаково
     важным и оттого дешёвым. */
  /* Главный показатель — первая плитка ряда. У неё светящаяся кромка по
     верху и еле заметный тёплый подсвет за числом: на экране должна быть
     ровно одна точка, за которую цепляется глаз, иначе взгляд бродит по
     четырём одинаковым прямоугольникам и не находит главного.

     Акцент один и очень слабый — семь процентов цвета. Дорогой вид
     держится на сдержанности: стоит сделать подсветку заметной, и
     интерфейс сразу становится похож на витрину. */

  .tile .name, .cards .box .name, .outlook-strip .label {
    font-size: var(--t-micro-size); font-weight: var(--t-micro-weight);
    letter-spacing: var(--t-micro-track); line-height: var(--t-micro-lead);
    text-transform: uppercase; color: var(--ink-muted);
    margin-bottom: var(--s3);
  }
  /* Подпись занимает две строки всегда — даже когда ей хватает одной.
     Иначе плитки в ряду разной высоты: «ЗАВЕРШЁННЫЕ ЗАПИСИ» переносится
     на вторую строку, «КЛИЕНТЫ» нет, и число под ними стоит на разном
     уровне. В ряду из пяти это первое, что бросается в глаза, — хотя
     объяснить, что именно не так, трудно.

     Две строки, а не одна с многоточием: обрезать «ЗАВЕРШЁННЫЕ ЗАП…»
     значит спрятать смысл ради ровности. */
  .tile .name {
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
    overflow: hidden; min-height: calc(var(--t-micro-size) * var(--t-micro-lead) * 2);
  }

  .tile .value .unit { font-size: var(--t-head-size); font-weight: 400; color: var(--ink-secondary); margin-left: 4px; letter-spacing: -0.01em; }
  .tile .cmp {
    /* Прижата к низу: у одних плиток есть «· было 1 471 190», у других
       только «н/д», и без этого числа в ряду стояли на разной высоте. */
    color: var(--ink-secondary);
    font-size: var(--t-small-size); line-height: var(--t-small-lead);
    /* И вся нижняя строка плитки переносится, а не уезжает за край. */
    white-space: normal;
    overflow-wrap: anywhere;
    /* Подпись прижата к низу, а место под неё отведено ровно под две
    строки — не меньше и не больше. Без верхней границы одна длинная
    подпись вырастала в три строки, тянула за собой весь ряд, и ряды
    шли ступеньками: 146 пикселей сверху, 158 снизу. Ровность плиток
    важнее, чем возможность дописать в подпись ещё пять слов. */
    margin-top: auto;
    padding-top: 11px;
  }

  /* Итог по сети: в плитке два сравнения, строкой каждое — «к июлю 2026
     ↑ 0,2 % · было …» и «к августу 2025 ↓ 7,6 % · было …». Правило 2
     требует двойного сравнения; до 03.09.2026 лист показывал одно, а
     второе пряталось за переключателем в свёртке. Выбранное (ведёт
     таблицу и вердикт) чуть темнее и жирнее, второе тише. */
  /* Строка сравнения переносится, но только по своим швам. Целиком
     запрет переноса стоил цифр: для идущего месяца подпись длиннее —
     «к 1–6 августа 2026 ↑ 0,8 % · было 58 941 000», — и на снимке
     владельца 07.09.2026 (пять плиток на 1440) хвост «было 58…» уходил
     за правый край карточки. Теперь подпись периода и «· было …» — два
     неделимых куска, а между ними перенос разрешён: число никогда не
     рвётся посередине, а в узкой плитке «было» уходит второй строкой. */
  .tile .cmp .cmp-row { display: block; white-space: normal; }
  .tile .cmp .cmp-row + .cmp-row { margin-top: 3px; }
  .tile .cmp .cmp-row .to { color: var(--ink-muted); white-space: nowrap; }
  .tile .cmp .cmp-row.on .to { color: var(--ink-secondary); font-weight: 600; }
  .tile .cmp .was { color: var(--ink-muted); /* Подпись «было …» в плитке переносится. У «Клиентов» она длинная
   («было 111 434 на 08.2025 · ядро 57 7…») и с nowrap уходила за край
   плитки, обрезанная на полуслове (замер 23.09.2026). */
white-space: normal; }

  /* Шутка дня. Мельче и тише всего на странице: она не спорит с
     цифрами, её видно краем глаза. Курсивом — чтобы сразу читалось как
     реплика, а не как подпись к показателю. */
  .joke {
    margin: 0 0 14px; padding: 0 2px;
    font-size: var(--t-small-size); font-style: italic; line-height: 1.5;
    color: var(--ink-muted); opacity: .9;
  }

  /* Строка «показать все» в длинных таблицах. Тише обычных: это не
     данные, а сообщение о том, что осталось за кадром. */
  /* Искра: год помесячно одной линией внутри строки. Отвечает на
     вопрос, которого в таблице нет, — растёт филиал или падает. Цвет
     говорит направление, но не он один: линия видна и в чёрно-белой
     печати, и тем, кто не различает красное и зелёное. */
  td.spark, th.spark { padding-left: 6px; padding-right: 6px; }
  th.spark { text-align: left; }
  svg.sp { display: block; width: 100%; height: 26px; overflow: visible; }
  svg.sp .l { fill: none; vector-effect: non-scaling-stroke;
  stroke-width: 1.4;
  stroke-linejoin: round;
  stroke-linecap: round;
              }
  svg.sp .a { stroke: none; opacity: .13; }
  svg.sp .dot { stroke: var(--glass); stroke-width: 1.2; vector-effect: non-scaling-stroke; }
  svg.sp.up   .l, svg.sp.up   .dot { stroke: var(--good); }
  svg.sp.up   .a, svg.sp.up   .dot { fill: var(--good); }
  svg.sp.down .l, svg.sp.down .dot { stroke: var(--critical); }
  svg.sp.down .a, svg.sp.down .dot { fill: var(--critical); }
  svg.sp.flat .l, svg.sp.flat .dot { stroke: var(--ink-muted); }
  svg.sp.flat .a, svg.sp.flat .dot { fill: var(--ink-muted); }

  /* Строка свёрнутого хвоста («показать всех») на «Барберах», «Услугах»,
     «Метриках», «Ступенях». Тише обычных: это не строка данных, а
     сообщение о том, что осталось за кадром. Копии в шаблонах удалены
     25.09.2026 — они повторяли это правило дословно. */
  tr.tail-row td {
    background: color-mix(in srgb, var(--ink) 3%, transparent);
    font-size: var(--t-small-size); padding-top: 10px; padding-bottom: 10px;
  }
  tr.tail-row .tail-note { color: var(--ink-muted); }

  /* Пока идёт выгрузка, числа посчитаны по половине залитой таблицы.
     Они приглушаются, а сравнение с прошлым периодом не показывается
     вовсе: сравнивать неполный отрезок с неполным — это шум, из которого
     получается «рост» на ровном месте. Полоса «идёт обновление» вверху
     была и раньше, но цифры под ней притворялись готовыми — притворяться
     они перестают. */
  .tiles.loading .tile .value { opacity: .45; }
  .tiles.loading .tile .name { opacity: .6; }
  /* Строка «данные ещё грузятся — 167 из 214» не помещалась в плитку и
     обрезалась на «167 из 21». Виноват ``inline-flex``: он не даёт
     строке переноситься, а плитка в ряду из пяти — двести пикселей.
     Теперь переносится по словам и целиком видна. */
  .tile .cmp .partial {
    display: flex; align-items: flex-start; gap: 6px;
    font-size: var(--t-small-size); color: var(--ink-muted); font-style: italic;
    line-height: 1.35;
  }
  .tile .cmp .partial::before { margin-top: 5px; flex: 0 0 auto; content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--accent); opacity: .55; animation: partial-pulse 1.4s ease-in-out infinite; }

  @keyframes partial-pulse { 0%,100% { opacity: .2 } 50% { opacity: .75 } }
  @media (prefers-reduced-motion: reduce) {
    .tile .cmp .partial::before { animation: none; }
  }

  /* Плитка-ссылка. Число в ней красилось в синий, потому что это ссылка,
     и ряд превращался в три огромные синие цифры подряд — так выглядит
     реклама, а не отчёт. Цвет здесь ничего не сообщает: щёлкается вся
     плитка целиком, и понятно это по тому, что она приподнимается под
     курсором, а не по окраске числа.

     Взамен — стрелка в углу: тихая, появляется под курсором. */
  a.tile { display: flex; flex-direction: column; text-decoration: none; }
  a.tile .value, a.tile .name, a.tile .cmp { color: inherit; }
  a.tile .value { color: var(--ink); }
  a.tile .name { color: var(--ink-muted); }
  a.tile .cmp { color: var(--ink-secondary); }
  /* Плашка, называющая ухудшение. Правило 5: красным красится «хуже», а
     не «отрицательное число», поэтому класс вешает расчёт — он знает,
     что плохо именно для этого числа, — а не шаблон по минусу. Норма
     остаётся чёрной: покрась её, и красный перестанет что-либо значить.

     Цвет только на числе. Красная рамка вокруг всей плитки уводит глаз
     на плитку целиком, а плохое здесь — одно число в ней. */
  .tile.worse .value, a.tile.worse .value { color: var(--critical); }
  a.tile::before {
    content: "→"; position: absolute; top: 17px; right: 19px;
    font-size: var(--t-body-size); line-height: 1; color: var(--ink-muted);
    opacity: 0; transform: translateX(-3px);
    transition: opacity .18s ease, transform .18s ease;
  }
  a.tile:hover::before, a.tile:focus-visible::before {
    opacity: .85; transform: translateX(0);
  }

  /* Число в подписи не рвётся посередине: «было 80 737» либо стоит в
     строке целиком, либо целиком уходит на вторую. Обрезанная посреди
     цифры сумма выглядит как ошибка вёрстки и читается как другая сумма. */
  .tile .cmp span { white-space: nowrap; }

  /* Стрелка и знак несут смысл сами: цвет усиливает, но не остаётся
     единственным носителем значения.

     Стрелка вынесена в отдельную ячейку постоянной ширины, а цифры идут
     моноширинными — иначе «↑ +4,0%» и «↓ −13,2%» встают по-разному и
     колонка выглядит рваной. */
  /* Изменение в процентах. Стрелка и число стоят в двух колонках
     постоянной ширины: иначе «↑ +4,0%» и «↓ −13,2%» встают уступом и
     столбец выглядит рваным. */
  .delta {
    display: inline-grid; grid-template-columns: 0.9em auto; gap: 3px;
    align-items: baseline; justify-items: end;
    font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap;
  }
  .delta i { font-style: normal; text-align: center; justify-self: center; }
  .delta.up { color: var(--good); }
  .delta.down { color: var(--critical); }
  .delta.flat { color: var(--ink-muted); }
  /* В колонке таблицы прижимаем к правому краю, чтобы знаки процента
     выстроились в одну линию. */
  td.num .delta { justify-content: end; }

  /* --- карточки с графиками ---

     Один и тот же приём на двух страницах: «Динамика сети» показывает так
     показатели сети, «Услуги» — доли услуг. Правила общие, чтобы карточки
     на обеих страницах были одного размера и вели себя одинаково. */
  /* Динамика сети: все показатели на одном листе.

     Карточка на каждый показатель — крупная цифра последнего месяца,
     изменение и график. Наведение на месяц даёт его значение: график
     поделён невидимыми колонками, каждая носит свою карточку. Всё на
     CSS, без скриптов — политика безопасности их запрещает.

     Ниже одна таблица со всеми показателями сразу: карточки отвечают
     «как менялось», таблица — «сколько именно». */
  /* Девять карточек — три в ряд, три ряда: сетка ровная, и все девять
     показателей видно на одном экране без прокрутки. Высота карточек
     одинаковая, иначе ряды съезжают и лист выглядит собранным на глазок. */
  .charts-grid {
    display: grid; gap: 8px; margin-bottom: 14px;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    align-items: stretch;
  }
  /* Два графика в ряд, а не три: когда их два, каждый вдвое шире и
     линия читается точнее. */
  .charts-grid.two { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .metric-card { padding: 9px 14px 7px; display: flex; flex-direction: column; }
  .metric-card h3 {
    margin: 0 0 1px; font-size: var(--t-small-size); font-weight: 600; height: 16px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    color: var(--ink-secondary); letter-spacing: -0.01em;
  }
  .metric-card .amount {
    font-size: var(--t-head-size); font-weight: 600; letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums;
  }
  /* Шапка карточки показателя: доля, изменение и два счётчика в строку.
     ``nowrap`` держал их вместе, а в просторной плотности строка
     требовала 283 пикселя при карточке в 275 — последний счётчик
     вылезал за край. Лишнее теперь прячется многоточием: главное здесь
     первое число, оно набрано крупно и стоит слева. */
  .metric-card .head {
    display: flex; align-items: baseline; gap: 9px;
    white-space: nowrap; margin-bottom: 4px; height: 24px;
    overflow: hidden; text-overflow: ellipsis;
  }
  /* Высота графика — от его собственной пропорции. Жёсткие 48 пикселей
     здесь и растяжение по ширине как раз и деформировали холст; заодно
     подписи усыхали до нечитаемых. */
  .metric-card .plot { margin-top: 2px; }

  /* Сводная таблица: одиннадцать колонок должны уместиться в ширину
     листа. Поэтому раскладка жёсткая (колонки делят ширину поровну, а
     не растягиваются под содержимое), отступы уже обычных, а заголовки
     переносятся по словам вместо того, чтобы раздвигать колонку. */
  .sweep table { table-layout: fixed; }
  .sweep th { white-space: normal; font-size: var(--t-micro-size); letter-spacing: .03em; }
  .sweep td { font-size: var(--t-small-size); }
  .sweep col.month { width: var(--col-num); }
  /* «Барбершопов» — одно слово в 116 пикселей, переносить его негде, а
     общая ширина числовой колонки 108. В плотном режиме заголовок
     терял хвост. Своё значение здесь оправдано: ширину диктует не
     число, а длина слова над ним. */
  .sweep col.branches { width: 122px; }

  /* Здесь жила своя рисовалка графиков: холст 100×34 без шкалы, поверх
     него полсотни прозрачных столбцов с всплывающими подсказками. Она
     умела показать «выше или ниже» и не умела показать «насколько» —
     ни одной цифры на графике не было. Заодно её подсказка вылезала за
     край окна и давала горизонтальную прокрутку на всю страницу.

     Теперь везде один график — ``_plot.html``: со шкалой, подписанными
     уровнями и месяцами, с подсказкой средствами самого SVG. */

  /* Заголовок-ссылка: щелчок меняет порядок сортировки. Выглядит как
     обычный заголовок — подчёркивание и цвет появляются при наведении,
     чтобы шапка не выглядела гирляндой ссылок. */
  /* Заголовок-ссылка ничем не отличается от обычного: сортировка — это
     свойство таблицы, а не признак важности столбца. Перенос разрешён,
     иначе «Вернулись, 90 дн.» вылезало за свою колонку. */
  /* Заголовок с сортировкой — одной строкой: «Выручка, ₽» с уехавшим
     вниз рублём ломала линию шапки. */
  /* Заголовок-ссылка переносится вместе с заголовком.
     Стояло ``white-space: nowrap`` — чтобы стрелка сортировки не
     отрывалась от подписи. Отрыва она и правда не давала, но заодно
     запрещала перенос всей подписи: обход 27.08.2026 на «Оборотах» —
     «Средний чек, ₽» требовал 124 пикселя в колонке шириной 115,
     «Изменение» 97 в 92, и оба выпирали за свои клетки, растягивая
     таблицу на девять пикселей за карточку.
     Стрелка держится у последнего слова сама: она лежит в своём
     ``span`` внутри flex-строки и переносится вместе с ним. */
  th a.sort {
    color: inherit; text-decoration: none; display: inline-flex; gap: 4px;
    align-items: baseline; flex-wrap: wrap; white-space: normal;
  }

  th a.sort:hover { color: var(--accent); text-decoration: none; }
  th a.sort.on { color: var(--ink); }
  th a.sort .arrow { font-size: var(--t-micro-size); color: var(--accent); }
  th.num a.sort { justify-content: flex-end; }

  /* Прогноз на текущий месяц: одна строка — сумма, факты, сравнения.
     Источник у него другой (YClients), поэтому карточка отдельная: в
     ряду с плитками «Оборотов» её путали бы с фактом. */
  .outlook { padding: 14px 20px 12px; margin-bottom: 14px; }
  .outlook .head { display: flex; gap: 28px; align-items: baseline; flex-wrap: wrap; }
  .outlook .label { font-size: var(--t-small-size); color: var(--ink-secondary); }
  .outlook .sum {
    font-size: var(--t-title-size); font-weight: 600; letter-spacing: -.022em;
    font-variant-numeric: tabular-nums;
  }
  /* Факты — столбиком, а не вразброс по всей ширине: три обрывка текста,
     растянутые по двум тысячам пикселей, читались как случайные слова. */
  .outlook details.all { margin-top: 10px; }
  .outlook details.all summary {
    cursor: pointer; font-size: var(--f-note); font-weight: 500; color: var(--ink-secondary);
  }
  .outlook details.all ol {
    margin: 10px 0 0; padding-left: 28px; columns: 2 420px; column-gap: 32px;
    font-size: var(--f-note); line-height: 1.6;
  }
  .outlook details.all li { break-inside: avoid; }
  .outlook details.all .why { color: var(--ink-muted); }
  .outlook .facts {
    display: flex; flex-direction: column; gap: 5px; font-size: var(--t-body-size);
    color: var(--ink-secondary);
  }
  .outlook .deltas {
    display: flex; gap: 18px; flex-wrap: wrap; font-size: var(--t-body-size);
    color: var(--ink-secondary); margin-left: auto;
  }

  /* «Под угрозой»: причин четыре, и в свободном потоке они вставали
     лесенкой — вторая строка начиналась не там, где первая. Две ровные
     колонки, кнопка прижата к правому краю и выровнена по середине
     карточки, а не по первой строке текста. */
  .outlook.risk .head { align-items: center; gap: 24px; }
  .outlook.risk .facts {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    align-items: start;
    display: flex;
    flex-direction: column;
  }
  /* Причину не обрезаем: «выручка ниже прош…» не сообщает ничего.
     Строка переносится внутри своей колонки, колонки остаются ровными. */
  .outlook.risk .facts > span { display: block; min-width: 0; }
  .outlook.risk .deltas { align-self: center; }
  @media (max-width: 900px) {
    .outlook.risk .facts { grid-template-columns: minmax(0, 1fr); }
  }
  .outlook .hint {
    margin: 10px 0 0; font-size: var(--t-small-size); color: var(--ink-muted);
    border-top: 1px solid var(--hairline); padding-top: 9px;
  }

  /* Печать: лист А4 без навигации и фильтров. Отчёт носят на встречи, и
     распечатанная страница с кнопками и выпадающими списками выглядит как
     скриншот, а не как документ. */
  @media print {
    @page { size: A4 landscape; margin: 12mm; }
    body { background: #fff; color: #000; }
    .wrap { max-width: none; padding: 0; }
    body::before, body::after { display: none; }
    nav.tabs, .filters, .theme-switch, .btn, .btn-plain, .btn-exit,
    button, .tiles-note, #tiles-progress { display: none !important; }
    .card, tr, .tile { break-inside: avoid; }
    .wrap { display: block !important; }
    nav.tabs, .nav-toggle, .canchors, .freshness .away { display: none !important; }
    .card { box-shadow: none !important; }
    .card {
      background: #fff; box-shadow: none; border: 1px solid #d5d5d8;
      break-inside: avoid; backdrop-filter: none;
    }
    header.top { border: 0; box-shadow: none; background: #fff; }
    a { color: #000; text-decoration: none; }
    th { background: #f2f2f2 !important; color: #000; }
    tbody tr:hover td { background: transparent; }
    .up1, .up2, .up3 { background: #eef7ee !important; }
    .dn1, .dn2, .dn3 { background: #fbeeee !important; }
    .scroll { overflow: visible; }

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

       Стекло, тени и полупрозрачность на бумаге дают серую грязь;
       убираем всё до чёрного по белому. Экранные подсказки, шутка дня и
       навигация не печатаются: на листе они превращаются в мусор,
       который потом объясняют. */
    /* ``.suggest`` — выпадающая подсказка поиска, её рисует скрипт;
       раньше здесь стоял ``.search-box``, которого в разметке нет
       вовсе, и подсказка на бумагу всё-таки попадала. */
    .joke, .hint, .tail-row, .views, .suggest { display: none !important; }
    a[href]:after { content: none !important; }

    /* Каждая доска — со своей страницы. Разорванный посередине топ-20
       нельзя ни прочитать, ни подшить. */
    .board { break-inside: avoid; page-break-inside: avoid; margin-bottom: 10mm; }
    .board + .board { break-before: page; page-break-before: always; }

    /* Шапка таблицы повторяется на каждой странице, если доска всё же
       не поместилась. */
    thead { display: table-header-group; }
    tfoot { display: table-footer-group; }
    tr { break-inside: avoid; page-break-inside: avoid; }

    /* На бумаге тонкая линия исчезает — границы чуть плотнее. */
    table, th, td { border-color: #c9c9cc !important; }
    caption { padding: 0 0 6px; font-size: 14pt; }
    body { font-size: 10.5pt; }

    /* Подпись «когда собрано» нужна именно на бумаге: лист живёт
       отдельно от системы, и через месяц по нему спросят, за какой он
       период. На экране это лишняя строка, на бумаге — обязательная. */
    .print-stamp { display: block !important; }
  }

  /* Штамп печати: на экране его нет, он появляется только на листе. */
  .print-stamp { display: none; font-size: var(--t-micro-size); color: #444; margin-bottom: 8px; }

  /* --- таблицы --- */
  table {
    width: 100%; border-collapse: collapse; font-size: var(--t-body-size);
    font-variant-numeric: tabular-nums lining-nums;
    font-feature-settings: "tnum" 1, "lnum" 1;
  }
  /* Ширина числовой колонки — одна на всё приложение. */
  /* Размеры каркаса объявлены один раз: их знает и раскладка, и кнопка
     сворачивания панели. Пока ширина панели была вписана числом в двух
     местах, кнопка садилась мимо границы. */
  :root { --col-num: 108px; --wrap-max: 1560px; --wrap-pad: 24px; --nav-width: 208px; }
  /* Заголовок таблицы — та же порода, что и заголовок страницы, только
     на ступень ниже: плотный, тёмный, с коротким просветом до пояснения.
     Пояснение набрано заметно мельче и светлее — иначе два абзаца
     одинаковой силы спорят друг с другом, и лист выглядит черновиком. */
  caption {
    text-align: left;
    font-size: var(--t-head-size); font-weight: var(--t-head-weight);
    letter-spacing: var(--t-head-track); line-height: var(--t-head-lead);
    font-family: -apple-system, "SF Pro Display", system-ui, "Segoe UI", sans-serif;
    padding: var(--s5) var(--s5) var(--s3);
  }
  caption .hint {
    display: block; margin-top: var(--s2);
    font-size: var(--t-small-size); font-weight: var(--t-small-weight);
    letter-spacing: var(--t-small-track); line-height: var(--t-small-lead);
    color: var(--ink-muted);
    /* Строка длиннее девяноста знаков не читается: глаз теряет начало
       следующей. Это не вкусовое ограничение, а типографская мера. */
    max-width: 90ch;
  }
  th, td {
    text-align: left; vertical-align: baseline;
    border-bottom: 1px solid var(--hairline);
    font-size: var(--t-body-size); line-height: var(--t-body-lead);
  }
  /* Первая колонка отступает от края чуть сильнее: у таблицы появляется
     поле, и она перестаёт «упираться» в стенку карточки. */  /* Цифры темнее текста вокруг: в таблице читают именно их. Правило
     только для ячеек, но не для шапки — иначе числовые заголовки
     получались чёрными, а текстовые серыми, и шапка выглядела наполовину
     недокрашенной. Особенно заметно это было в светлой теме. */
  td.num { color: var(--ink); }
  th.num { white-space: normal; }
  th {
    /* Было 10,5 пикселя светло-серым — подпись, которую невозможно
       прочесть, не приблизившись к экрану. Прописные буквы и без того
       читаются медленнее строчных, и мельчить их нельзя. */
    /* Заголовок переносится, а не вылезает. С ``nowrap`` длинные подписи
       вроде «Вернулись, 90 дн.» и «Средний чек, ₽» вылезали за свою
       колонку и наезжали на соседнюю — при том, что места на две строки
       в шапке достаточно. */
    white-space: normal; hyphens: none;
    padding-top: 13px; padding-bottom: 11px;
    position: sticky; top: 0; z-index: 2;
    /* Тонкая тень под шапкой: при прокрутке видно, что она «висит»
       над строками, а не является первой строкой таблицы. */
    text-transform: none;
    letter-spacing: 0;
    font-size: var(--t-small-size);
    font-weight: 600;
    box-shadow: inset 0 -.5px 0 var(--hairline);
  }
  /* Числовые колонки: моноширинные цифры, прижаты вправо, не переносятся.
     Моноширинность обязательна — иначе «1» уже «8», разряды в соседних
     строках не совпадают по вертикали и колонка «плывёт». */
  td.num, th.num { font-variant-numeric: tabular-nums;
    text-align: right;
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1, "lnum" 1;
    /* Числа не переносятся никогда — «336 639 891» на двух строках
       читать невозможно. Переносится только заголовок. */
    white-space: nowrap;
    letter-spacing: -0.004em;
    width: var(--col-num);
    min-width: var(--col-num);
  }
  /* Столбец «Изменение» — единственное место, где цвет говорит о
     динамике. Раньше тем же цветом красилась и выручка, и глаз читал
     красный брусок как ошибку в данных. */
  td.num.change { font-variant-numeric: tabular-nums; }

  /* Состояние «ничего не нашлось». Пустая таблица с одной шапкой
     выглядит поломкой: непонятно, отчёт не посчитался или в срезе
     правда никого нет. Здесь всегда написано, что случилось и что
     сделать, — и это одна строка, а не пустота. */
  td.empty {
    padding: 34px 20px; text-align: center; color: var(--ink-secondary);
  }
  td.empty b { display: block; font-size: var(--t-head-size); font-weight: 600; color: var(--ink); margin-bottom: 6px; }
  td.empty span { display: block; font-size: var(--t-body-size); color: var(--ink-muted); max-width: 62ch; margin: 0 auto; }

  /* Числовые колонки прижимаются друг к другу и к правому краю, а весь
     свободный простор достаётся первой — с названием. Иначе на широком
     экране пять колонок растягиваются по всей ширине, и глазу приходится
     ехать через полэкрана от месяца к сумме.
     ``width: 1%`` — обычный приём: браузер сжимает такую колонку до
     содержимого и отдаёт остаток соседям. */
  /* Ширина у всех числовых колонок одна и та же — и внутри таблицы, и
     между страницами. Раньше «Кресел» была узкой, «Изменение» широкой, и
     соседние таблицы выглядели собранными на глазок. Одно число в
     переменной задаёт сетку всему приложению. */
  td.num, th.num { padding-left: 16px;
  }
  /* Классы оставлены: разметка ими размечена, а ширина теперь общая. */
  td.num.tight, th.num.tight, td.num.change, th.num.change {
    width: var(--col-num); min-width: var(--col-num);
  }
  /* Название не должно расползаться на весь экран: длинные строки
     читаются хуже коротких. */
  td:first-child:not(.num), th:first-child:not(.num) { max-width: 460px; }
  tbody tr:last-child td { border-bottom: 0; }
  /* Заливки через строку больше нет: она спорила с окраской отклонений —
     бледно-зелёная клетка на сером фоне выглядела иначе, чем на белом, и
     сравнивать цвета глазами было нельзя. Строки разделяет волосяная
     линия, а под курсором строка подсвечивается целиком. */
  tbody tr:hover td {
    background: color-mix(in srgb, var(--accent) 6%, transparent);
  }
  tbody tr:hover td:first-child {
    box-shadow: inset 3px 0 0 color-mix(in srgb, var(--accent) 55%, transparent);
  }
  /* Окраска отклонений. Пороги в процентах: 3, 10, 25 — меньше трёх
     не красим, это шум месяца, а не событие.

     Заливка во всю ячейку годится только для плотной матрицы, где она
     работает как тепловая карта. В обычной таблице такая полоса режет
     строку пополам, поэтому там значение оборачивается в «пилюлю» —
     цветной фон по размеру самого числа. */
  /* Плашка не растит строку. Отступы сверху и снизу добавляли к высоте
     ячейки семь пикселей, и строка с плашкой становилась выше соседних:
     в «Учёте» так выбивался «Грозный» с пометкой «на отключение».
     Высота задана явно, отступы только по бокам. */
  .chip {
    display: inline-block; padding: 0 10px; border-radius: 999px;
    font-weight: 600; font-variant-numeric: tabular-nums;
    min-width: 92px; text-align: right;
    line-height: 20px; vertical-align: middle;
  }
  .chip.up1, .chip.up2, .chip.up3 { color: var(--good); }
  .chip.dn1, .chip.dn2, .chip.dn3 { color: var(--critical); }
  /* Плашке первая ступень заливку сохраняет. В матрице заливка занимает
     всю клетку и, повторённая сотню раз, становится фоном; плашка —
     короткая метка на пару слов, и цвет на ней остаётся меткой. */
  .chip.up1 { background: color-mix(in srgb, var(--good) 11%, transparent); }
  .chip.dn1 { background: color-mix(in srgb, var(--critical) 11%, transparent); }
  .chip.flat { color: var(--ink-muted); background: color-mix(in srgb, var(--ink) 5%, transparent); }

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

     Осталось две ступени: заметное отклонение и сильное. Цвет теперь
     означает «сюда посмотри», а не «здесь есть число». */
  .up2 { background: color-mix(in srgb, var(--good) 14%, transparent); }
  .up3 { background: color-mix(in srgb, var(--good) 26%, transparent); }
  .dn2 { background: color-mix(in srgb, var(--critical) 14%, transparent); }
  .dn3 { background: color-mix(in srgb, var(--critical) 26%, transparent); }

  /* Долг — исключение, и вот почему.
     Правило выше писалось для таблиц выручки: там первая ступень значит
     «отклонение в пределах обычного», и красить её незачем. В долгах
     смысл другой — первая ступень значит «долг есть», а не «долг гуляет».
     Двадцать пять тысяч рублей выглядели ровно как ноль: ни цвета, ни
     пометки. Здесь заливка слабее второй ступени, но она есть. */
  table.debts .dn1 { background: color-mix(in srgb, var(--critical) 7%, transparent); }

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

     Цвет ушёл в фон и в кромку, текст остался чернильным: пять красных
     надписей подряд читаются как авария, хотя половина из них — «долг
     10 500 ₽». Тяжесть признака показывает насыщенность кромки. */
  .reason {
    display: inline-block; margin: 2px 5px 2px 0; padding: 3px 9px;
    border-radius: var(--r-s); font-size: var(--t-small-size); line-height: 1.4;
    color: var(--ink); white-space: nowrap;
    background: color-mix(in srgb, var(--critical) 9%, transparent);
    border: 1px solid color-mix(in srgb, var(--critical) 18%, transparent);
  }
  .reason.w3 {
    border-color: color-mix(in srgb, var(--critical) 48%, transparent);
    background: color-mix(in srgb, var(--critical) 15%, transparent);
    font-weight: 600;
  }
  /* Ячейки этой таблицы выравниваются по верху: в строке с пятью
     причинами название иначе уезжало к середине столбца. */
  td.reasons, tr:has(td.reasons) td { vertical-align: top; }

  /* Прочерк вместо значения не должен выглядеть как данные — ни в
     ячейке, ни в плитке. Правило общее: «пусто» и «ноль» система
     обязана различать одинаково всюду, а не только в таблицах. */
  .none, td .none { color: var(--ink-muted); font-weight: 500; }
  .tag { font-size: var(--t-micro-size); color: var(--ink-muted); }
  /* Причина вместо числа переносится по словам. Числовым колонкам
     переносы запрещены — «33 452 770» в две строки не число; но
     «с прошлых лет» просило 138 пикселей в колонке на 124 и теряло
     последнее слово. Две строки здесь честнее обрезанной фразы. */
  td.num .none.wrapped {
    white-space: normal; display: inline-block; line-height: 1.15;
  }
  /* Маленькая таблица на широком экране растягивается, и между
     подписью и числом остаётся полстраницы пустоты. Такой таблице
     ширину задаём по содержимому. */
  /* Таблица занимает всю ширину листа, а колонки делятся равномерно:
     справа не остаётся пустого поля, но и колонки не разъезжаются каждая
     по-своему. Числовым задан общий минимум (--col-num), поэтому суммы
     стоят ровно; свободное место достаётся текстовым колонкам — названию
     и городу, которым оно и нужно. */
  table.fit { width: 100%; table-layout: fixed; }
  /* Заголовок колонки переносится, а не обрубается.
     Здесь стоял ``nowrap`` ради ровной линии шапки — и он же делал
     подписи нечитаемыми. Замер на «Загрузке и потерях»: «Визитов на
     кресло в день» требует 230 пикселей, колонка даёт 115, и в шапке
     стояло «ВИЗИТОВ…». Что это за колонка, узнать было неоткуда:
     подсказки у заголовков нет, а значение подписи — единственное, что
     объясняет столбец цифр.
     У строк с данными многоточие оправдано: там полное значение
     показывает подсказка, и ровная высота строк важнее. Шапка одна на
     таблицу, две строки в ней ничего не ломают. */
  /* Заголовок переносится по словам — и только по словам.
     ``hyphens: auto`` тут стоял полдня и был ошибкой: на узкой колонке
     браузер рубил слова по слогам, и шапка «Барберов» превращалась в
     «ВИ-ЗИ-ТОВ» и «УСЛУ-ГИ, ₽» — три строки высотой 89 пикселей вместо
     одной. Читать такое нельзя, а выглядит как поломка.
     Настоящая беда была не в переносах, а в узких колонках: широкой
     таблице надо давать ``min-width`` и возить её в коробке, а не
     ужимать до нечитаемого. См. ``table.fit.wide`` ниже. */
  table.fit th { white-space: normal; hyphens: none; }
  /* Свёрток «Откуда цифры» стоит внутри подписи таблицы и меряется по
     ней. При просторной плотности подпись шире на четыре пикселя, чем
     коробка, — и стрелка свёртка вылезала за карточку. Пусть занимает
     ровно то, что есть. */
  caption .how, caption .how > summary { max-width: 100%; }

  /* Широкая таблица не ужимается до нечитаемого — она возится вбок.
     Двенадцать столбцов «Барберов» требуют 1 150 пикселей; на окне
     1280 контенту достаётся 990, и колонки резали числа: «1 533 998»
     в 69 пикселях, «5 703» в 69, «28 %» в 59. Ширина задана снизу,
     карточка ``.scroll`` возит остаток. */
  /* «Барберы» — одиннадцать столбцов, «Обороты» — тоже одиннадцать, но
     уже. Ширина задаётся каждой своя. */
  table.fit.wide { min-width: 1150px; }
  /* 1 150 — ширина листа на окне 1 440. При 1 200 таблица уезжала вбок
     на 50 пикселей, и ранг «ПРЕМИУМ ≠ БАРБЕР» (175) резался в 168
     (замер 23.09.2026). */
  table.fit.staff.wide { min-width: 1150px; }
  /* «Учёт»: девять столбцов, из них шесть числовых. Заголовки
     «Маркетинг, ₽» (98) и «Собираемость» (123) не умещались в 87 и 107
     пикселей, которые доставались им на окне 1280. */
  table.fit.debts.wide { min-width: 1130px; }
  /* «Клиенты»: одиннадцать столбцов, из них шесть окон возвратности.
     Заголовок окна — «За полгода», 79 пикселей неразрывного слова; на
     окне 1100 колонке доставалось 67, и подпись обрезалась во всех
     шести сразу. Таблица целиком просит 1 000. */
  table.fit.retention.wide { min-width: 985px; }
  /* «Услуги» и «Загрузка»: семь и девять столбцов. На окне 1280 они
     помещались, на 1100 — уже нет: «Барбершопов» просило 116 пикселей
     при 102, «Состоялось» — 106 при 87, «Кресел» — 67 при 55. Девятьсот
     сорок — та ширина, на которой в обеих плотностях не режется ничего;
     до окна 1220 прокрутка вбок не появляется вовсе. */
  table.fit.load.wide, table.fit.inspections.wide,
  table.fit.milestones.wide, table.fit.wide960 { min-width: 960px; }
  /* «Услуги» — на сорок пикселей уже прочих: их карточка сама по себе
     948 в просторном режиме, и общие 960 давали боковую прокрутку на
     двенадцать пикселей там, где всё помещалось. */
  table.fit.services.wide { min-width: 940px; }

  /* Содержимое ячеек — по вертикальному центру строки. */
  table.fit td { vertical-align: middle; /* Только ячейки: шапке перенос разрешён выше, и это правило её больше
     не перебивает. Заголовку, которому перенос вреден, его отменяет
     ``th.one-line`` — тем же способом, что и ячейке. */
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  /* Все строки одной высоты — иначе таблица выглядит «кривой».
     И это не про выравнивание колонок: колонки-то ровные. Дело в том,
     что одна ячейка переносит текст на вторую строку и тянет за собой
     всю строку. В «Учёте» так расходились 44, 64 и 70 пикселей, в
     «Барберах» — 56 и 65. Глаз ловит сбитую линию, а причину не видит,
     и вся таблица читается как небрежная.
     Поэтому по умолчанию — одна строка, лишнее многоточием, полное
     значение в подсказке. Перенос возвращается там, где он осмыслен:
     ячейке с классом ``wrap`` и строке во всю ширину (пояснения,
     «данных нет», свёрнутый хвост списка). */

  table.fit td.wrap, table.fit td[colspan] {
    white-space: normal; overflow: visible;
  }
  /* Колонка, которая обязана держать строку в одну линию: длинное имя
     обрезается многоточием, а целиком видно при наведении. Разная
     высота строк — первое, что выдаёт неаккуратную таблицу. */
  table.fit td.one-line, table.fit th.one-line, td.one-line, th.one-line {
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  /* Ширины задаёт colgroup таблицы — числовым колонкам общие правила
     ширины здесь только помешали бы. */
  /* Отступ у числовых колонок общий, как у всех: своё значение здесь
     давало шесть разных отступов в одной таблице, и колонки визуально
     стояли неровно, хотя ширины были верные. */
  table.fit td.num, table.fit th.num,
  table.fit td.num.tight, table.fit th.num.tight,
  table.fit td.num.change, table.fit th.num.change {
    width: auto; min-width: 0;
  }
  table.fit td:first-child:not(.num), table.fit th:first-child:not(.num) {
    max-width: none;
  }
  table.compact { width: auto; min-width: 460px; }
  table.compact td:first-child, table.compact th:first-child { padding-right: 40px; }

  /* Боковая прокрутка — по умолчанию. Скрипт ``sticky.js`` меряет
     каждую таблицу и уточняет: помещается — прокрутка снимается вовсе,
     и шапка липнет к окну; не помещается — обёртка получает высоту по
     окну, и шапка липнет к её верху. Подробности в самом скрипте. */
  .scroll { overflow-x: auto; }
  .scroll.fits { overflow: visible; }
  .scroll.boxed { overflow: auto; }
  .note { color: var(--ink-secondary); font-size: var(--t-body-size); margin: 12px 6px 0; }
  /* Оговорка плашки («сеть задана карточкой») — по левому краю
     заголовка, а не с отступом абзаца: стояла на 4 пикселя правее
     «№ 1 · ТОПГАН» строкой выше и читалась как опечатка вёрстки. */
  .note.plate-note { margin-left: 0; margin-right: 0; }
  /* «Обе сети» там, где лист считает одну сеть: на месте, но погашена. */
  .views.brands > span.off {
    padding: 5px 13px; border: 1px solid transparent; white-space: nowrap;
    font-size: var(--f-body); font-weight: 500;
    color: var(--ink-muted); opacity: .5; cursor: not-allowed;
  }

  /* --- график --- */
  /* Линия и сетка. Сетка бледная настолько, чтобы её было видно и она
     не спорила с линией: её задача — дать шкалу, а не рисунок. */
  /* Высота — от ширины, по пропорции холста. Жёсткая высота вместе с
     растяжением по ширине и деформировала подписи. */
  .plot { display: block; width: 100%; height: auto; overflow: visible; }
  .plot-grid { stroke: var(--hairline); stroke-width: 1; }
  /* Кегль наследуется от холста: он задаётся в ``_plot.html`` и зависит
     от размера графика. Жёсткое значение здесь означало бы, что в
     плитке буквы усохнут вместе с холстом — именно так с графиков и
     «слетели цифры». */
  /* Текущее значение у конца линии: единственная жирная подпись на
     холсте — глаз находит её первой. Белая обводка отделяет цифру от
     линии и сетки под ней. */
  .plot-now {
    font-weight: 600; font-size: 1.05em;
    paint-order: stroke; stroke-width: 3px;
    fill: var(--ink);
    stroke: var(--paper);
  }
  .plot-level, .plot-month {
    font-size: inherit; fill: var(--ink-muted);
    font-variant-numeric: tabular-nums;
  }
  .plot-level { text-anchor: end; }
  /* Заливка под линией — открытой терракотой, а не затемнённой: на
     двенадцати процентах прозрачности тёмный вариант даёт грязноватое
     пятно, светлый — тёплую тень. */  .plot-line {
    fill: none;
    stroke-linejoin: round; stroke-linecap: round;
    stroke: var(--chart);
    stroke-width: 1.75;
  }
  /* Прошлый период: пунктиром и бледно. Он для сравнения, а не для
     чтения по точкам — иначе спорит с основной линией. */
  .plot-past {
    fill: none;
    stroke-linejoin: round; stroke-linecap: round;
    stroke: var(--ink-muted);
    stroke-width: 1.25;
    stroke-dasharray: 3 4;
    opacity: .7;
  }  /* Незаконченный месяц — полая точка: сравнивать его с полными нельзя,
     и это должно быть видно, а не написано мелким шрифтом внизу. */
  .plot-dot.partial { stroke-width: 2; opacity: 1; fill: var(--paper); stroke: var(--chart); }
  /* Прозрачная полоса ловит подсказку движением к месяцу, а не
     попаданием в точку радиусом три пикселя. */
  .plot-hit { fill: transparent; }  .plot-empty {
    margin: 0; padding: 28px 0; text-align: center;
    font-size: var(--t-body-size); color: var(--ink-muted);
  }

  /* --- пустые состояния --- */
  /* Пустая таблица не отвечает на вопрос «поломка или правда нет
     данных». Здесь отвечает: что именно пусто, почему и что сделать. */
  .nothing {
    display: flex; flex-direction: column; align-items: center; gap: 8px;
    padding: 40px 24px; text-align: center;
  }
  .nothing .mark {
    width: 34px; height: 34px; border-radius: 50%;
    border: 1.5px dashed var(--glass-border);
    display: flex; align-items: center; justify-content: center;
    color: var(--ink-muted); font-size: var(--t-head-size);
  }
  .nothing .what { font-size: var(--t-body-size); font-weight: 600; color: var(--ink-secondary); }
  .nothing .why { font-size: var(--t-small-size); color: var(--ink-muted); max-width: 46ch; line-height: 1.5; }
  .nothing .btn { margin-top: 4px; }

  /* Строка состояния данных. Тихая: её читают краем глаза и только
     когда цифра удивила. Красной становится, если выгрузка отстала
     больше чем на день — тогда это уже не подпись, а предупреждение. */
  .freshness {
    display: flex; align-items: center; gap: 7px;
    margin: 0 2px 14px; font-size: var(--t-small-size); color: var(--ink-muted);
  }
  .freshness::before {
    content: ""; width: 6px; height: 6px; border-radius: 50%;
    background: var(--good); flex: 0 0 auto;
  }
  .freshness.stale { color: var(--critical); }
  .freshness.stale::before { background: var(--critical); }
  .freshness.busy::before { background: var(--accent); }

  /* Крупные суммы: разряды видны, знак валюты не спорит с числом.
     Девятизначное число читается за долю секунды только если разряды
     разделены, а «₽» тише самой суммы — иначе глаз спотыкается о него
     раньше, чем прочтёт цифру. */
  .value, .sum, .big {
    font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1, "lnum" 1;
  }
  /* Единица отделена от числа настоящим пробелом. Отступ считался в
     ``em`` от собственного кегля единицы, а он вдвое меньше кегля числа —
     получалось три пикселя, и «257 200 960 ₽» читалось как одно слово
     «960₽». Пять пикселей — то же расстояние, что между словами в
     строке этого размера. */
  .value .unit, .sum .unit, .big .unit {
    font-weight: 400; color: var(--ink-secondary);
    font-size: .52em; margin-left: 5px; letter-spacing: 0;
  }

  /* Плашка масштаба: появляется в правом нижнем углу при изменении окна
     или зума и гаснет — постоянная цифра спорила бы с цифрами отчёта. */
  .zoom-badge {
    position: fixed; right: 18px; bottom: 16px; z-index: 60;
    padding: 7px 13px; border-radius: var(--r-s);
    font-size: var(--t-small-size); font-weight: 600; letter-spacing: .01em;
    color: var(--ink); background: var(--glass-strong);
    border: 1px solid var(--glass-border);
    box-shadow: 0 6px 22px rgba(0,0,0,.22);
    opacity: 0; transform: translateY(6px); pointer-events: none;
    transition: opacity .18s ease, transform .18s ease;
  }
  .zoom-badge.on { opacity: 1; transform: none; }

  /* Методология — за свёрткой. Абзац «как считается» нужен, когда цифре
     не верят; в остальные триста утр он — шум, который пролистывают.
     Сворачивание убирает с экрана треть высоты, ничего не удаляя. */
  details.how { margin: 10px 6px 0; }
  details.how > summary {
    list-style: none; cursor: pointer; user-select: none;
    display: inline-flex; align-items: center; gap: 6px;
    font-size: var(--t-small-size); color: var(--ink-muted);
  }
  details.how > summary::-webkit-details-marker { display: none; }
  details.how > summary::after {
    content: "▸"; font-size: var(--t-micro-size); line-height: 1;
    transition: transform .15s ease;
  }
  details.how[open] > summary::after { transform: rotate(90deg); }
  details.how > summary:hover { color: var(--ink); }
  details.how > *:not(summary) {
    margin: 8px 0 0; font-size: var(--t-body-size); color: var(--ink-secondary);
    max-width: 78ch;
  }
  /* Семьдесят восемь знаков — мера строки, а не таблицы.

     Под «Подробнее» лежит не только абзац: на «Поводах» там список
     гостей, и он сжимался до 661 пикселя из 980 доступных. Номера
     карточек YClients при этом резались посередине — «35656143,
     81257711, 35437767» просило 423 пикселя при 106, а по ним человек
     ищет гостя в системе. Раз уже пришлось чинить это поштучно для
     ``.sales-box``, чиним разом: всё, что не текст, занимает ширину
     целиком. */
  details.how > .card,
  details.how > .scroll,
  details.how > figure,
  details.how > .boards,
  details.how > table { max-width: 100%; }

  /* Фильтры — свёрнуты в строку. Их меняют раз в день, а места они
     занимали четверть экрана; до первой цифры было полэкрана служебного. */
  details.filterbox { margin-bottom: 14px; }
  details.filterbox > summary {
    list-style: none; cursor: pointer; user-select: none;
    display: inline-flex; align-items: center; gap: 8px;
    padding: 8px 14px; min-height: 36px;
    font-size: var(--t-body-size); color: var(--ink-secondary);
    background: var(--mac-button);
    box-shadow: var(--mac-button-shadow);
  }
  details.filterbox > summary::-webkit-details-marker { display: none; }
  details.filterbox > summary:hover { border-color: var(--ink-muted); color: var(--ink); }
  details.filterbox > summary b { font-weight: 600; color: var(--ink); }
  details.filterbox > summary::after {
    content: "▾"; font-size: var(--t-micro-size); color: var(--ink-muted);
    transition: transform .15s ease;
  }
  details.filterbox[open] > summary { margin-bottom: 10px; }
  details.filterbox[open] > summary::after { transform: rotate(180deg); }

  /* Свёрнутый хвост таблицы: строки, в которых всё в порядке. Одна
     строка-резюме вместо двухсот одинаковых. */
  details.calm-rows > summary {
    list-style: none; cursor: pointer; user-select: none;
    display: flex; align-items: center; gap: 8px;
    padding: 12px 24px; border-top: 1px solid var(--hairline); /* было 18 — на 7 px левее ячеек под ним */
    font-size: var(--t-body-size); color: var(--ink-secondary);
  }
  details.calm-rows > summary::-webkit-details-marker { display: none; }
  details.calm-rows > summary::after {
    content: "▾"; font-size: var(--t-micro-size); color: var(--ink-muted);
    transition: transform .15s ease;
  }
  details.calm-rows[open] > summary::after { transform: rotate(180deg); }
  details.calm-rows > summary:hover { color: var(--ink); }

  /* --- графики --- */
  .charts { display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); margin-bottom: 26px; }
  .chart { padding: 18px 20px 14px; }
  .chart h3 { margin: 0 0 3px; font-size: var(--t-head-size); font-weight: 600; letter-spacing: -0.014em; }
  .chart .sub { font-size: var(--t-small-size); color: var(--ink-muted); margin-bottom: 12px; }
  .chart svg { display: block; width: 100%; height: 92px; overflow: visible; }
  .chart .line { fill: none; stroke: var(--accent); stroke-width: 1.6; stroke-linejoin: round; stroke-linecap: round;
                 filter: drop-shadow(0 2px 5px color-mix(in srgb, var(--accent) 45%, transparent)); }
  .chart .area  { fill: color-mix(in srgb, var(--accent) 15%, transparent); stroke: none; }
  .chart .dot   { fill: var(--accent); stroke: var(--glass-strong); stroke-width: 1.4; }

  @media (prefers-reduced-transparency: reduce) {
    /* Кому прозрачность мешает — отдаём плотный материал без размытия. */
    .card, header.top, nav.tabs, .filters, th, select, input {
      -webkit-backdrop-filter: none; backdrop-filter: none;
      background: var(--glass-strong);
    }
  }
  /* --- вертикальный ритм --- */
  /* Расстояние между блоками страницы — одно на всю систему.

     Отступы задавались в каждом шаблоне руками, и на карточке филиала их
     набралось восемь разных: 0, 14, 16, 22, 30, 32, 48 и 50 пикселей.
     Глаз читает такой разнобой как неаккуратность, даже не понимая, в
     чём дело, — «разные расстояния между плитками».

     Правило стоит последним в таблице стилей намеренно: частные отступы
     блоков (``.tiles``, ``.card``, ``.note``) объявлены выше, и только
     отсюда их можно перекрыть, не расставляя ``!important``.

     Метятся прямые дети ``.wrap`` — блоки страницы. Внутренняя вёрстка
     карточек не трогается: там у каждой свои правила. */
  /* Расстояний ровно три, и каждое означает своё:

       16px — между самостоятельными блоками;
        6px — между блоком и подписью, которая к нему относится;
        0   — внутри одной группы.

     Больше значений быть не должно. Их было восемь, потом пять, и
     каждый раз это читалось как небрежность: глаз видит сбитый ритм
     раньше, чем успевает понять, в чём дело. */
  /* Селектор начинается с ``body`` намеренно: стили страниц подключаются
     после общих, и правило вида ``.board-fold`` или ``details.filterbox``
     перебивало ритм просто потому, что объявлено позже или назвало тег.
     Лишний ``body`` поднимает вес — и расстояние остаётся общим, не
     заставляя расставлять ``!important`` по всей таблице. */
  body .wrap > * { margin-top: 0; margin-bottom: var(--s4); }
  body .wrap > *:last-child { margin-bottom: 0; }
  body .wrap > nav.tabs { margin-bottom: 0; }
  /* Тот же ритм — внутри разворотов карточки.
     Правило выше достаёт только до прямых детей ``.wrap``. Когда
     карточка филиала переехала на развороты, между ``.wrap`` и
     карточками встал ``<section class="pane">``, карточки стали
     внуками — и ритм до них перестал доходить. Замерено на всех
     четырёх разворотах: между соседними карточками ровно ноль
     пикселей, блоки слипались в одну простыню.
     Тот же ``body`` в начале и по той же причине: стили страниц
     подключаются позже общих. */
  body .pane > * { margin-top: 0; margin-bottom: var(--s4); }
  body .pane > *:last-child { margin-bottom: 0; }
  /* Заголовок и строка свежести — одна группа, между ними 6. Строке
     своего отрицательного отступа не нужно: иначе они наезжают. */
  body .wrap > header.top { margin-bottom: 6px; }
  body .wrap > .freshness { margin-top: 0; }
  /* Пояснение прижато к тому, что поясняет. */
  body .wrap > .note,
  body .wrap > details.how { margin-top: calc(var(--s4) * -1 + 6px); }
  /* --- единые отступы --- */
  /* Замер показал систему без системы: шесть разных отступов у карточек,
     шесть у ячеек, пять у шапок и три высоты кнопок. Глаз читает это как
     «внутри всё криво», хотя каждое значение по отдельности разумно.

     Здесь одна сетка на всё. Ячейка — 12/16, первая и последняя колонки
     отступают на 24, столько же у подписи таблицы: тогда заголовок,
     шапка и первая колонка стоят на одной вертикали. Раньше подпись
     начиналась с 24, а ячейка с 14 — текст не выстраивался в столбик, и
     это было заметно, даже когда непонятно почему. */
  th, td { padding: 12px 16px; }
  th:first-child, td:first-child { padding-left: var(--s5); }
  th:last-child, td:last-child { padding-right: var(--s5); }

  /* Карточка: один отступ. Исключение одно — карточка, внутри которой
     таблица: её поля задают ячейки, и двойной отступ смотрелся бы рамкой
     внутри рамки. */
  .card { padding: var(--s5); }
  /* Два исключения из общего отступа, оба по делу: полоса с одной
     кнопкой и карточка с заголовком в пару строк. Больше вариантов быть
     не должно — их было пятнадцать, и это читалось как небрежность. */
  /* Сбоку — 24, как у всех карточек: было 16 и 20 px, текст стоял в 17 и 21 px против 25 у соседей (24.09.2026). Ужимается только верх и низ. */
  .card.tight { padding: var(--s3) var(--s5); }
  .card.snug { padding: var(--s4) var(--s5); }
  /* Карточка, внутри которой только таблица, отступов не имеет: их
     задают сами ячейки, и двойной отступ смотрелся бы рамкой в рамке.

     Но если в карточке есть ещё и текст — заголовок, вывод словами,
     совет, — отступ нужен, иначе текст ложится вплотную к краю. Именно
     это и случилось на «Разборе»: карточка с фразой «Выручка выросла на
     9,3 %» содержала таблицу, правило срабатывало, и весь текст
     прилипал к кромке, а числа справа уезжали за неё. */
  .card:has(> table):not(.tile):not(:has(> p)):not(:has(> h2)):not(:has(> h3)):not(:has(> .worst-head)),
  .card.scroll { padding: 0; }
  /* ``:not(.tile)`` добавлен 03.09.2026: доски «Первые десять барберов»
     — плитки с таблицей внутри, и правило снимало с них отступ целиком.
     Имя доски и пояснение ложились на кромку карточки, а число справа
     уезжало за неё — замер по снимку владельца: текст на 248 px при
     границе карточки на 250. У плитки текст есть всегда, отступ ей
     нужен всегда.
     ``.worst-head`` — 24.09.2026: на «К кому идти» фраза разбора ушла
     из абзаца в строку таблицы, абзацев в карточке не осталось, и
     правило прижало имя точки и таблицу к кромке. */
  .card:has(> table) caption, .card.scroll caption { padding-top: 20px; }

  /* Кнопки одной высоты. Три разных высоты в одном ряду — первое, что
     выдаёт несобранный интерфейс. */
  button, .btn { height: 36px; min-height: 36px; padding: 0 16px; }

  /* Переключатель темы жил на 35,5 пикселя со своими отступами и в ряду
     с кнопками съезжал на полпикселя. По отдельности не видно, в ряду —
     видно сразу. */
  .theme-switch { min-height: 36px; }

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

     Полторы десятых секунды — верхняя граница незаметности. Длиннее
     начинает ощущаться как задержка, и система кажется медленной ровно
     там, где она стала красивее.

     Смена темы и плотности из этого исключены намеренно: перекрашивать
     всю страницу плавно — значит показать полсекунды переходных
     полутонов, которых нет ни в одной из тем. */
  :where(a, button, .btn, .chip, .card, tbody tr, summary, input, select) {
    transition:
      background-color .14s ease,
      border-color .14s ease,
      color .14s ease,
      box-shadow .14s ease,
      transform .12s ease;
  }
  /* Строка таблицы подсвечивается под курсором: в таблице на двадцать
     колонок глазу нужна опора, чтобы не съехать на соседнюю строку. */
  tbody tr:hover { background: color-mix(in srgb, var(--ink) 3%, transparent); }
  /* Карточка чуть приподнимается, если она ссылка. Обычная карточка не
     реагирует ни на что: движение без последствия — обман. */
  a.card:hover { transform: translateY(-1px); border-color: var(--ink-muted); }
  /* Кнопка проваливается под нажатием — единственное движение, которое
     повторяет физику: нажал, и оно подалось. */
  /* Появление страницы. Не анимация, а снятие резкости: содержимое
     проступает за одну шестую секунды вместо того, чтобы возникнуть
     рывком. Переход между разделами перестаёт мигать. */
  @keyframes page-in { from { opacity: 0; transform: translateY(3px); } }
  /* Смена темы и плотности — не переход, а другое состояние листа.
     Плавно перекрашивать всю страницу значит показать полсекунды
     полутонов, которых нет ни в одной из тем: выглядит выцветанием, а
     не переключением. Скрипт вешает эту метку на время смены. */
  [data-switching] *, [data-switching] *::before, [data-switching] *::after {
    transition: none !important;
    animation: none !important;
  }

  @media (prefers-reduced-motion: reduce) {
    /* Уважаем системную настройку: кому движение мешает, тому оно
       мешает и здесь. */
    :where(a, button, .btn, .chip, .card, tbody tr, summary, input, select) {
      transition: none;
    }
    .wrap > *:not(nav.tabs) { animation: none; }
    a.card:hover, button:active, .btn:active { transform: none; }
  }

  /* Строка «вне расчёта» рядом со свежестью. Тихая, как и свежесть:
     её читают краем глаза и только когда цифра удивила. Список
     раскрывается щелчком — двадцать одно название в строке состояния
     превратило бы её в абзац. */
  .freshness .away { display: inline; }
  .freshness .away details { display: inline; }
  .freshness .away summary {
    display: inline; cursor: pointer; list-style: none;
    text-decoration: underline; text-decoration-style: dotted;
    text-underline-offset: 3px;
  }
  .freshness .away summary::-webkit-details-marker { display: none; }
  .freshness .away[open] summary { text-decoration: none; font-weight: 600; }

  /* Раскрытое — отдельная панель под строкой, а не текст, вклинившийся
     в неё. Раньше это был блок внутри строчного элемента внутри абзаца:
     объяснение вываливалось вбок, наезжало на соседний текст и обрывало
     строку состояния посередине. Теперь оно встаёт под ней целиком —
     на всю ширину листа, с полем и рамкой, как всякая другая карточка. */
  .freshness .away-list {
    display: block; margin: 10px 0 2px; padding: 12px 14px;
    max-width: 78ch; border: 1px solid var(--hairline); border-radius: var(--r-m);
    background: var(--surface-sunken, transparent);
    color: var(--ink-secondary); font-size: var(--t-small-size); line-height: 1.55;
  }
  .freshness .away-why { margin: 0 0 8px; }
  .freshness .away-why:last-of-type { margin-bottom: 0; }
  /* Список названий — тише объяснения: его читают, только когда уже
     поняли, о чём речь. */
  .freshness .away-names {
    margin: 10px 0 0; padding-top: 9px;
    border-top: 1px solid var(--hairline);
    color: var(--ink-muted); font-size: var(--t-small-size); line-height: 1.6;
  }
  /* Имена — ссылки в карточки точек, но тихие: подчёркивание точками,
     цвет тот же. Синие ссылки в списке из двадцати одного имени
     превратили бы тихую сноску в главный объект шапки. */
  .freshness .away-names a {
    color: inherit; text-decoration: underline dotted; text-underline-offset: 3px;
  }
  .freshness .away-names a:hover { color: var(--ink); }

  /* Полоса главных чисел листа. Родилась на «Клиентах», живёт здесь:
     одна на всю систему, чтобы числа везде были одного кегля и веса. */
  /* Все правила ниже привязаны к полосе ``.kpis``, а не к голому
     классу. Это не вкусовщина: ``.kpi`` — распространённое слово, и на
     «Показателях» им помечена **полоса плиток** (``<section class="tiles
     kpi">``), а не плитка. Она подхватывала чужие поля — 15 пикселей
     сверху, 18 по бокам — и весь ряд съезжал внутрь относительно
     заголовков и таблиц под ним. Ровно это и читалось как «расстояние
     между плитками съехало». */
  /* Тихая плитка приглушает цвет, но не отбирает у числа направление.
     Здесь пряталась стрелка — и «↓ 7,7 %» превращалось в «7,7 %».
     На «Клиентах» это выглядело так: «Из них новых 41 264, было
     44 730, 7,7 %», и падение читалось ростом. Семь плиток из восьми
     на листе, который открывает генеральный директор.
     Приглушать — значит убрать красный и зелёный: тревога у второстепенной
     цифры лишняя. Стрелка — не тревога, а смысл. */
  @media (max-width: 760px) {

    /* Переключатели видов на телефоне.

       Четыре разворота карточки не помещались в 375 пикселей и
       выталкивали страницу вбок: лист ездил по горизонтали на семь
       пикселей, чего на телефоне достаточно, чтобы палец промахивался
       мимо вертикальной прокрутки. Теперь ряд прокручивается сам внутри
       себя, а страница стоит на месте.

       Заодно цель нажатия: пять пикселей отступа давали кнопку высотой
       31 px при норме 44. Промахнуться по такой на ходу — обычное
       дело. */
    .views {
      flex-wrap: nowrap; overflow-x: auto; width: 100%;
      scrollbar-width: none; -webkit-overflow-scrolling: touch;
    }
    .views::-webkit-scrollbar { display: none; }
    .views a { padding: 11px 14px; }

    /* Таблица разбора на телефоне.

       Три колонки — «из-за чего», объяснение и сумма — не влезали в
       375 пикселей и выталкивали лист вбок на семь пикселей: страница
       ездила по горизонтали, и вертикальная прокрутка срывалась.
       Столбец с деньгами ужимается, длинное объяснение переносится по
       словам: читать его на телефоне всё равно приходится в несколько
       строк. */
    table.why-table { table-layout: fixed; width: 100%; }
    table.why-table td.say { white-space: normal; word-break: break-word; }
    /* Девяносто два, а не восемьдесят восемь: «+65 433 630» просит 91
       пиксель вместе с отступами, и на восьмидесяти восьми у числа
       срезался последний разряд. Знак и разряды — не украшение: без
       последней цифры сумма перестаёт быть суммой. */
    table.why-table .num, table.why-table th.num { width: 94px; }
    table.why-table td, table.why-table th { padding-left: 6px; padding-right: 6px; }
    /* Фильтры на телефоне — по строке на срез, а не по две.

       Метка занимала фиксированные 104 пикселя, опции не помещались в
       остаток и переносились вниз: пять фильтров съедали 396 пикселей,
       и первая цифра листа оказывалась за краем экрана — на телефоне
       «Показатели» открывались вообще без единого числа в поле зрения.

       Теперь метка по содержимому, ряд опций прокручивается сам. */
    .pick { flex-wrap: nowrap; gap: 8px; }
    .pick > .lbl { flex: 0 0 auto; }
    .pick a { padding: 10px 13px; }
    /* Шутка дня — не на первом экране телефона: она стоит между меню и
       фильтрами и отодвигает цифры ещё на строку. */
    .joke { display: none; }
  }

  /* Фильтры листа: подпись + сегмент-контрол. Одна капсула на всю
     систему — тот же язык, что у .views. */
  /* Один срез — одна строка.

     Была попытка поставить их в две колонки ради экономии высоты, и она
     провалилась: «Сравнивать» и «Где» — длинные ряды, в половине ширины
     они не помещались и переносились внутри капсулы на второй этаж.
     Строка вырастала с 37 пикселей до 66, ряды съезжали друг
     относительно друга, и лист выглядел разъехавшимся. Экономия в
     тридцать пикселей того не стоила. */
  .picks { display: grid; gap: 8px 26px; margin: 0 0 var(--s4); }

  /* строка действий с данными
     «Загрузить Учёт», выгрузки и «Обновить» — то, чем пользуются раз в
     неделю. Они должны быть на виду, но не занимать семь строк над
     первой цифрой: это работа с данными, а не ответ на вопрос листа.
     Отсюда одна строка мелких кнопок и подписи, ушедшие в подсказку. */
  .pick { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; min-width: 0; }
  .pick > .lbl {
    flex: 0 0 104px;
    font-size: var(--t-micro-size); font-weight: var(--t-micro-weight);
    letter-spacing: var(--t-micro-track); text-transform: uppercase;
    color: var(--ink-muted);
  }
  .pick a {
    padding: 4px 12px; border-radius: var(--r-s); font-size: var(--f-note);
    line-height: 1.5; color: var(--ink-secondary); text-decoration: none;
    white-space: nowrap; border: 1px solid transparent;
    transition: color .14s, background .14s;
  }
  .pick a:hover { color: var(--ink); }
  .pick a.on {
    background: var(--paper); color: var(--ink); font-weight: 600;
    border-color: var(--hairline); box-shadow: var(--shadow-1);
  }

  /* Заголовок блока внутри листа. Нужен там, где на одной странице
     стоят подряд несколько разных вопросов: без него три таблицы
     читаются как одна длинная и непонятно, зачем они рядом. */
  .block-head {
    margin: var(--s5) 0 var(--s3); font-size: var(--t-head-size); font-weight: 600;
    letter-spacing: -0.022em; color: var(--ink);
  }
  body .wrap > .block-head { margin-top: var(--s5); margin-bottom: var(--s3); }
  body .wrap > .block-head:first-of-type { margin-top: var(--s4); }

  /* Вступление листа: одна фраза о том, на что он отвечает. */
  .lead {
    margin: 0; max-width: 80ch;
    font-size: var(--t-body-size); line-height: 1.55; color: var(--ink-secondary);
  }

  /* Вывод словами под таблицей. Таблица процентов — это материал;
     человек всё равно спрашивает «лучше стало или хуже», и ответ должен
     быть написан, а не выведен им самим. */
  /* Главный вывод дня — крупнее обычного вывода: это первое, что
     читают, и читают его вместо всего листа, если торопятся. */
  .verdict.day { font-size: var(--t-head-size); font-weight: 600; padding: 14px 18px; }
  .verdict {
    margin: 0; padding: 12px 16px;
    font-size: var(--t-body-size); line-height: 1.5; font-weight: 500;
  }

  /* Список инструментов в настройках. Не таблица: две строки с
     пояснением читаются как список, а не как отчёт. */
  .tools { margin: 0; padding-left: 20px; }
  .tools li { margin-bottom: 8px; line-height: 1.5; color: var(--ink-secondary); }
  .tools li:last-child { margin-bottom: 0; }
  .tools a { font-weight: 600; color: var(--ink); }

  /* Заголовок внутри карточки. Такой же по весу, как заголовок блока,
     но без внешних отступов: их задаёт сама карточка. */
  /* Персона в таблице: кружок фото дышит от имени. */
  /* Фотографии барберов на «Барберах» и в карточке барбершопа — одно
     правило: два списка про одних и тех же людей должны выглядеть
     одинаково. Кружок один и тот же с фотографией и с буквой — разница
     в display между ними давала четыре пикселя по высоте строки. */
  td.photo { padding-right: 0; }
  td.photo img, td.photo .noface {
    display: block; width: 30px; height: 30px; border-radius: 50%;
    object-fit: cover; box-shadow: 0 0 0 1px var(--hairline);
  }
  /* Без фотографии — инициал в полный контраст. Бледная буква в бледном
     кружке читалась как пустая дырка, и колонка из таких дырок выглядела
     как незагрузившиеся картинки. */
  td.photo .noface {
    line-height: 30px; text-align: center; font-size:var(--f-cap); font-weight: 600;
    color: var(--ink-secondary); background: color-mix(in srgb, var(--ink) 13%, transparent);
    box-shadow: none; letter-spacing: .02em;
  }
  td.photo + td { padding-left: 10px; }

  .card-head {
    margin: 0 0 var(--s3); font-size: var(--f-card); font-weight: 600;
    letter-spacing: -0.02em;
  }

  /* Разбор — не таблица чисел, а объяснение. Крупная фраза сверху,
     потом слагаемые, потом что делать. Читается сверху вниз за полминуты.

     Колонки в этом порядке намеренно: причина → что произошло → сколько
     это стоило. Раньше деньги стояли вторыми, сразу за подписью, и
     «Средний чек +14 601 097 ₽» читалось как «средний чек четырнадцать
     миллионов». Теперь между подписью и суммой стоит объяснение, а над
     суммой — заголовок «вклад в выручку». */
  .why-head { margin: 0 0 var(--s4); font-size: var(--t-head-size); line-height: 1.45; font-weight: 600; }
  .why-head.down { color: var(--critical); }
  /* Второе сравнение — к предыдущему полному месяцу. Набрано слабее
     главной фразы и сильнее серой строки с месяцами: это ответ, а не
     сноска, но ответ на второй вопрос. Цвет общий с чернилами — по
     правилу 5 красным красится «хуже», и оно уже сказано выше. */
  .why-also {
    margin: calc(-1 * var(--s3)) 0 var(--s4);
    font-size: var(--f-body); line-height: 1.5; color: var(--ink-secondary);
  }
  /* Те же две фразы строками таблицы — на «К кому идти», где разборов
     восемь подряд и абзацы между ними запрещены правилом 11. */
  .why-table tr.why-line td { font-weight: 600; font-size: var(--f-body); padding-top: 10px; }
  .why-table tr.why-line.down td { color: var(--critical); }
  .why-table tr.why-line.also td { font-weight: 400; color: var(--ink-secondary); padding-top: 2px; }
  .why-table tr.why-line .why-when { font-weight: 400; font-size: var(--t-small-size); color: var(--ink-muted); margin-left: 8px; white-space: nowrap; }
  /* За какие месяцы посчитано. Тихо, но всегда — под самой фразой, а не
     в шапке листа, где это можно не заметить. */
  .why-period {
    margin: -8px 0 var(--s4); font-size: var(--t-small-size); line-height: 1.5;
    color: var(--ink-muted); max-width: 92ch;
  }

  /* Вывод словами перед таблицей. Таблица — материал, а человек читает
     её и всё равно спрашивает «так из-за чего?». Здесь ответ написан,
     и написан он крупнее таблицы намеренно: это главное на карточке,
     а не подпись к ней. Маркер — тире, а не точка: точки выстраиваются
     в столбик кружков и выглядят списком покупок. */
  .said {
    margin: 0 0 var(--s4); padding: 0; list-style: none;
    max-width: 96ch; font-size: var(--t-body-size); line-height: 1.55;
  }
  .said li {
    position: relative; padding: 0 0 0 18px; margin: 0 0 7px;
    color: var(--ink-secondary);
  }
  .said li::before {
    content: "—"; position: absolute; left: 0; top: 0;
    color: var(--ink-muted);
  }
  /* Первая строка — самая тяжёлая причина. Она читается в полный
     контраст, остальные приглушены: иначе пять равнозначных строк
     заставляют читать все пять, чтобы понять, какая главная. */
  .said li:first-child { color: var(--ink); }

  .why-table { width: 100%; }
  /* Крайние ячейки без своего отступа: иначе таблица стоит на 12
     пикселей правее заголовка над ней, и текст выглядит съехавшим. */
  .why-table th, .why-table td { padding: 10px 14px; vertical-align: top; }
  .why-table th:first-child, .why-table td:first-child { padding-left: 0; }
  .why-table th:last-child, .why-table td:last-child { padding-right: 0; }
  .why-table thead th {
    font-size: var(--t-micro-size); font-weight: var(--t-micro-weight);
    letter-spacing: var(--t-micro-track); text-transform: uppercase;
    color: var(--ink-muted); border-bottom: 1px solid var(--hairline);
    padding-top: 0; padding-bottom: 8px;
    /* Заголовок переносится: с ``nowrap`` «Вклад в выручку, ₽» распирал
       колонку, таблица вылезала за карточку, и числа уезжали за край. */
    white-space: normal;
  }
  .why-table td { border-bottom: 1px solid var(--hairline); }
  .why-table .what { width: 23%; font-weight: 600; }
  .why-table .sum {
    width: 17%; font-variant-numeric: tabular-nums; white-space: nowrap;
    font-weight: 600;
  }
  .why-table .say { color: var(--ink-secondary); }
  /* Ступень глубже — тише и с отступом: это не отдельная причина, а та
     же, рассмотренная ближе. Одинаковый вес строк заставлял бы
     складывать их между собой, а складывать нельзя. */
  .why-table .deeper td { color: var(--ink-muted); font-size: var(--t-small-size); }
  .why-table .deeper .what { padding-left: 20px; font-weight: 500; }
  .why-table .deeper .sum { font-weight: 500; }
  .why-table .deeper-2 .what { padding-left: 38px; }
  .why-table tfoot td {
    border-bottom: 0; border-top: 1px solid var(--glass-border);
    padding-top: 12px; font-size: var(--t-small-size);
  }
  .why-table tfoot .what, .why-table tfoot .sum { font-weight: 600; color: var(--ink); }
  .why-table tfoot .say { color: var(--ink-muted); }
  .plus { color: var(--good); }
  .minus { color: var(--critical); }

  .advice { margin-top: var(--s5); padding-top: var(--s4); border-top: 1px solid var(--hairline); }
  .advice-title {
    font-size: var(--t-micro-size); font-weight: var(--t-micro-weight);
    letter-spacing: var(--t-micro-track); text-transform: uppercase;
    color: var(--ink-secondary); margin-bottom: 10px;
  }
  .advice ol { margin: 0; padding-left: 20px; }
  .advice li { margin-bottom: 8px; line-height: 1.55; color: var(--ink-secondary); }
  .advice li:last-child { margin-bottom: 0; }

  /* Строка «данные ещё грузятся» — над плитками, одна на все. */
  .partial-line { margin: 0; font-size: var(--t-body-size); line-height: 1.5; max-width: 90ch; }
  .partial-line .partial {
    display: inline; color: var(--ink-muted); font-style: italic;
  }
  .partial-line .partial::before {
    content: ""; display: inline-block; vertical-align: 1px;
    width: 7px; height: 7px; border-radius: 50%; margin-right: 7px;
    background: var(--accent-fill);
    animation: partial-pulse 1.4s ease-in-out infinite;
  }
  @media (prefers-reduced-motion: reduce) {
    .partial-line .partial::before { animation: none; }
  }

  /* Карточка-предупреждение: свёрнута, но заметна кромкой. Раскрывать
     её каждый раз незачем — это не работа дня, а сигнал «посмотри». */
  .warn-card {
    border-color: color-mix(in srgb, var(--critical) 34%, var(--glass-border));
  }
  .warn-card > summary {
    cursor: pointer; padding: 2px 0; list-style: none;
    font-size: var(--t-body-size); line-height: 1.5;
  }
  .warn-card > summary::-webkit-details-marker { display: none; }
  .warn-card > summary::before { content: "▸ "; color: var(--critical); }
  .warn-card[open] > summary::before { content: "▾ "; }

  /* «Остальные — 201» — свёртка с хвостом длинной таблицы. Выглядит
     строкой, а не кнопкой: это не действие, а продолжение списка. */
  .tail-more {
    cursor: pointer; list-style: none; padding: 16px var(--s5);
    font-size: var(--t-body-size); color: var(--ink-secondary);
  }
  .tail-more::-webkit-details-marker { display: none; }
  .tail-more::before { content: "▸ "; color: var(--ink-muted); }
  details[open] > .tail-more::before { content: "▾ "; }
  .tail-more:hover { color: var(--ink); }

  /* Короткая форма в одну строку: поле и кнопка. Длинную форму не
     заполняют — её обходят. */
  .inline-form { display: flex; gap: 8px; align-items: center; }
  .inline-form .target {
    flex: 1 1 auto; min-width: 0; height: 36px; padding: 0 12px;
    border: 1px solid var(--glass-border); border-radius: var(--r-m);
    background: var(--glass); color: var(--ink); font-size: var(--t-body-size);
  }

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

     Прячутся только формы, которые что-то меняют. Фильтры и поиск
     работают обычным способом и остаются: смотреть можно всё. */
  /* ``.btn-edit`` в разметке сегодня не встречается. Правило оставлено
     намеренно: это ограничение прав, а не оформление, и оно должно
     сработать в тот день, когда такая кнопка появится, а не после того,
     как её заметят у роли «смотреть». */
  [data-viewer] form[method="post"],
  [data-viewer] .btn-edit,
  [data-viewer] a[href*="edit=1"] { display: none !important; }

  /* телефон
     Система для директора, которую нельзя открыть в машине, — это
     половина системы. Здесь она открывается.

     Правило одно: на узком экране ничего не прячется, всё
     перестраивается. Спрятанный на телефоне раздел означает, что
     ответить на вопрос из машины нельзя, — а вопрос из машины и есть
     самый частый.

     Порог 640 — граница, ниже которой две колонки перестают быть
     колонками: каждая уже двухсот пикселей, и таблица в ней не живёт. */
  @media (max-width: 640px) {
    .wrap { padding: 0 12px 48px; }

    /* Сетки — в одну колонку. Плитка шириной в пол-экрана вмещает
       «257 200 960 ₽» переносом на три строки. */
    .tiles, .tiles.kpi, .cards, .charts, .charts-grid, .board-tiles, .agenda, .summary-pair, .rating-tables, .settings, .outlook .facts {
      grid-template-columns: minmax(0, 1fr) !important;
      gap: 10px;
    }

    /* Заголовок раскладывается сверху вниз: справа на телефоне места
       нет, и кнопки уезжали за край. */
    header.top { flex-direction: column; align-items: stretch; gap: 12px; padding-top: 16px; }
    header.top h1 { font-size: var(--t-title-size); }
    header.top > div:last-child { display: flex; flex-wrap: wrap; gap: 8px; }

    /* Под палец. Сорок четыре пикселя — размер, на который человек
       попадает не глядя; тридцать шесть годятся только для мыши. */
    button, .btn, .btn-plain, .btn-exit, .theme-switch,
    select, input[type="text"], input[type="search"], input[type="number"] {
      min-height: 44px; height: auto; font-size: var(--t-head-size);
    }
    /* Шестнадцать пикселей в поле ввода — не украшение: браузер на
       телефоне сам приближает страницу, если кегль меньше, и после
       ввода она остаётся увеличенной. */
    input, select, textarea { font-size: var(--t-head-size); }

    .filters { grid-template-columns: minmax(0, 1fr); gap: 10px; }
    .filters label { width: 100%; }

    /* Таблицы ездят вбок, но первый столбец остаётся на месте: без
       него на четвёртой колонке уже непонятно, чья это строка. */
    .scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
    /* И ездят они по-настоящему — вот это и было сломано. На большом
       экране таблица делит ширину по процентам из ``colgroup``
       (``table-layout: fixed``), и на телефоне она честно делила те же
       проценты от 390 пикселей: девять колонок по сорок семь. Возить
       было нечего — вместо прокрутки выходило девять столбцов
       многоточий. Замер «Сверки роялти» на 390: «Выручка, ₽» — 47
       пикселей при нужных 83, и так все до одной.
       На телефоне ширину задаёт содержимое: колонка ровно такая, чтобы
       число поместилось, а таблица шире экрана — и вот тогда её
       действительно возят. Первый столбец при этом приклеен, так что
       чья это строка, видно всегда. */
    table.fit { table-layout: auto; width: auto; min-width: 100%; }
    /* Доски первых десяти — исключение: это не таблица, которую возят
       вбок, а карточка на три колонки. Общее правило делало её шириной
       по содержимому — 411 пикселей при карточке в 351, — и весь лист
       «Барберы» ехал вправо. Коробки прокрутки у доски нет и быть не
       должно: десять мест читают глазами сверху вниз. */
    .boards table.fit { table-layout: fixed; width: 100%; min-width: 0; }
    /* На телефоне доске остаётся 349 пикселей, и «1 533 998 ₽» просило
       110 при 107 отведённых. Три пикселя взяты у отступов клетки:
       сжимать имя барбершопа нельзя — оно и так под многоточием. */
    .boards td, .boards th { padding-left: 4px; padding-right: 4px; }

    /* Доска лидеров — исключение из «на телефоне ничего не прячется, всё
       перестраивается»: перестраивать тут нечего, тринадцать месяцев
       остаются тринадцатью. Делить 375 пикселей на них — значит по 28 на
       колонку, где заголовок «янв» просит 43. Доска держит свою ширину и
       ездит вбок внутри коробки. */
    .board table { min-width: 640px; }
    /* Кроме одной: ячейке с длинным пояснением нельзя растягивать
       таблицу на три экрана. Ей — перенос и потолок ширины. */
    table.fit td.wrap { max-width: 44ch; white-space: normal; }
    th, td { padding: 10px 12px; }
    th:first-child, td:first-child {
      padding-left: 14px;
      position: sticky; left: 0; z-index: 1;
      background: var(--glass);
    }
    thead th:first-child { z-index: 3; }
    th:last-child, td:last-child { padding-right: 14px; }
    caption { padding: 16px 14px 10px 24px; } /* слева 24 — на одной вертикали с первой ячейкой (было 14 против 24) */

    /* Селектор с ``body`` — по той же причине, что и у ритма отступов:
       стили страниц подключаются позже общих и перебивали бы это. */
    body .card { padding: 14px; }
    body .card.tight, body .card.snug { padding: 12px 14px; }

    /* Полоса разделов выходит на поля листа — и поля должны совпадать.
       Панель растягивалась на четырнадцать пикселей в каждую сторону,
       а поле стало двенадцать: страница ездила вбок на два пикселя,
       ровно на эту разницу. */
    nav.tabs { margin-left: -12px; margin-right: -12px; }

    /* Крупное число не должно переноситься: «257 200 960» в две строки
       перестаёт читаться как одно число. */
    .value, .amount, .big { font-size: var(--t-title-size); white-space: nowrap; }
    .tiles .value { font-size: var(--t-title-size); }

    /* Боковая панель уже свёрнута в строку сверху (правило выше на
       940 пикселях). Кнопка сворачивания на телефоне бессмысленна. */
    .nav-toggle { display: none; }
  }

  /* Нить загрузки: переход думает — сверху бежит полоса. */
  #loadbar {
    position: fixed; top: 0; left: 0; height: 2px; width: 40%;
    background: var(--accent); z-index: 200; border-radius: 0 2px 2px 0;
    animation: loadbar 1.1s ease-in-out infinite;
  }
  @keyframes loadbar {
    0% { left: -40%; width: 30%; }
    50% { width: 46%; }
    100% { left: 100%; width: 30%; }
  }

  /* Фокус с клавиатуры: кольцо видно на всём интерактивном. Мышь
     колец не рисует — :focus-visible само отличает. */
  :is(a, button, input, select, textarea, summary):focus-visible {
    outline: 2px solid color-mix(in srgb, var(--accent) 70%, transparent);
    outline-offset: 2px; border-radius: 4px;
  }

  /* Д13—Д14: длинная таблица читается по строкам — лёгкая полоса через
     одну и ховер. Короткие таблицы полосой не шумят: порог — двадцать
     первая строка. */
  .card table:has(tbody tr:nth-child(21)) tbody tr:nth-child(even) td {
    background: color-mix(in srgb, var(--ink) 2.5%, transparent);
  }
  .card table tbody tr:hover td {
    background: color-mix(in srgb, var(--ink) 5%, transparent);
  }

  /* режим съёмки
     Правило Виктории: не видно НИ ОДНОЙ цифры; названия филиалов и
     названия показателей остаются. Поэтому размывается всё числовое —
     ячейки без ссылок, значения плиток, дельты, оси, суммы, даты, — а
     подписи, заголовки и имена (ячейки со ссылками) не трогаются.
     Подсказки при наведении глушатся: случайное движение курсора на
     видео показало бы выручку. */
  [data-private] :is(
    td:not(:has(a)), .value, .val, .sum, .pair, .amount, .cmp, .kpi .sub,
    .delta, .plot-level, .plot-now, .plot-month, .said, .verdict,
    .why-head, .why-table .say, .fact .v, .facts .v, .fine, .rung .chg,
    .freshness, header.top .meta, .away-list, .pnum,
    .mark, .rank-block, .box .note, .box .value, .said, .why-period,
    .inspect .facts-row, .inspect .viol,
    .outlook-strip, .watchbox > summary, .watch-list li span,
    .agenda .what, .agenda .count, .inspect, .said li, .joke,
    .note, .hint, .lead
  ) {
    filter: blur(7px) !important;
    user-select: none;
  }
  /* Имена и подписи — в полный контраст: видео рассказывает о функциях,
     а функции называются словами. Ячейка со ссылкой — имя точки или
     мастера, она тоже видима. */
  [data-private] :is(
    th, .name, .lbl, .k, .block-head, .card-head,
    td a, .canchors, .pick, .views
  ) {
    filter: none !important;
  }
  [data-private] .plot-hit { pointer-events: none; }
  [data-private] svg title { display: none; }

  /* плотность строк
     «Плотно» ужимает только вертикаль: отступы ячеек и заданные высоты
     строк. Кегль тот же — уменьшать буквы значит менять читаемость, а
     не плотность. На таблице в двести строк экран вмещает вдвое больше.

     Правила перечислены поимённо, а не через общий множитель: высоты
     задавались там, где содержимое ячейки могло растянуть строку, и
     каждая такая высота была подобрана под своё содержимое. */
  /* Главный рычаг — сама шкала отступов. Раньше «плотно» правило только
     ячейки таблиц, и на листе с плитками и пояснениями разницы было не
     видно вовсе: строки ужимались на четыре пикселя, а полтора экрана
     воздуха между карточками оставались на месте. Пережимаем шкалу — и
     ужимается всё сразу, включая то, о чём никто не вспомнил. */
  [data-density="dense"] {
    --s1: 2px; --s2: 4px; --s3: 6px; --s4: 8px; --s5: 10px; --s6: 13px;
    /* Кегль тоже чуть меньше: на плотном экране решает число строк.
       13 пикселей ещё читаются с рабочего расстояния; меньше — уже
       напряжение, и туда не идём. */
    font-size: var(--t-body-size);
  }
  [data-density="dense"] h1 { font-size: var(--t-title-size); }
  [data-density="dense"] .top { margin-bottom: 6px; }
  [data-density="dense"] .tile .value { font-size: var(--t-title-size); margin-top: 2px; }
  [data-density="dense"] .kpi .val { font-size: var(--t-title-size); }
  [data-density="dense"] .kpi { padding: 8px 13px 7px; }
  [data-density="dense"] .lead { font-size: var(--t-small-size); margin-bottom: 6px; }
  [data-density="dense"] details.how { font-size: var(--t-micro-size); }

  [data-density="dense"] th,
  [data-density="dense"] td { padding-top: 3px; padding-bottom: 3px; font-size: var(--t-small-size); }
  [data-density="dense"] th { padding-top: 4px; padding-bottom: 3px; }
  [data-density="dense"] caption { padding-top: var(--s3); padding-bottom: var(--s1); }
  [data-density="dense"] tbody tr,
  [data-density="dense"] .board tbody tr,
  [data-density="dense"] table.archive tbody tr { height: auto; }

  /* Карточки и плитки: поля вдвое меньше, между ними — тоже. */
  [data-density="dense"] .card { padding: 8px 11px; }
  [data-density="dense"] .card.tight { padding: 5px 9px; }
  [data-density="dense"] .card.snug { padding: 8px 11px; }
  [data-density="dense"] .tiles, [data-density="dense"] .tiles.kpi, [data-density="dense"] .cards, [data-density="dense"] .charts-grid, [data-density="dense"] .rating-tables { gap: 6px; margin-bottom: 6px; }
  [data-density="dense"] .tile.card { padding: 7px 10px; }

  [data-density="dense"] .tile .cmp { margin-top: 3px; }

  /* Заголовки блоков и вводные абзацы съедали больше места, чем сами
     данные: между двумя таблицами стояло сорок пикселей пустоты. */
  [data-density="dense"] .block-head,
  [data-density="dense"] body .wrap > .block-head { margin: 10px 0 5px; font-size: var(--t-body-size); }
  [data-density="dense"] .card-head { margin-bottom: 6px; font-size: var(--t-body-size); }
  [data-density="dense"] .lead,
  [data-density="dense"] .note,
  [data-density="dense"] .hint { line-height: 1.4; }
  [data-density="dense"] .verdict { padding: 9px 13px; }
  [data-density="dense"] .verdict.day { padding: 10px 14px; font-size: var(--t-body-size); }
  [data-density="dense"] .wrap { padding-bottom: 28px; row-gap: 7px; }
  [data-density="dense"] header.top { margin-bottom: 8px; }
  [data-density="dense"] .freshness { margin: 4px 0 8px; }

  /* Пояснения под строками воронки и подписи графиков — самое
     необязательное на плотном экране. */
  [data-density="dense"] .rung { padding: 6px 12px; }
  [data-density="dense"] .rung .pair { font-size: var(--t-head-size); }
  [data-density="dense"] .hero .plot { margin-top: 4px; }
  [data-density="dense"] .rung .why { display: none; }
  [data-density="dense"] .picks { gap: 5px 20px; margin-bottom: 10px; }
  [data-density="dense"] .hero { padding: 11px 14px 8px; }
  [data-density="dense"] .board td, [data-density="dense"] .board th,
  [data-density="dense"] table.archive td { padding-top: 2px; padding-bottom: 2px; }
  [data-density="dense"] .inspect { padding: 6px 0 8px; }
  [data-density="dense"] .watch-list li { padding: 4px 0 4px 14px; }

  /* телефон: фильтры лентой
     Пять срезов столбиком занимали 396 пикселей: лист открывался вообще
     без единой цифры в поле зрения — шапка, меню, семь видов и
     полэкрана фильтров. Лентой поперёк экрана это 66 пикселей, и первый
     экран снова начинается с денег.

     Правило стоит в конце файла намеренно: базовое `.picks { display:
     grid }` объявлено ниже мобильного блока, и в середине файла эта
     раскладка проигрывала ему по порядку. */
  @media (max-width: 760px) {
    .picks {
      display: flex; flex-wrap: nowrap; overflow-x: auto; gap: 14px;
      scrollbar-width: none; padding-bottom: 2px;
    }
    .picks::-webkit-scrollbar { display: none; }
    .picks > .pick { flex: 0 0 auto; }
  }

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

     Имя не `.pair`: так уже называется «было → стало» в ступени
     воронки. */
  .duo { display: grid; gap: 16px; grid-template-columns: repeat(2, minmax(0, 1fr)); }
  @media (max-width: 1100px) { .duo { grid-template-columns: minmax(0, 1fr); } }

  /* поиск в боковой панели
     Тихое поле: оно нужно часто, но кричать о себе не должно — это
     инструмент, а не раздел. Ширина по панели, высота как у пункта
     меню, чтобы ряд не сбивался. */
  .tabs .navfind { margin: 0 0 10px; }
  .tabs .navfind input[type="search"] {
    width: 100%; height: 34px; padding: 0 12px;
    font: inherit; font-size: var(--f-note);
    color: var(--ink); background: color-mix(in srgb, var(--ink) 5%, transparent);
    border: 1px solid color-mix(in srgb, var(--ink) 8%, transparent);
    border-radius: var(--r-m);
  }
  .tabs .navfind input::placeholder { color: var(--ink-muted); }
  .tabs .navfind input:focus {
    outline: none; background: var(--paper);
    border-color: color-mix(in srgb, var(--accent) 40%, var(--glass-border));
  }
  /* На телефоне панель — горизонтальная лента, и поле в ней съедало бы
     всю ширину. Там поиск открывается со страницы «Поиск». */
  @media (max-width: 760px) { .tabs .navfind { display: none; } }

/* Плитка-сообщение над «Настройками»: ответ системы на действие.
   Своего стиля у неё не было, и длинный текст — «пароль …, перепишите
   сейчас» — обрезался краем карточки. Пароль показывается один раз,
   обрезать его нельзя ни при каких обстоятельствах. */
.card.banner {
  white-space: normal;
  overflow: visible;
  word-break: break-word;
  border-left: 3px solid var(--ink);
  font-size: var(--t-head-size);
  line-height: 1.45;
}

/* Шкала «плохо / средне / хорошо» — три черты рядом с числом.
   Заполнена одна — нижняя треть сети, две — середина, три — верхняя.
   Красная только нижняя треть: красный означает «хуже» (правило 5),
   середина не красится вовсе. Словами уровень назван в подсказке. */
.lvl { display: inline-flex; gap: 2px; margin-left: 7px; align-items: flex-end; }
.lvl i { width: 4px; border-radius: 1px;
         background: color-mix(in srgb, var(--ink) 14%, transparent); }
.lvl i:nth-child(1) { height: 5px; }
.lvl i:nth-child(2) { height: 8px; }
.lvl i:nth-child(3) { height: 11px; }
.lvl-bad i:nth-child(1) { background: var(--critical); }
.lvl-mid i:nth-child(-n+2) { background: var(--ink-muted); }
.lvl-good i { background: var(--good); }

/* Закрашенная шкала со стрелкой на карточке барбера. Три зоны — трети
   сети: левая — «плохо» (красная, правило 5: красный значит «хуже»),
   середина не красится, правая — «хорошо». Стрелка стоит на доле сети,
   у которой показатель ниже, чем у этого мастера. */
.band-row { margin: 2px 0 18px; }
/* Тридцать пикселей — не «на глаз»: подпись стрелки висит на −24 px над
   полосой и занимает 18 px по высоте, то есть кончается за 6 px до
   заголовка. При шестнадцати они делили одну строку, и у полосы «Товары
   на визит» заголовок кончался на 790 px, а подпись начиналась на 736 —
   пятьдесят четыре пикселя текста поверх текста. */
.band-row .band-head { display: flex; justify-content: space-between;
  font-size: var(--f-note); margin-bottom: 30px; }
.band-row .band-head b { font-variant-numeric: tabular-nums; }
.band { position: relative; height: 10px; border-radius: 5px; display: flex;
  overflow: visible; }
.band .zone { height: 100%; }
.band .zone.bad { width: 33.333%; border-radius: 5px 0 0 5px;
  background: color-mix(in srgb, var(--critical) 26%, transparent); }
.band .zone.mid { width: 33.334%;
  background: color-mix(in srgb, var(--ink) 10%, transparent); }
.band .zone.good { width: 33.333%; border-radius: 0 5px 5px 0;
  background: color-mix(in srgb, var(--good) 30%, transparent); }
.band .pin { position: absolute; top: -13px; transform: translateX(-50%);
  width: 0; height: 0; border-left: 6px solid transparent;
  border-right: 6px solid transparent; border-top: 9px solid var(--ink); }
.band .pin-label { position: absolute; top: -24px; transform: translateX(-50%);
  font-size: var(--f-note); white-space: nowrap; color: var(--ink-secondary); }
/* У краёв подпись прижимается к своей стороне: центрированная по стрелке,
   она наполовину уезжала за полосу — на ста процентах текст обрывался. */
.band .pin-label.to-left { transform: translateX(-100%); }
.band .pin-label.to-right { transform: translateX(0); }
.band-row .band-legend { display: flex; justify-content: space-between;
  font-size: var(--t-micro-size); color: var(--ink-muted); margin-top: 5px; }

/* Матрица «Продажи мастеров»: показатели строками, мастера столбцами.

   Мастеров сравнивают между собой, а глаз сравнивает по строке, а не по
   странице, — поэтому таблица развёрнута. Столбец «Все» стоит первым и
   выделен: без него строка «средний чек 5 756 ₽» не говорит, много это
   или мало. */
/* Столбцы НЕ делят ширину поровну (``table.fit``): имя показателя
   «Чек к стоимости стрижки» требует 249 пикселей, а равная доля на
   одиннадцать столбцов давала 96 — и половина подписей обрезалась. Здесь
   ширина берётся по содержимому, а не помещающаяся таблица едет вбок
   внутри своей обёртки (``sticky.js`` ставит ей ``.boxed``). */
.sales { width: 100%; table-layout: auto; border-collapse: collapse; }
.sales th, .sales td { white-space: nowrap; }
.sales th.row-head {
  text-align: left; font-weight: 400; color: var(--ink-secondary);
  white-space: nowrap; position: sticky; left: 0; min-width: 230px;
  /* Фон обязан быть непрозрачным: клетка прилипает, и под ней едут
     чужие строки. Здесь стояло ``var(--surface)`` — имени, которого в
     системе нет. Браузер такое правило молча выбрасывает, клетка
     становилась прозрачной, и при прокрутке шапка «Показатель»
     наезжала на «Стоимость стрижки» — владелец прислала снимок
     22.09.2026. */
  background: var(--glass-strong); z-index: 1;
}
.sales thead th.row-head { z-index: 3; }
.sales thead th.num, .sales tbody td { min-width: 78px; }
/* Своя коробка прокрутки: широкая таблица едет вбок внутри неё, а не
   уводит вбок всю страницу. Ширина — во всю карточку: у раскрывающегося
   блока она ограничена 78 знаками, и это правило для текста, а не для
   таблицы — она сжималась до 663 пикселей из 1150 доступных. */
/* На телефоне ряд «Месяц · выбор · Показать · Скачать Excel» требует
   390 пикселей, а в окне 349: без переноса кнопка выгрузки уезжала за
   край экрана. */
#sales form.inline-form { flex-wrap: wrap; row-gap: 10px; }
.sales thead th { vertical-align: bottom; }
.sales thead th .sub {
  display: block; font-size: var(--t-micro-size); font-weight: 400;
  color: var(--ink-muted); text-transform: lowercase;
}
.sales th.row-head .sub {
  display: inline; margin-left: 5px; color: var(--ink-muted);
  font-size: var(--t-micro-size);
}
.sales td.total, .sales th.total {
  /* Подкрашено по непрозрачному листу, а не по прозрачности: шапка
     прилипает, и полупрозрачная клетка «Все» показывала под собой число
     строки — «3 565» поверх «3 508». */
  background: color-mix(in srgb, var(--ink) 4%, var(--glass-strong));
  font-variant-numeric: tabular-nums;
}
/* Главные строки — чек и коэффициент допродажи — набраны плотнее: по
   ним читают таблицу, остальное объясняет их. */
.sales tr.strong td, .sales tr.strong th.row-head {
  font-weight: 600; color: var(--ink);
}
.sales tr.split th {
  text-align: left; font-size: var(--t-micro-size); font-weight: 600;
  text-transform: uppercase; letter-spacing: .04em; color: var(--ink-muted);
  padding-top: 16px; border-bottom: 1px solid var(--glass-border);
}

/* Занятые темы — слова, которые в идеях сети уже встречались не раз.
   Список, а не облако: размер шрифта как мера частоты читается на глаз
   хуже, чем число рядом со словом. */
.taken { display: flex; flex-wrap: wrap; gap: 8px; }
.taken span {
  padding: 4px 10px; border-radius: 99px; font-size: var(--t-small-size);
  background: color-mix(in srgb, var(--ink) 5%, transparent);
  color: var(--ink-secondary);
}
.taken span b { color: var(--ink); font-weight: 600; }

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

     Серую рампу пробовали первой и отвергли по замеру 27.08.2026: пять
     ступеней от #1f1e1d до #cfccc2 расходятся в OKLab на 19,2 · 16,2 ·
     13,8 · 11,7 при пороге различимости 15, а самая светлая даёт
     контраст 1,61:1 к бумаге. Две верхние линии человек не различит,
     нижнюю почти не увидит.

     Подобранная палитра проверена на трёх видах дальтонизма. Светлая:
     худшая пара 20,4 при обычном зрении, 9,0 при дейтеранопии (порог
     8), минимальный контраст к бумаге 2,45:1. Тёмная подобрана отдельно
     под свою бумагу, а не осветлением светлой: осветление сжимает
     разницу между тонами и роняет различимость до 11,0.

     Цвет закреплён за годом (год % 5) и не зависит от того, сколько
     годов включено: иначе выключение 2023-го перекрасило бы 2024-й. */

  /* Цвет ряда берётся один раз, по номеру ячейки палитры, и дальше вся
     разметка ряда — линия, точки, галочка года, строка легенды — красится
     от него. Так цвет линии и цвет её подписи не могут разойтись. */
  [data-slot="0"] { --line: var(--year-0); }
  [data-slot="1"] { --line: var(--year-1); }
  [data-slot="2"] { --line: var(--year-2); }
  [data-slot="3"] { --line: var(--year-3); }
  [data-slot="4"] { --line: var(--year-4); }

  .years-form {
    display: flex; flex-wrap: wrap; align-items: center;
    gap: var(--s3) var(--s4); margin: 0 0 var(--s3);
  }
  .years-form .lbl {
    text-transform: uppercase; color: var(--ink-muted); margin-right: 2px;
    font-size: var(--t-micro-size);
    font-weight: 600;
    letter-spacing: .06em;
  }
  .years-pick { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
  /* Галочка года — кнопка с полосой своего цвета. Полоса стоит и когда
     год выключен: тогда она бледная, но год уже «свой», и глаз находит
     его на графике по той же краске. */
  .years-pick label {
    display: inline-flex; align-items: center;
    /* Плотнее обычной кнопки: годов на листе тринадцать, и при обычных
       отступах строка ломалась так, что свежий год оставался один на
       второй строке — то есть самый нужный уезжал дальше всех. */
    font-variant-numeric: tabular-nums;
    padding: 5px 12px 5px 9px;
    min-height: 30px;
    border: .5px solid var(--mac-button-edge);
    border-radius: 999px;
    background: var(--mac-button);
    box-shadow: var(--mac-button-shadow);
    cursor: pointer;
    transition: box-shadow .15s ease, transform .1s ease, background-color .15s ease;
  }
  .years-pick label::before {
    content: "";
    width: 9px;
    height: 9px;
    border-radius: 50%;
    opacity: 1;
    background: transparent;
    box-shadow: inset 0 0 0 1.5px var(--line, var(--ink-muted));
  }  .years-pick label:has(input:checked)::before { opacity: 1; box-shadow: 0 0 0 2px color-mix(in srgb, var(--line) 25%, transparent); }
  .years-pick input { position: absolute; opacity: 0; width: 0; height: 0; }
  .years-pick label:focus-within { outline-offset: 2px; }

  /* Сам график. Линии тонкие, сетка отступает назад: на холсте пять
     рядов, и жирная сетка спорит с ними за внимание. */
  .yplot { width: 100%; height: auto; display: block; }
  .yplot .y-grid { stroke: var(--hairline); stroke-width: 1; }
  .yplot .y-level, .yplot .y-month {
    font-size: inherit; fill: var(--ink-muted);
    font-variant-numeric: tabular-nums;
  }
  .yplot .y-level { text-anchor: end; }
  .yplot .y-line {
    fill: none; stroke: var(--line);
    stroke-linejoin: round; stroke-linecap: round;
    stroke-width: 1.75;
  }
  /* Точки мельче, чем на обычном графике: их здесь до шестидесяти, и
     крупные сливаются в бусы. */
  .yplot .y-dot { fill: var(--line); r: 2px; stroke: var(--paper); stroke-width: 1; }
  /* Год подписан у конца своей линии. Легенда сверху тоже есть, но глаз
     читает подпись там, где линия кончилась, а не переводит взгляд. */
  /* Обводка бумагой под подписью года. Линия свежего года кончается
     посреди холста — июлем, потому что август ещё идёт, — и её подпись
     ложится прямо на чужие линии. Обводка отделяет её от них так же,
     как это сделано у крайнего значения на обычном графике. */
  .yplot .y-name {
    font-size: inherit; font-weight: 600; fill: var(--line);
    font-variant-numeric: tabular-nums;
    paint-order: stroke fill; stroke-width: 3.5;
    stroke-linejoin: round;
    stroke: var(--paper);
  }

  .years-legend { margin-top: var(--s3); }
  .years-legend td:first-child { white-space: nowrap; }
  /* ``.btn`` объявляет ``display: inline-flex`` и перебивает
     браузерное правило для ``[hidden]``: кнопка «Показать», спрятанная
     скриптом, оставалась на экране. Селектор здесь весомее. */
  .years-form button[hidden] { display: none; }

  .years-legend .swatch {
    display: inline-block; width: 10px; height: 10px; border-radius: 3px;
    background: var(--line); margin-right: 7px; vertical-align: -1px;
  }

  /* «Роялти по месяцам»: четырнадцать колонок на лист
     Замер 27.08.2026 на экране 1024 px: колонка месяца выходит 47
     пикселей, из них 32 занимает отступ ячейки — под число остаётся
     пятнадцать. Таблица обрезала многоточием все 140 заполненных ячеек,
     и «157 5…» — это уже не оформление, а неверное число.

     Чиним обе половины: узкий отступ здесь и тысячи вместо рублей в
     самих ячейках (см. ``_months_in_thousands``). Горизонтальная
     прокрутка сюда не годится — на неё она и жаловалась. */
  .months-grid td, .months-grid th { padding-left: 5px; padding-right: 5px; }
  /* Название барбершопа отступ сохраняет: оно первое в строке и стоит
     у края карточки. */
  .months-grid td:first-child, .months-grid th:first-child { padding-left: 16px; }

  /* «Сколько нам должны»
     Ответ, который сеть пишет каждый день в двенадцать. Стоит под
     плашкой на Сводке: это первое, что спрашивают. */
  .money-note .money-text {
    margin: 8px 0; padding: 12px 14px;
    background: var(--paper); border: 1px solid var(--hairline);
    border-radius: 8px; white-space: pre-wrap;
    font-family: inherit; font-size: var(--t-body-size);
    line-height: 1.6; font-variant-numeric: tabular-nums;
  }
  /* Деньги — вправо и одной ширины цифр. Общее правило ``td.num`` ниже
     ставит числа по центру, и в столбцах шириной 33 % «2 655 083,35» и
     «5 421,36» стояли серединами друг под другом — разряды не сходились
     (критик 24.09.2026). Свёрток «Готовый ответ» — по левому краю
     таблицы, без отступа в 6 пикселей, как «Чего нет в ответе» под ним. */
  .money-note table td.num, .money-note table th.num {
    text-align: right; font-variant-numeric: tabular-nums;
  }
  .money-note details.how { margin-left: 0; margin-right: 0; }

  /* Глубина карточек
     03.09.2026, решение владельца: карточки должны выглядеть глубокими
     и отзываться на курсор нажатием — плоская вёрстка читалась как
     «непременно», дёшево. Тень двухслойная: короткая контактная и
     длинная мягкая, обе слабые, чтобы страница не стала стопкой бумаг.
     Под курсором карточка приподнимается на два пикселя, тень
     удлиняется; при нажатии на ссылку-плитку — оседает на пиксель, тень
     сжимается: рука чувствует ход. Всё — только через transform и тень,
     без перекраски: цвет в системе означает «хуже» (правило 5). */

    a.card:active, a.tile:active, button.card:active {
    transform: translateY(1px);
    box-shadow: var(--depth-press);
    transition-duration: .08s;
  }
  /* Вложенные карточки не громоздят тени: глубина у внешней. */
  .card .card, .card .tile, .card .card:hover, .card .tile:hover { box-shadow: none; transform: none; }
  @media (prefers-reduced-motion: reduce) {
    .card, .tile { transition: none; }
    .card:hover, .tile:hover, a.card:active, a.tile:active { transform: none; }
  }
  @media print {
    .card, .tile, .card:hover, .tile:hover { box-shadow: none; transform: none; }
  }

/* Вид macOS — 22.09.2026, решение владельца

   «Сделай интерфейс таким, чтобы он был похож на Mac, на iOS: стеклянные
   кнопки, меньше анимации переходов, более элитно и дорого. Каждая
   страница должна быть похожа — ключевые компоненты, плитки, — как будто
   ты в очень умной операционной системе, где у тебя нет вопросов».

   Как это устроено у самой Apple — в «Системных настройках», Finder,
   «Заметках» — и как устроено здесь:

   * **Стекло — только на «хроме».** Боковая панель, переключатели,
     кнопки и прилипающие шапки таблиц — полупрозрачные, с размытием.
     Карточки с числами — плотные: на стекле цифра теряет контраст, а
     это отчётная система. Системе уже случалось быть стеклянной целиком
     и уйти от этого — стекло на всём, с цветными пятнами на подложке,
     читалось как шаблон. Здесь его ровно там, где оно у Apple.
   * **Серый лист, белые группы.** Подложка — серый #f5f5f7, как у
     настроек macOS; карточки — белые, скругление 14, волосяная кромка,
     тень почти невидимая.
   * **Переключатели — как сегменты iOS.** Серая дорожка, выбранное —
     белая «таблетка» с тенью. Один вид у всех: разделы листа, сеть,
     период, окно, состав.
   * **Движение — только у того, на что нажимают.** Раньше приподнималась
     каждая карточка, даже таблица на двести строк, и каждая страница
     проявлялась с анимацией. Теперь страница появляется сразу, а
     отзывается на курсор только плитка-ссылка и кнопка.
   * **Шапки таблиц — как в Finder:** обычным регистром, без разрядки,
     сплошной полосой. Раньше каждая клетка шапки отбрасывала свою тень,
     и шапка распадалась на отдельные плашки с просветами.

   Правило 5 не трогается: цвет — только на отклонениях. */

/* --- движение: только то, на что нажимают --- */
.wrap > *:not(nav.tabs) { animation: none; }
.card, .tile { transition: none; border: .5px solid var(--glass-border); box-shadow: var(--mac-card-shadow); border-radius: var(--r-l); }
.card:hover, .tile:hover { transform: none; box-shadow: var(--mac-card-shadow); }
a.card, a.tile { transition: box-shadow .12s ease, transform .12s ease; }
a.card:hover, a.tile:hover { box-shadow: var(--mac-card-hover); transform: none; }
a.card:active, a.tile:active { transform: scale(.992); box-shadow: var(--mac-card-shadow); }

/* --- боковая панель: матовое стекло, выбранное — как в Finder --- */nav.tabs a { transition: none; }
nav.tabs a.on {
  box-shadow: none;
  font-weight: 600;
}

/* --- переключатели: сегменты iOS, один вид на всю систему --- */

.views a, .pick a {
  border: 0; border-radius: 7px; transition: none; color: var(--ink);
  font-weight: 500;
}
.views a:hover, .pick a:hover { background: rgba(118, 118, 128, .10); }
.views a.on, .pick a.on {
  background: var(--mac-thumb); box-shadow: var(--mac-thumb-shadow);
  border: 0; font-weight: 600;
}

/* --- кнопки: стекло с глубиной и ходом при нажатии --- */
button, .btn {
  border-radius: var(--r-m);
  font-weight: 500;
  transition: transform .08s ease, box-shadow .08s ease;
}
button:hover, .btn:hover {
  border-color: var(--mac-button-edge);
}
button:active, .btn:active {
  transform: scale(.97);
  box-shadow: 0 .5px 1px rgba(0,0,0,.12), inset 0 1px 2px rgba(0,0,0,.08);
}
button.btn-main, .btn.btn-main { border-color: transparent;
}

.btn-quiet:hover { background: rgba(118, 118, 128, .10); }

/* --- поля ввода: как в macOS --- */
input[type="text"], input[type="search"], input[type="date"], input[type="number"],
input[type="password"], select, textarea {
  border-radius: 8px; border: .5px solid var(--mac-button-edge);
  background: var(--glass); box-shadow: inset 0 .5px 1px rgba(0,0,0,.05);
}

/* --- таблицы: шапка как в Finder — сплошная, обычным регистром --- */

td { border-color: var(--hairline); }

/* --- ответ листа: мягкая серая подложка без рамки --- */
/* --- «Срез» и прочие свёртки — как всплывающая кнопка macOS --- */
details.filterbox > summary { border: .5px solid var(--mac-button-edge); border-radius: var(--r-m);
}

/* --- предупреждение внутри карточки: полоска с точкой, как «Ждём от вас».
   Розовая заливка на всю ширину в системной теме (стекло) кричала громче
   самих денег в таблице под ней (ревизия 24.09.2026). Цвет — только на
   слове «Внимание» и точке, приглушённо: остальное читается обычным
   текстом (правило 5). --- */
.alert {
  display: flex; align-items: baseline; gap: 10px;
  margin: 0 0 10px; padding: 2px 0; background: none;
  color: var(--ink); font-size: var(--t-body-size); line-height: 1.45;
}
.alert-dot {
  flex: none; width: 8px; height: 8px; border-radius: 50%;
  background: color-mix(in srgb, var(--critical) 75%, transparent);
  transform: translateY(-1px);
}
.alert b { color: color-mix(in srgb, var(--critical) 80%, var(--ink)); font-weight: 600; }

/* плитки: одна сетка и один кегль на всю систему
   Владелец 22.09.2026: «пять плиток, зачем растянутая информация на
   каждой плитке», «каждая страница должна быть похожа — дизайны плиток».
   На «Сегодня» ряд из пяти стоял рядом с рядами из трёх, растянутых на
   всю ширину, и число в них было на кегль крупнее. Теперь плитка —
   всегда пятая часть ряда, как виджет одного размера: ряд из трёх
   занимает три пятых, а не растягивается. Кегль числа — лестница
   главного ряда (26 → 24 → 22), у всех плиток одна. */
.tiles, .tiles.kpi, .tiles.second {
  grid-template-columns: repeat(5, minmax(0, 1fr));
}@media (max-width: 1440px) {
  .tiles .tile .value, .tiles.kpi .tile .value, .tiles.second .tile .value { font-size: var(--t-display-long); }
}
@media (max-width: 1260px) {
  .tiles, .tiles.kpi, .tiles.second { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .tiles .tile .value, .tiles.kpi .tile .value, .tiles.second .tile .value { font-size: var(--t-title-size); }
}
@media (max-width: 1100px) {
  .tiles, .tiles.kpi, .tiles.second { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 760px) {
  .tiles, .tiles.kpi, .tiles.second { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Главный ряд карточки барбершопа — на всю ширину. Четыре плитки по
   пятой части ряда занимали около 920 пикселей из 1150 и оставляли справа
   дыру в плитку шириной (критик 24.09.2026). Остальные ряды карточки
   держат общую сетку в пятую часть. */
.tiles.branch-main:has(> :nth-child(4):last-child) { grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (max-width: 760px) {
  .tiles.branch-main:has(> :nth-child(4):last-child) { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Плашка под названием в таблице «Учёта» — своей строкой, без многоточия. */
table.fit td .under { display: block; white-space: normal; text-align: left; margin-top: 3px; line-height: 1.3; }
table.fit td .under .chip { white-space: nowrap; font-weight: 500; }

/* «Все показатели помесячно» на «Графике» — двенадцать колонок. По общей
   ширине числа (108) они требовали 1 310 пикселей при 1 150 на листе
   (замер 23.09.2026, окно 1 440), и «Сметки» с «Барбершопами» уезжали
   за край. Самое длинное число здесь — в тысячах, ему хватает 80; шапка
   переносится на две строки. */
.sweep table { width: 100%; }
.sweep td.num, .sweep th.num, .sweep td.num.tight, .sweep th.num.tight { width: auto; min-width: 0; padding-left: 8px; }
.sweep thead th { vertical-align: bottom; }
/* Идущий месяц отделён чертой: он неполный и в общий счёт не идёт (правило 2). */
.sweep tr.running td { border-bottom: 2px solid var(--ink-muted); color: var(--ink-secondary); }

/* Полоса фильтров и «Откуда цифры» — общие детали скелета листа
   (правило 11, решение владельца 23.09.2026; _bar.html, _sources.html).
   */
/* Только форма: у полос прогресса на «Сегодня» и «Загрузке» тот же
   класс ``bar``, и общее правило съедало их заливку (владелец 23.09.2026:
   «пилюля не заполнена»). */
form.bar {
  margin: 0 0 4px; padding: 8px 12px;
  background: var(--glass-bg, var(--paper));
  border: 1px solid var(--hairline);
  -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px);
}
.bar-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 20px; }
.bar-f { display: flex; align-items: center; gap: 8px; }
.bar-l {
  font-size: var(--f-cap); font-weight: 500; letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink-muted);
}
.bar-seg {
  display: inline-flex; gap: 2px; padding: 2px;
  background: color-mix(in srgb, var(--ink) 5.5%, transparent);
  border-radius: var(--r-m);
}
.bar-seg a {
  padding: 4px 11px; border-radius: var(--r-s); white-space: nowrap;
  font-size: var(--f-note); font-weight: 500; color: var(--ink-secondary);
  text-decoration: none; border: 1px solid transparent;
}
.bar-seg a:hover { color: var(--ink); text-decoration: none; }
.bar-seg a.on {
  color: var(--ink); font-weight: 600; background: var(--paper);
  border-color: var(--hairline); box-shadow: var(--shadow-1);
}
.bar-sel { display: inline-flex; gap: 6px; align-items: center; }
.bar-sel select { font-size: var(--f-note); padding: 4px 8px; border-radius: var(--r-s); max-width: 100%; padding-right: 30px; background-position: right 7px center, right 3px center, 0 0; }
.bar-more { margin-left: auto; }
.bar-more > summary {
  cursor: pointer; list-style: none; font-size: var(--f-note); font-weight: 500;
  color: var(--ink-secondary); padding: 4px 10px; border-radius: var(--r-s);
}
.bar-more > summary::-webkit-details-marker { display: none; }
.bar-more > summary::after { content: " ⌄"; color: var(--ink-muted); }
.bar-more[open] { flex-basis: 100%; margin-left: 0; }
.bar-more-body {
  display: flex; flex-wrap: wrap; gap: 10px 14px; align-items: flex-end;
  padding: 10px 0 4px; border-top: 1px solid var(--hairline); margin-top: 6px;
}
.bar-more-body label { display: flex; flex-direction: column; gap: 3px; font-size: var(--f-cap); color: var(--ink-muted); }
.bar-said { margin: 2px 4px 16px; font-size: var(--f-cap); color: var(--ink-muted); }

/* Поля как у всех карточек: было 12/18, текст в 19 px против 25 у соседей (24.09.2026). */
.sources { margin-top: 18px; padding: 16px 24px; }
.sources > summary { cursor: pointer; font-weight: 600; color: var(--ink); }
.sources-body { color: var(--ink-secondary); font-size: var(--f-note); max-width: 900px; }
.sources-body p { margin: 8px 0; }
.sources-body .actions { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; margin-top: 10px; }

/* Детали внутри «Откуда цифры» и «Ещё» (просьбы исполнителей листов
   23.09.2026): плашки в свёртке — без второй рамки и тени, иначе
   «карточка в карточке»; кнопки и флажки в «Ещё» — в ряд с полями;
   списки полосы не шире экрана — на «Двух точках» названия длинные. */
.sources-body .card { margin: 10px 0; padding: 0; border: 0; box-shadow: none; }
.bar-more-body .btn, .bar-more-body .btn-quiet { display: inline-flex; align-items: center; }
.bar-more-body fieldset { border: 0; padding: 0; margin: 0; font-size: var(--f-cap); color: var(--ink-muted); }

/* Период в подписи плитки не рвётся посередине: «1–» в конце строки и
   «6.2026» на следующей читались как два разных числа (24.09.2026). */
.tile .cmp .nowrap { white-space: nowrap; }

/* Плитки — вариант A «виджеты iOS» (выбор владельца 23.09.2026 из трёх
   макетов). Один компонент на все листы: отдельная белая карточка,
   скругление 22, мягкая тень; название обычными буквами в одну строку;
   крупное число; первая плитка главного ряда шире и крупнее — одна
   точка, за которую цепляется глаз. Рост не красится (норма, правило 5),
   красное — только «хуже». У плитки-ссылки постоянный «›».
   */
/* ``:not(#_)`` поднимает вес правила над ``.tiles:has(> :nth-child(5):last-child)``
   выше по файлу: без него ряд оставался пятью равными колонками, и
   главная плитка не становилась шире (замер 23.09.2026). */
.tiles.kpi:not(#_) {
  grid-template-columns: minmax(0, 1.6fr);
  grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr);
  gap: 14px;
}
@media (max-width: 1100px) {
  .tiles.kpi:not(#_) { grid-auto-flow: row; grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .tiles.kpi > .tile:first-child { grid-column: 1 / -1; }
}
.tiles .tile {
  background: var(--paper); border: 1px solid var(--hairline);
  border-radius: 22px; padding: 18px 14px 16px; min-height: 164px; /* было 20 сбоку: плитки без .kpi (карточка точки) стояли в 21 px против 15 (24.09.2026) */
  box-shadow: 0 1px 1px rgba(0,0,0,.03), 0 8px 24px rgba(0,0,0,.05);
  overflow: hidden; transition: border-color .15s ease;
}
.tiles .tile .name {
  text-transform: none; letter-spacing: 0; font-size: var(--t-body-size); font-weight: 600;
  color: var(--ink-secondary); margin: 0;
  display: block; min-height: 0;
  padding-right: 18px;
  /* Шкала шрифтов (24.09.2026): названия плиток стали 12,5 px вместо 12 и
  «Вне регламента», «Долг на сегодня», «Выпали из базы» обрезались
  многоточием в узкой плитке. Название — до двух строк, а не обрез. */
  white-space: normal !important;
  overflow: visible !important;
  text-overflow: clip !important;
  overflow-wrap: break-word;
}
.tiles .tile .value,
.tiles.kpi .tile .value, .tiles.second .tile .value {
  font-size: var(--t-display-size); font-weight: 650; letter-spacing: -.025em; line-height: 1.08;
  margin: 10px 0 6px; font-variant-numeric: tabular-nums;
}
.tiles.kpi > .tile:first-child .value { font-size: var(--t-hero-size); }
.tiles .tile .value .unit { font-size: var(--t-body-size); font-weight: 600; color: var(--ink-muted); margin-left: 4px; }
.tiles .tile .cmp { font-size: var(--t-small-size); line-height: 1.45; color: var(--ink-secondary); }
.tiles .tile .delta.up { color: var(--good); } /* рост зелёным — владелец 24.09.2026 */
.tiles a.tile::after {
  content: "›"; position: absolute; top: 14px; right: 16px;
  font-size: var(--t-title-size); line-height: 1; color: var(--ink-muted); opacity: 1;
}
.tiles a.tile:hover { border-color: var(--ink-muted); }
@media (max-width: 1440px) {
  .tiles .tile .value, .tiles.kpi .tile .value { font-size: var(--t-display-long); }
  .tiles.kpi > .tile:first-child .value { font-size: var(--t-hero-size); }
}

/* Сравнение в плитке — две строки: выбранное чёрным, второе тише. */
.tiles .tile .cmp .cmp-row { display: block; }
.tiles .tile .cmp .cmp-row:not(.on) { color: var(--ink-muted); }
.tiles .tile .cmp .cmp-row.on { font-weight: 600; color: var(--ink); }
.tiles .tile .cmp .cmp-row .to { color: inherit; font-weight: inherit; }

/* График года в плитке — во всю ширину, прижат к низу. */
.tiles .tile .tile-spark { display: block; height: 38px; margin: 10px -20px -16px; }
.tiles .tile .tile-spark path { fill: color-mix(in srgb, var(--ink) 6%, transparent); }
.tiles .tile .tile-spark polyline { fill: none; stroke: var(--ink); stroke-width: 1.6; }
/* Две карточки рядом на «Оборотах»: «Под угрозой» и структура сети. */
.pair { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; align-items: stretch; margin-bottom: 14px; }
.pair > .card { margin: 0; }
.pair .outlook.risk .head { flex-direction: column; align-items: flex-start; gap: 12px; }
.pair .outlook.risk .facts { display: flex; flex-direction: column; gap: 6px; width: 100%; }
.pair .outlook.risk .facts > span { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
@media (max-width: 1100px) { .pair { grid-template-columns: minmax(0, 1fr); } }

/* Всплывающие пояснения (просьба владельца 23.09.2026)
   На виду — только число или короткая строка; всё, что его объясняет,
   появляется мягко при наведении (и при фокусе с клавиатуры) и уходит.
   Первый пример — свежесть данных: три строки над первым числом стали
   одной, «данные по 22.09», в правом краю шапки. */
header.top .freshness { position: relative; margin: 0; }
header.top .freshness .fresh-head { cursor: default; outline: none; }
.fresh-pop, .hover-pop {
  z-index: 40; top: calc(100% + 10px); right: 0;
  width: max-content; max-width: min(380px, 86vw);
  padding: 12px 14px; border-radius: 14px;
  font-size: var(--t-small-size); line-height: 1.55;
  opacity: 0; visibility: hidden; transform: translateY(-4px) scale(.98);
  transform-origin: top right;
  pointer-events: none;
  background: var(--lg-pop);
  -webkit-backdrop-filter: var(--lg-blur-nav);
  backdrop-filter: var(--lg-blur-nav);
  color: var(--ink-secondary);
  position: fixed;
}
.freshness:hover .fresh-pop, .freshness:focus-within .fresh-pop,
.has-pop:hover > .hover-pop, .has-pop:focus-within > .hover-pop {
  opacity: 1; visibility: visible; transform: none; pointer-events: auto;
  transition: opacity .22s ease, transform .22s ease, visibility 0s;
}
/* Мостик: пока мышь идёт от строки к окошку, окошко не гаснет. */
.fresh-pop::before, .hover-pop::before { content: ""; position: absolute; left: 0; right: 0; top: -12px; height: 12px; }
.fresh-pop p { margin: 0 0 8px; } .fresh-pop p:last-child { margin-bottom: 0; }
.fresh-pop .fresh-tail { color: var(--ink); }
.fresh-pop .away { display: block; }
.fresh-pop .away-title { font-weight: 600; color: var(--ink); margin-top: 10px; }
.fresh-pop .away-list { margin: 0; padding: 0; border: 0; background: none; max-width: none; }
.has-pop { position: relative; }
@media (prefers-reduced-motion: reduce) { .fresh-pop, .hover-pop { transition: none; } }

/* Доля услуг и товаров (23.09.2026): полоски делали эти плитки на вид
   ниже соседних с графиком. Теперь доля — крупная строка, без полоски. */
.tiles .tile .tile-part { display: block; color: var(--ink-muted); font-size: var(--t-body-size); }
.tiles .tile .tile-part b { display: block; color: var(--ink); font-size: var(--t-title-size); font-weight: 650; letter-spacing: -.02em; line-height: 1.15; margin-bottom: 1px; font-variant-numeric: tabular-nums; }

/* «Где именно теряем гостей» — одна карточка, строка на шаг (23.09.2026). */
.card.loss { padding: 24px; border-radius: 22px; }
.loss-lead { margin: 0 0 12px; font-size: var(--t-head-size); font-weight: 600; }
.loss-head, .loss-row { display: grid; grid-template-columns: minmax(0, 1fr) 110px 28px 110px 170px; align-items: center; column-gap: 12px; }
.loss-head { font-size: var(--t-small-size); color: var(--ink-muted); padding: 0 0 6px; }
.loss-head span:nth-child(2), .loss-head span:nth-child(4) { text-align: right; }
.loss-row { padding: 11px 0; border-top: 1px solid var(--hairline); font-variant-numeric: tabular-nums; }
.loss-row .k { font-weight: 600; cursor: default; width: max-content; max-width: 100%; border-bottom: 1px dotted color-mix(in srgb, var(--ink) 25%, transparent); }
.loss-row .k .hover-pop { left: 0; right: auto; transform-origin: top left; font-weight: 400; }
.loss-row .was { text-align: right; color: var(--ink-muted); }
.loss-row .arrow { text-align: center; color: var(--ink-muted); }
.loss-row .now { text-align: right; font-weight: 650; font-size: var(--t-head-size); }
.loss-row .chg { display: flex; flex-direction: column; align-items: stretch; text-align: right; gap: 4px; font-weight: 600; }
.loss-row .chg .bar { display: block; height: 4px; border-radius: 2px; background: color-mix(in srgb, var(--ink) 25%, transparent); min-width: 4px; margin-left: auto; }
.loss-row .chg .bar.dn { background: color-mix(in srgb, var(--critical) 55%, transparent); }
.loss-row.key { background: color-mix(in srgb, var(--ink) 4%, transparent); margin: 0 -22px; padding-left: 22px; padding-right: 22px; }
.loss-row.aside { color: var(--ink-muted); }
.loss-row.aside .k { font-weight: 400; }
@media (max-width: 700px) {
  .loss-head { display: none; }
  .loss-head, .loss-row { grid-template-columns: minmax(0, 1fr) auto auto auto; }
  .loss-row .chg { grid-column: 1 / -1; align-items: flex-start; }
}

/* Заголовок блока со своим переключателем справа (например, период
   графиков на «Графике» — он меняет только этот блок). */
.block-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.block-row .block-head { margin-bottom: 0; }
.block-row .views { margin: 0; }

/* Стрелка у списков — в самом конце: слой «как в macOS» выше задаёт
   ``background`` сокращением и стирал её. */.verdict .more-dot { color: var(--ink-muted); font-weight: 400; font-size: .85em; cursor: default; }
.verdict.has-pop .hover-pop { font-size: var(--t-body-size); font-weight: 400; }
/* Места под стрелку в узких списках полосы фильтров: «20⌄26» —
   стрелка наезжала на год (владелец 23.09.2026). *//* Доля у «Услуг» и «Товаров» — на той же высоте, что сравнения у
   соседних плиток, а не прижатой ко дну. */
.tiles .tile .cmp:has(> .tile-part) { margin-top: 0; }
/* Скрытая скриптом кнопка «Показать» оставалась видна: стиль кнопки
   задаёт ``display`` и перебивал атрибут ``hidden``. */
.bar-go[hidden] { display: none !important; }

/* Таблица барбершопов на «Оборотах»: числа и их заголовки — по центру
   столбца, стрелка сортировки — в той же строке, что заголовок
   (владелец 23.09.2026: «столбцы не отцентрованы, цифры уехали»). */
table.centered th.num, table.centered td.num, table.centered th.spark, table.centered td.spark { text-align: center; }
table.centered th.num .sort { justify-content: center; }
table.centered th .sort { white-space: nowrap; }
table.centered td.spark svg { margin: 0 auto; width: 60px; }
table.fit.centered { table-layout: auto; }
table.centered th, table.centered td { white-space: nowrap; }
table.centered th a.sort { flex-wrap: nowrap; white-space: nowrap; }
table.centered th.num, table.centered td.num, table.centered th.spark, table.centered td.spark,
table.centered th.tight, table.centered td.tight { width: 1%; }
table.centered td.one-line:nth-child(2) { overflow: hidden; text-overflow: ellipsis; max-width: none; }
table.centered tbody td { padding-top: 11px; padding-bottom: 11px; }
/* Узкий экран: ничто не шире окна (замер 23.09.2026).
   До правки на 900 px «Показатели» были шириной 1 126 px — строка
   «Показатель» из девяти кнопок в одну линию; на 500 px все листы
   с полосой фильтров были 523 px, а вкладки разделов и «Обе сети /
   ТОПГАН / FIDEL» уходили за край молча, «Сегодня» срезалось слева
   отрицательным полем панели. Правило одно: ряды кнопок переносятся,
   а широкая таблица прокручивается в своей рамке, с приклеенным
   первым столбцом и видимым признаком «правее ещё».
   */
.wide-hint { display: none; font-size: var(--f-cap); color: var(--ink-muted); margin: 0 0 6px; }
@media (max-width: 940px) {
  nav.tabs {
    margin: 0 0 14px; padding: 6px 0; border-right: 0;
  }  nav.tabs .navfind { flex: 1 1 100%; }
  nav.tabs .util { margin: 0; }
  /* Знак на узком экране не нужен: он дублирует «Сегодня» и занимал
     отдельную строку в 40 px над вкладками. */
  nav.tabs a.brand { display: none; }
  nav.tabs .subcount { margin-left: 3px; }
  header.top, .views, .bar-f, .bar-seg { flex-wrap: wrap; max-width: 100%; min-width: 0; }
  .views { display: inline-flex; }
  .bar-sel { flex-wrap: wrap; }
  .bar-more { margin-left: 0; }
  /* Широкая таблица — своя прокрутка, а не срез. */
  .card:has(> table), .scroll.fits { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  /* Первый столбец — имя строки — стоит на месте, пока числа едут. */
  .scroll table tr > :first-child, .card:has(> table) > table tr > :first-child {
    position: sticky; left: 0; z-index: 1; background: var(--paper);
  }
  .wide-hint { display: block; }
}
@media (max-width: 760px) {
  /* На телефоне панель в три строки съедала бы полэкрана при прокрутке. */
  nav.tabs { position: static; }
}
/* Признак «правее ещё»: подпись над таблицей видна только на узком
   экране; правый край таблицы притушен, пока есть куда крутить, и
   проясняется в конце (там, где браузер умеет привязывать анимацию
   к прокрутке). У таблицы, которая влезла, шкалы прокрутки нет —
   анимация не идёт, и край не гаснет зря: начальное значение 0. */
@supports (animation-timeline: scroll(self inline)) {
  @media (max-width: 940px) {
    .scroll, .card:has(> table), .matrix {
      mask-image: linear-gradient(to left, transparent 0, #000 var(--fade, 28px));
      animation: wide-fade linear both; animation-timeline: scroll(self inline);
    }
    @property --fade { syntax: '<length>'; inherits: false; initial-value: 0px; }
  }
}
@keyframes wide-fade { 0%, 94% { --fade: 28px; } 100% { --fade: 0px; } }

/* Liquid Glass — 24.09.2026, решение владельца

   «Элитно, как новая операционная система Apple, один в один». Слой лежит
   последним и переопределяет всё выше; прежние слои не удалены, чтобы
   история решений (почему бумага, почему «вид macOS») осталась читаемой.

   Как устроено, по порядку macOS 26 / iOS 26:
   * Под листом — живой фон: три мягких цветных пятна, неподвижных
     (position: fixed). Без них стеклу нечего размывать, и полупрозрачная
     карточка выглядит серым пластиком — на этом стекло и провалилось в
     прошлый раз.
   * Карточка — стекло: заливка 74 % белого (в тёмной 64 % графита),
     размытие 28 px с насыщением 180 %, светлая кромка сверху (inset-тень)
     и мягкая тень снизу. Заливка плотная намеренно: цифра на стекле не
     должна терять контраст (замер ниже, у токенов).
   * Цвет — системная палитра Apple. Синий — только «сюда можно нажать» и
     «выбрано»: кнопка, фокус, выделение, линия графика. На числах цвет
     появляется лишь как «хуже» — правило 5 не трогается.
   * Тёмная — не инверсия, а тёмный режим macOS: окно #1c1c1e, стекло
     графитовое, текст #f5f5f7, вторичный — серые macOS.

   Контраст текста (замер 24.09.2026, WCAG, к фону карточки):
     светлая — основной 16,8:1, вторичный #424245 10,0:1, тихий #636368
     5,6:1 к белому и 5,0:1 к листу #f0f1f6; «хуже» #d70015 5,4:1;
     белый на кнопке #0071e3 4,7:1.
     тёмная — основной 12,8:1, вторичный 9,2:1, тихий #a1a1a6 5,4:1 к
     стеклу #2c2c2e; «хуже» #ff6961 4,9:1; ссылка #409cff 4,9:1.
   Системные #007aff / #0a84ff дают с белым текстом 4,0 и 3,7 — ниже
   порога 4,5, поэтому на подписях и кнопках стоят их «доступные» пары,
   а чистые системные цвета — на линиях, кольцах фокуса и заливках.
   */

:root {
  --chart: var(--sys-blue);
}
/* «Светлая» — просто светлая (владелица 24.09.2026): ровный фон без
   пятен, карточки белые и непрозрачные, без размытия. Стекло с
   подсветками — отдельный режим «Apple» (data-look="glass"). Правило
   стоит до тёмных: у них тот же вес, и тёмные побеждают порядком. */

/* --- живой фон: пятна неподвижны, лист едет над ними --- */
body {
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro", system-ui, "Helvetica Neue", sans-serif;
  background: var(--page);
}
body::before {
  content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background:
    radial-gradient(40vw 34vw at 8% 6%, var(--blob-1), transparent 70%),
    radial-gradient(36vw 32vw at 92% 14%, var(--blob-2), transparent 70%),
    radial-gradient(44vw 36vw at 70% 96%, var(--blob-3), transparent 70%),
    radial-gradient(30vw 26vw at 22% 78%, var(--blob-4), transparent 70%),
    var(--page);
}
::selection { background: color-mix(in srgb, var(--sys-blue) 26%, transparent); color: var(--ink); }

/* --- стекло: карточки, плитки, полоса фильтров, вывод --- */
.card, .tiles .tile, form.bar, .verdict {
  background: var(--lg-fill);
  -webkit-backdrop-filter: var(--lg-blur); backdrop-filter: var(--lg-blur);
  border: .5px solid var(--lg-rim);
  box-shadow: var(--lg-shadow);
}
.card, .tiles .tile { border-radius: 20px; }
form.bar { border-radius: 16px; }
.verdict { background: var(--mac-verdict); border-radius: 14px; box-shadow: inset 0 1px 0 var(--lg-edge), 0 .5px 1px rgba(0,0,0,.04); }
.card:hover, .tiles .tile:hover { box-shadow: var(--lg-shadow); transform: none; }
a.card:hover, .tiles a.tile:hover { box-shadow: var(--lg-shadow-hover); transform: translateY(-1px); border-color: var(--lg-rim); }
a.card, .tiles a.tile { transition: box-shadow .16s ease, transform .16s ease; }
a.card:active, .tiles a.tile:active { transform: scale(.992); }
/* Вложенное стекло не размывает ещё раз: двойной blur мутит цифры и
   дорого стоит браузеру. */
.card .card, .card .tile, .card details.card {
  -webkit-backdrop-filter: none; backdrop-filter: none;
  background: color-mix(in srgb, var(--ink) 3%, transparent); box-shadow: none;
}
.sources-body .card { background: none; }

/* --- боковая панель — стекло сайдбара macOS --- */
nav.tabs {
  background: var(--mac-chrome);
  -webkit-backdrop-filter: var(--lg-blur-nav); backdrop-filter: var(--lg-blur-nav);
  border-right: .5px solid var(--hairline);
}
nav.tabs a:hover { background: rgba(118, 118, 128, .10); text-decoration: none; color: var(--ink); }
nav.tabs a.on { background: rgba(118, 118, 128, .18); color: var(--ink); }

/* --- сегменты iOS: одна дорожка, выбранное — стеклянная таблетка --- */
.views, .bar-seg {
  background: var(--mac-track); border: 0; border-radius: 10px; padding: 2px; gap: 0;
  box-shadow: inset 0 .5px 1px rgba(0,0,0,.06);
}
.views a, .pick a, .bar-seg a {
  border: 0; border-radius: 8px; color: var(--ink); font-weight: 500; cursor: pointer;
  transition: background-color .15s ease, box-shadow .15s ease;
}
.views a:hover, .pick a:hover, .bar-seg a:hover { background: rgba(118, 118, 128, .14); text-decoration: none; color: var(--ink); }
.views a.on, .pick a.on, .bar-seg a.on {
  background: var(--mac-thumb); box-shadow: var(--mac-thumb-shadow);
  border: 0; font-weight: 600; color: var(--ink);
}

/* --- кнопки --- */
button, .btn {
  background: var(--mac-button); border: .5px solid var(--mac-button-edge);
  box-shadow: var(--mac-button-shadow); color: var(--ink); cursor: pointer;
  -webkit-backdrop-filter: var(--lg-blur-btn); backdrop-filter: var(--lg-blur-btn);
}
button:hover, .btn:hover { filter: none; background: var(--mac-button); box-shadow: var(--mac-button-shadow), 0 0 0 3px rgba(118,118,128,.10); }
button.btn-main, .btn.btn-main, button[type="submit"].btn-main {
  background: var(--mac-main); color: #fff; border-color: rgba(0,0,0,.08);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.28), 0 1px 2px rgba(0,40,120,.2), 0 6px 16px -6px rgba(0,113,227,.55);
}
button.btn-main:hover, .btn.btn-main:hover { filter: brightness(1.06); }
:where(a, button, select, input, textarea, summary, label, [tabindex]):focus-visible {
  outline: none; box-shadow: var(--focus-ring); border-radius: 8px;
}

/* --- поля и списки: как всплывающая кнопка macOS, явно нажимаемые --- */
select {
  background-color: transparent;
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='14' viewBox='0 0 10 14'%3E%3Cpath d='M2 5l3-3 3 3M2 9l3 3 3-3' fill='none' stroke='white' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"),
    linear-gradient(180deg, #1a86f5, var(--sys-blue-btn)),
    var(--mac-button);
  background-repeat: no-repeat;
  background-position: right 8px center, right 4px center, 0 0;
  background-size: 10px 14px, 18px 20px, 100% 100%;
  border: .5px solid var(--mac-button-edge); border-radius: 8px;
  box-shadow: var(--mac-button-shadow);
  padding-right: 32px; cursor: pointer; color: var(--ink); font-weight: 500;
  -webkit-appearance: none; appearance: none;
}
select:hover { box-shadow: var(--mac-button-shadow), 0 0 0 3px rgba(118,118,128,.12); }

input[type="text"], input[type="search"], input[type="date"], input[type="number"], input[type="password"], textarea {
  background: var(--lg-fill-strong); border: .5px solid var(--mac-button-edge); border-radius: 8px;
  box-shadow: inset 0 .5px 1.5px rgba(0,0,0,.08);
}

/* --- тема: одна кнопка и меню macOS с галочкой --- */
.theme-pick { position: relative; }
.theme-pick .theme-switch { width: 100%; display: flex; align-items: center; gap: 8px; }
.theme-pick .theme-switch .theme-now { flex: 1 1 auto; text-align: left; }

/* --- таблицы: шапка — стеклянная полоса, строки с подсветкой --- */
th {
  background: var(--mac-head); color: var(--ink-secondary);
  -webkit-backdrop-filter: var(--lg-blur-btn); backdrop-filter: var(--lg-blur-btn);
}
tbody tr { transition: background-color .12s ease; }
tbody tr:hover > td { background: color-mix(in srgb, var(--sys-blue) 5%, transparent); }

/* --- годы на «Графике»: чипы, которые видно, что нажимаются --- */

.years-pick label { gap: 7px;
  color: var(--ink-secondary); font-weight: 500;
}

.years-pick label:hover { box-shadow: var(--mac-button-shadow), 0 0 0 3px color-mix(in srgb, var(--line, var(--ink)) 18%, transparent); color: var(--ink); }
.years-pick label:active { transform: scale(.96); }
.years-pick label:has(input:checked) {
  background: color-mix(in srgb, var(--line) 16%, var(--lg-fill-strong));
  border-color: color-mix(in srgb, var(--line) 55%, transparent);
  color: var(--ink); font-weight: 600;
  box-shadow: inset 0 1px 0 var(--lg-edge), 0 1px 2px rgba(0,0,0,.06), 0 4px 12px -4px color-mix(in srgb, var(--line) 45%, transparent);
}
.years-pick label:has(input:checked)::before { background: var(--line); }
.years-pick label:has(input:disabled:not(:checked)) { cursor: not-allowed; }
.years-pick label:focus-within { outline: none; box-shadow: var(--focus-ring); }

/* --- графики: тонкие сглаженные линии, заливка-градиент, подсказка --- */
.plot-grid, .yplot .y-grid { stroke: var(--hairline); stroke-width: 1; stroke-dasharray: 2 4; }
.plot-level, .plot-month, .yplot .y-level, .yplot .y-month { fill: var(--ink-muted); font-weight: 500; }
.plot-area { fill: url(#lg-area); stroke: none; }

.plot-dot { fill: var(--chart); stroke: var(--paper); stroke-width: 1.5; opacity: 0; transition: opacity .12s ease; }

.plot-pt:last-of-type .plot-dot { opacity: 1; }

.plot-guide { stroke: var(--chart); stroke-width: 1; stroke-dasharray: 2 3; opacity: 0; pointer-events: none; }
.plot-tip, .yplot .y-tip {
  opacity: 0; pointer-events: none; font-weight: 600; font-size: 1.05em; fill: var(--ink);
  paint-order: stroke fill; stroke: var(--paper); stroke-width: 4px; stroke-linejoin: round;
  font-variant-numeric: tabular-nums; transition: opacity .1s ease;
}
.plot-pt:hover .plot-dot, .plot-pt:hover .plot-guide, .plot-pt:hover .plot-tip { opacity: 1; }
.plot-pt:hover .plot-dot { r: 4.5px; }
.plot-hit:hover { fill: color-mix(in srgb, var(--chart) 5%, transparent); }
.plot-pt:hover ~ text.plot-now { opacity: 0; }

.yplot .y-hot { fill: transparent; }
.yplot .y-pt:hover .y-dot { r: 4.5px; }
.yplot .y-pt:hover .y-tip { opacity: 1; }

[data-private] .plot-tip, [data-private] .y-tip, [data-private] .y-hot { display: none; }

/* Графики в плитках — тот же язык: синяя линия и заливка-градиент. */
.tiles .tile .tile-spark path:not(.l) { fill: url(#lg-area); }
.tiles .tile .tile-spark path.l { fill: none; stroke: var(--chart); stroke-width: 1.5; stroke-linejoin: round; stroke-linecap: round; }
.tiles .tile .tile-spark circle { fill: var(--chart); stroke: var(--paper); stroke-width: 1.5; }

.lg-defs { position: absolute; width: 0; height: 0; overflow: hidden; }
.lg-defs .top { stop-color: var(--chart-fill-top); }
.lg-defs .bottom { stop-color: var(--chart-fill-bottom); }

/* Таблицы — одно правило на всю систему (по образцу «Барбершопов» на
   «Оборотах», владелица 24.09.2026: «столбцы сильно развёрнуты там, где
   можно сделать уже»).

   Ширина — по содержимому: числовой столбец ровно по своему числу
   (width: 1% + nowrap), заголовок в одну строку и по центру над числом,
   остаток забирает название. Раньше у каждого листа были свои проценты
   в <colgroup> и table-layout: fixed — числа стояли в 150-пиксельных
   колоннах с дырами по 60–90 пикселей.

   Проценты из <colgroup> сняты !important: они стоят в разметке
   атрибутом style, и иначе их не перебить. Минимальная ширина «широких»
   таблиц (1 150 px) снята тоже: она и выталкивала «Средний чек» за край
   карточки на 1 440 (замер: рамка 1 150, таблица 1 190).
   */
/* Только таблицы с шапкой. Доски без шапки («Барберы», три узкие
   колонки) держатся на table-layout: fixed и многоточии: в авто-раскладке
   длинное имя раздвигало доску шире её колонки (замер 24.09.2026: 405
   при 373). */
table.fit:has(> thead), .card table:has(> thead), table.centered { table-layout: auto; }
table.fit.wide, table.fit.staff.wide, table.fit.debts.wide, table.fit.retention.wide { min-width: 0; }
table:has(> thead) col { width: auto !important; }
thead th { white-space: nowrap; vertical-align: bottom; }
th.num, td.num { width: 1%; white-space: nowrap; text-align: center; padding-left: 12px; padding-right: 12px; }
th.num .sort { justify-content: center; }
td.num { font-variant-numeric: tabular-nums; }
tbody td { padding-top: 9px; padding-bottom: 9px; }
/* Длинное название не распирает таблицу: у ячейки в авто-раскладке
   max-width не работает, поэтому ограничена ссылка внутри неё. */
td.one-line { white-space: nowrap; }
td.one-line > a, td.one-line > .name { display: inline-block; max-width: 280px; overflow: hidden; text-overflow: ellipsis; vertical-align: bottom; }

table.centered th:nth-child(4), table.centered td:nth-child(4) { width: auto; padding-right: 14px; }
/* «Все показатели помесячно» — двенадцать столбцов: здесь заголовкам
   можно в две строки, иначе таблица шире листа. */
.sweep thead th { white-space: normal; }

/* --- всплывающие пояснения: стекло своей темы ---
   Фон окна брал ``var(--surface, #fff)`` — такого токена нет, и в тёмной
   теме подставлялся белый: светлый текст на белой плашке (владелица
   24.09.2026). Теперь — стекло из токенов темы; заливка 92 %, чтобы
   текст держал контраст, что бы ни лежало под окном: тихий текст на
   нём 5,4:1 в тёмной и 5,4:1 в светлой. */

.fresh-pop, .hover-pop {
  border: .5px solid var(--lg-rim);
  box-shadow: inset 0 1px 0 var(--lg-edge), var(--lg-pop-shadow);
}

/* «Барбершопы» на «Оборотах» обязаны влезать целиком и на 1 440, и на
   ноутбуке 1 280 (замер 24.09.2026: естественная ширина 1 184 при
   рамке 1 150 и 990 — «Средний чек» резался пополам). Сжимаем поля,
   название и город, а не заголовки: они остаются в одну строку. */
table.centered th.num, table.centered td.num, table.centered th.spark, table.centered td.spark,
table.centered th.tight, table.centered td.tight { padding-left: 9px; padding-right: 9px; }

table.centered td.one-line > a { max-width: 240px; }
table.centered td.one-line > .name { max-width: 130px; }
@media (max-width: 1360px) {
  table.centered td.one-line > a { max-width: 150px; }
  table.centered td.one-line > .name { max-width: 100px; }
  table.centered th.num, table.centered td.num, table.centered th.spark, table.centered td.spark,
  table.centered th.tight, table.centered td.tight { padding-left: 7px; padding-right: 7px; }
  table.centered th, table.centered td { font-size: var(--t-body-size); }
}

/* Названия в одну строку на ноутбуке уже: 280 пикселей под название
   делали «Барберов» шириной 1 302 при рамке 1 150 (замер 24.09.2026). */
@media (max-width: 1500px) {
  td.one-line > a, td.one-line > .name { max-width: 170px; }
  th.num, td.num { padding-left: 8px; padding-right: 8px; }
  table.fit th, table.fit td { padding-left: 8px; padding-right: 8px; }
}
@media (max-width: 1360px) {
  td.one-line > a, td.one-line > .name { max-width: 160px; }
}

/* Повестка на «Сегодня» разложена CSS-колонками (masonry без дыр — решение
   владельца 23.09.2026). Карточка с backdrop-filter внутри колонок в
   Chrome и Safari теряет фон и шапку во второй и третьей колонке — в
   тёмной теме было видно одни строки, первая строка пропадала
   (владелица 24.09.2026). Здесь стекло без размытия: плотная заливка
   того же тона; колонки остаются. */
section.agenda > .card:not(#_) {
  -webkit-backdrop-filter: none; backdrop-filter: none;
  background: var(--lg-fill-strong);
}

/* Спрятанное окошко не должно раздвигать лист. Невидимое (visibility:
   hidden) оно всё равно занимает место: на «Итоге по сети» окошко у
   прочерка в «% прироста» делало лист шириной 1 698 при окне 1 440 —
   появлялась прокрутка вбок (замер 24.09.2026). В покое окошко —
   position: fixed за нижним краем окна: такое в ширину листа не входит.
   Появляется плавно, прячется сразу — иначе гасло бы не на своём месте. */
.fresh-pop, .hover-pop { transition: none; }
.freshness:hover .fresh-pop, .freshness:focus-within .fresh-pop,
.has-pop:hover > .hover-pop, .has-pop:focus-within > .hover-pop { position: absolute; }

/* Боковая панель — на том же слое, что страница (владелица 24.09.2026,
   тёмная тема, «Услуги»): своя заливка и размытие давали прямоугольник,
   начинавшийся в ~35 px от края окна (поле .wrap), с жёстким краем и
   градиентом, не совпадавшим с фоном. Теперь фона у панели нет вовсе —
   под ней те же пятна листа, от края окна; разделитель — волосяная
   линия низкой прозрачности. */
nav.tabs:not(#_) {
  background: transparent;
  -webkit-backdrop-filter: none; backdrop-filter: none;
  box-shadow: none;
  border-right: 1px solid color-mix(in srgb, var(--ink) 8%, transparent);
}
/* Кнопки и поля в тёмной теме — приглушённое стекло: прежняя заливка
   (серый 90→72 %) светилась на тёмном листе ярче карточек. */

/* Полное число оборота в плитке (владелец 24.09.2026): девять цифр
   набираются мельче, чтобы не вылезать за край плитки. */
.tiles .tile .value .long { font-size: .78em; letter-spacing: -.02em; }

/* Таблица в карточке начинается на той же линии, что заголовок
   карточки: первый столбец стоял в 8 px от рамки при заголовке в 24 —
   текст «прилипал» к левому краю (владелец 24.09.2026, «Клиенты»). */
.card table th:first-child, .card table td:first-child { padding-left: 24px; }
.card table th:last-child, .card table td:last-child { padding-right: 24px; }

/* Тёмная тема мягче (владелица 24.09.2026: «чуть уменьши прозрачность,
   слишком контрастно; яркость текста чуть убавь»). Карточки плотнее —
   сквозь них меньше светит фон; пятна фона вдвое тише; текст на ступень
   темнее белого. Контраст основного текста к карточке #2c2c2e — 11:1,
   приглушённого #98989d — 4,9:1 (≥ 4,5 по WCAG). */

/* Узкий экран, до 700 px (критик 24.09.2026, замер на 500 px).

   Меню разделов переносилось в четыре строки, подменю — в три, и
   вывод листа начинался на 815 px: первый экран телефона целиком
   уходил на навигацию. Теперь меню — одна строка, подменю — одна
   строка, обе прокручиваются вбок внутри себя; страница вбок не едет.
   */
@media (max-width: 700px) {
  nav.tabs {
    flex-wrap: nowrap; overflow-x: auto; overflow-y: hidden;
    scrollbar-width: none; -webkit-overflow-scrolling: touch;
    max-width: 100%;
  }
  nav.tabs::-webkit-scrollbar { display: none; }
  nav.tabs .row { flex-wrap: nowrap; flex: 0 0 auto; }
  nav.tabs .navfind { display: none; }
  nav.tabs a, nav.tabs .theme-switch { white-space: nowrap; flex: 0 0 auto; }
  header.top .views, .views.jump, .views.period {
    display: flex; flex-wrap: nowrap; overflow-x: auto; max-width: 100%;
    scrollbar-width: none; -webkit-overflow-scrolling: touch;
  }
  header.top .views::-webkit-scrollbar, .views.jump::-webkit-scrollbar,
  .views.period::-webkit-scrollbar { display: none; }
  .views a { white-space: nowrap; flex: 0 0 auto; }
}

/* До 600 px поля «Ещё» (поиск, порядок, показатель) стоят столбиком
   на всю ширину. Раньше сетка держала колонку не уже 215 px, и на
   500 px поля уходили за край: до 568 px на «Оборотах», 541 px на
   «Барберах», 518 px на «Итоге», 511 px на «Проверках». */
@media (max-width: 600px) {
  .bar-more-body { display: grid !important; grid-template-columns: minmax(0, 1fr) !important; }
  .bar-more-body > *, .bar-more-body label,
  .bar-more-body select, .bar-more-body input { min-width: 0; width: 100%; max-width: 100%; }
  .filters .more .fields { grid-template-columns: minmax(0, 1fr); }

  /* «Год к году»: каждая подпись — над своим списком, а не в конце
     чужой строки («СОСТАВ» висел справа от «Оборота»). */
  .years-form { display: grid; grid-template-columns: minmax(0, 1fr); gap: 6px; }
  .years-form .lbl { margin-top: 8px; }
  .years-form select { width: 100%; }

  /* Подписи месяцев на графике — через одну: целиком они сливались. */
  .plot .plot-month.alt { display: none; }

  /* До 600 px плитки держали десктопную высоту 164 px, и под подписью
     оставалось 60–80 px пустоты («Итог по сети», «Сегодня»). На «Сегодня»
     «Визиты»/«Новые» и шесть плиток «Наши деньги»/«Сеть» стоят по две
     в ряд, а не по одной: в пол-экрана их числа помещаются (самое
     длинное, «2 637 331 ₽», — около 170 px из 214). Десктоп не меняется. */

  .tiles .tile, .tiles.kpi .tile, .six .tile, .today-grid .side { min-height: 0; }
  .tiles.today-grid.today-grid, .tiles.six.six {
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
  }
  .tiles.today-grid.today-grid > .hero { grid-column: 1 / -1; }
  /* Две пилюли сравнения стояли столбиком — теперь в строку, с
     переносом и зазором 8 px, когда не влезают. */
  .today-grid .hero .chips.chips {
    flex-direction: row; flex-wrap: wrap; gap: 8px; align-items: center;
  }
  /* Телефон, 390 px (замер 24.09.2026): «Под угрозой» на «Оборотах»
     выходила за край на 16 px — строка точки не переносилась; список услуг
     на «В прайсе точек» — на 317 px длинными названиями в поле выбора. */
  .outlook.risk .head { flex-wrap: wrap; }
  .outlook.risk .facts, .outlook.risk .facts > span { min-width: 0; max-width: 100%; overflow-wrap: anywhere; white-space: normal; }
  .bar-sel, .bar-sel select { max-width: 100% !important; min-width: 0; }
  .bar-sel select { width: 100%; }
}

/* Таблицы и плитки, волна 24.09.2026 (зона «tables_visual»). */

/* Плитки «Итога по сети», «По месяцам» и доски барбершопа: подпись —
   сразу под числом. Раньше плитка держала 164 px и прижимала подпись
   к низу (``margin-top: auto``), и между числом и подписью стояло
   около 70 px пустоты (замер 24.09.2026 на 1440). На «Сегодня»
   раскладка утверждена владельцем и эта поправка её не трогает. */
.tiles.snug .tile { min-height: 0; }
.tiles.snug .tile .cmp { margin-top: 0; padding-top: 4px;
}

/* ===== Отделка тем (волна 24.09.2026) — в самом конце, чтобы слой
   «как в macOS» выше ничего из этого не перебивал. ===== */

/* Список «Август»/«2026»: слой macOS рисовал системную синюю капсулу со
   стрелками (синий градиент) — в Светлой и Тёмной это чужой цвет.
   Теперь свой шеврон цветом приглушённых чернил темы (--ink-muted:
   #6e6e73 светлая, #a1a1a6 тёмная); рисунок — токен темы --sel-arrow. */

select, .bar-sel select {
  -webkit-appearance: none; appearance: none;
  background-color: var(--glass, transparent);
  background-image: var(--sel-arrow);
  background-repeat: no-repeat; background-size: 10px 6px;
  background-position: right 11px center;
  padding-right: 30px;
}

/* Пустая пилюля «—» — не значение, а отсутствие значения (правило 4):
   серая капсула читалась как «ноль без изменений». Без фона и рамки. */
.chip.empty, .chip.flat.empty {
  background: none; border-color: transparent; box-shadow: none;
  color: var(--ink-muted); font-weight: 400;
}

/* «Сегодня», ряд из шести плиток: «2 637 331 ₽» при 28 px занимало
   165 px из 178 px плитки — до правого края оставалось 4 px. Кегль
   теперь общий для всего ряда и зависит от ширины окна; знак ₽ мельче. */
body .six .tile .value { font-size: clamp(20px, 1.55vw, 26px); white-space: nowrap; }
body .six .tile .value .cur, body .six .tile .value small { font-size: .7em; font-weight: 500; }

/* «Ждём от вас», значок «!»: токена --warning в теме нет, и запасной
   #8a5a00 на тёмном листе смешивался в оливковый. Янтарь — свой токен
   для каждой темы, подложка 18 %. */

body .act-ic { color: var(--amber); background: color-mix(in srgb, var(--amber) 18%, transparent); }

/* Свёртки «Все выгрузки в Excel» и «Откуда цифры»: был голый системный
   треугольник ▶. Теперь шеврон «›», как на плитках, и он поворачивается
   вниз при раскрытии. */
details.exports > summary, details.sources > summary {
  list-style: none; cursor: pointer; display: flex; align-items: center; gap: 8px;
}
details.exports > summary::-webkit-details-marker,
details.sources > summary::-webkit-details-marker { display: none; }
details.exports > summary::before, details.sources > summary::before {
  content: "›"; display: inline-block; width: 10px; text-align: center;
  color: var(--ink-muted); font-size: 1.25em; line-height: 1;
  transition: transform .15s ease;
}
details.exports[open] > summary::before, details.sources[open] > summary::before { transform: rotate(90deg); }

/* Узкие экраны, вторая волна (критик 24.09.2026, замер на 900 и 500 px). */

/* Мини-график в плитке «Оборотов»: последняя точка стоит в x = 100 из
   100 и на 900 px рисовалась на 903–912 px — за правым краем плитки,
   у которой overflow: hidden. Справа оставлено 8 px поля (радиус точки
   2,2 плюс обводка), и сам SVG не режет то, что выходит за его рамку. */
.tiles .tile .tile-spark {
  overflow: visible;
  width: calc(100% + 28px); margin-right: -8px;
}

/* Меню разделов с 700 до 940 px переносилось на две строки: вкладки
   сверху, «Как устроена система · Настройки · Выйти» под ними — 80 px
   первого экрана. Теперь, как на телефоне, одна строка с прокруткой
   вбок. Выше 940 px меню — боковая колонка, там переносить нечего. */
@media (max-width: 940px) {
  nav.tabs {
    flex-wrap: nowrap; overflow-x: auto; overflow-y: hidden;
    scrollbar-width: none; -webkit-overflow-scrolling: touch;
    max-width: 100%;
  }
  nav.tabs::-webkit-scrollbar { display: none; }
  nav.tabs .row { flex-wrap: nowrap; flex: 0 0 auto; }
  nav.tabs .navfind { display: none; }
  nav.tabs a, nav.tabs .theme-switch { white-space: nowrap; flex: 0 0 auto; }
}

/* «Сегодня», повестка: длинное имя переносится на вторую строку, а не
   режется. «FIDEL Волгоградский пр-т ЖК "Метрополия"» в «Просели»
   читалось как «…ЖК "Мет…"» — непонятно, какая точка. Больше двух строк
   не даём: ряд повестки не должен расти ступенькой. */
.agenda li .who.who {
  white-space: normal; text-overflow: clip;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  overflow: hidden; line-height: 1.3;
}

/* «Готовый ответ — скопировать»: треугольник после текста в мелком
   кегле рисовался точкой — «скопировать ·» читалось как хвостовая
   точка. Знак раскрытия — шевроном перед подписью, как у свёрток. */
details.how.copy-answer > summary::after { content: none; }
details.how.copy-answer > summary::before {
  content: "›"; display: inline-block; font-size: var(--t-head-size); line-height: 1;
  transition: transform .15s ease;
}
details.how.copy-answer[open] > summary::before { transform: rotate(90deg); }

/* «Учёт»: подпись плитки — одной строкой, полный текст в подсказке. */
.tiles.one-line .tile .cmp,
.tiles.one-line .tile .cmp .nowrap {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.tiles.one-line .tile { min-height: 0; }

/* Заливка отклонений сильнее полосы через строку. Полоса длинных
   таблиц (``.card table:has(tbody tr:nth-child(21)) … tr:nth-child(even)
   td``) по весу селектора перебивала ``.dn1``–``.dn3``, и в «Учёте»
   долг 2 000 ₽ на нечётной строке был закрашен, а 10 500 ₽ и даже
   104 128 ₽ на чётной — нет (замер 24.09.2026). Правило заливки одно:
   от суммы, а не от чётности строки. Классы удвоены ради веса. */
.card table tbody tr td.up2.up2.up2 { background: color-mix(in srgb, var(--good) 14%, transparent); }
.card table tbody tr td.up3.up3.up3 { background: color-mix(in srgb, var(--good) 26%, transparent); }
.card table tbody tr td.dn2.dn2.dn2 { background: color-mix(in srgb, var(--critical) 14%, transparent); }
.card table tbody tr td.dn3.dn3.dn3 { background: color-mix(in srgb, var(--critical) 26%, transparent); }
.card table.debts tbody tr td.dn1.dn1.dn1 { background: color-mix(in srgb, var(--critical) 7%, transparent); }

/* Лист «Товары» (критик 24.09.2026). Плитки до 1000 px: пять в ряд на
   900 px давали 142 px на плитку, и «8 219 515 ₽» не помещалось. */
@media (max-width: 1000px) {
  .gb-tiles, .tiles.kpi.gm-tiles { grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)) !important; }
}
/* Колонка «Регламент» — по содержимому. */
#brands th.gb-regcol, #brands td:nth-child(2) { width: 1%; white-space: nowrap; }
/* Ручной ввод на «По точкам»: 28 px, число по правому краю. */
.card table input.gm-input, input.gm-input { height: 28px; padding: 2px 8px; min-height: 0; box-sizing: border-box; width: 100%; text-align: right; font-variant-numeric: tabular-nums; }
.gm-hand { margin-left: 6px; font-size: var(--t-small-size); color: var(--ink-muted); white-space: nowrap; }
a.gm-sort { color: inherit; text-decoration: none; white-space: nowrap; }
a.gm-sort:hover { text-decoration: underline; text-underline-offset: 3px; }

/* Лист «Услуги», правка вида 24.09.2026 (критик) */
/* Плитки одной ширины и одного кегля; подпись переносится, а не режется
   многоточием («Моделирование бороды/бр…»). До 1000 px — сетка 2×2. */
.tiles.kpi.svc-tiles { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.tiles.kpi.svc-tiles .tile .value { font-size: var(--t-display-long); }
.tiles.kpi.svc-tiles .tile .name { white-space: normal; overflow: visible; text-overflow: clip;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; min-height: 2.6em; }
@media (max-width: 1000px) { .tiles.kpi.svc-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; } }
/* Столбцы таблиц групп — на одной вертикали. Ширина по содержимому
   (общее правило) давала каждой таблице свою сетку: «Оказано» стояло на
   552, 562 и 569 пикселях. Числовым столбцам — одна ширина по самому
   длинному заголовку, остаток забирает название. */
table.services.wide:not(.editing) th.num, table.services.wide:not(.editing) td.num { width: 132px; }
table.services.wide:not(.editing) th.num:last-child, table.services.wide:not(.editing) td.num:last-child { width: 170px; }
table.services td.svc-name { white-space: normal; overflow-wrap: anywhere; }
/* «+N» и «Барбершопов» — кнопки, но выглядят как пометка и число. */
button.tag.svc-open, button.linkish.svc-open {
  background: none; border: 0; padding: 0; min-height: 0; height: auto; font: inherit;
  cursor: pointer; text-decoration: underline dotted; text-underline-offset: 3px; }
button.tag.svc-open { font-size: var(--t-micro-size); color: var(--ink-muted); }
button.linkish.svc-open { color: inherit; font-variant-numeric: tabular-nums; }
tr.svc-more td { white-space: normal; font-size: var(--f-note); color: var(--ink-secondary);
  background: color-mix(in srgb, var(--ink) 3%, transparent); line-height: 1.6; }
tr.svc-more b { color: var(--ink); font-weight: 600; }
/* Тренды — свёрток после групп, с левым краем таблиц. */
.charts-fold.svc-charts { margin: 6px 0 14px; padding-left: 0; }
.charts-fold .head .chip { white-space: nowrap; }
/* «Прочее» — подвал под последней группой, а не отдельная карточка. */
details.minor-groups.minor-foot { padding: 0; }
/* При 900 px таблица обрезала «Вариантов названия» за краем карточки:
   теперь карточка возит таблицу вбок, а числа держатся по содержимому. */
@media (max-width: 1000px) {
  .card.scroll:has(> table.services.wide) { overflow-x: auto; }
  table.services.wide:not(.editing) th.num, table.services.wide:not(.editing) td.num { width: 1%; }
  table.services.wide:not(.editing) thead th.num { white-space: normal !important; min-width: 64px; }
}
/* На 900 px первая плитка вставала одна во всю ширину (общее правило
   .tiles.kpi > .tile:first-child) — сетка выходила 1+2+1. Здесь 2×2. */
@media (max-width: 1000px) { .tiles.kpi.svc-tiles > .tile:first-child { grid-column: auto; } }
.tiles.kpi.svc-tiles > .tile:first-child .value { font-size: var(--t-display-long); }
/* Тёмная тема: серые пометки читались хуже нормы. */
:root[data-theme="dark"] table.services .tag, :root[data-theme="dark"] tr.tail-row .tail-note,
:root[data-theme="dark"] tr.tail-row a { color: var(--ink-secondary); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) table.services .tag, :root:not([data-theme="light"]) tr.tail-row .tail-note,
  :root:not([data-theme="light"]) tr.tail-row a { color: var(--ink-secondary); }
}
/* Два сравнения в подписи мини-графика не влезали в одну строку с долей
   и налезали друг на друга: 420 px текста в карточке шириной 290. */
.svc-charts .metric-card .head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; height: auto; white-space: normal; }
.svc-charts .metric-card .head .chip { position: static; transform: none; font-size: var(--f-cap); }
.svc-charts .metric-card .head > span:last-child { flex-basis: 100%; }

/* Выравнивание отступов 24.09.2026 (владелец: «отступ должен быть одинаковый везде»).
   Доски «Первые десять барберов» — ряд плиток, но не .tiles: было поле 24, таблица в 30 px
   при 15 у плиток на том же листе. Стало поле плитки 18/14/16. */
section.boards > .tile.card { padding: 18px 14px 16px; }
/* Свёрнутые доски «Лидеров» и сверка в «Товарах»: таблица с полями 24 в карточке с полями 24 —
   первый столбец уезжал в 43 px от заголовка в 25. Внутри карточки у таблицы крайние поля 0. */
details.board-fold .board th:first-child, details.board-fold .board td:first-child,
#denis .scroll th:first-child, #denis .scroll td:first-child { padding-left: 0; }
details.board-fold .board th:last-child, details.board-fold .board td:last-child,
#denis .scroll th:last-child, #denis .scroll td:last-child { padding-right: 0; }
#denis .scroll caption { padding-left: 0; padding-right: 0; }
/* На узком экране плитка и карточка — одно поле 14 (было 12 против 14). */
@media (max-width: 1260px) { body .tiles.kpi .tile { padding-left: 14px; padding-right: 14px; } }
/* «Настройки»: таблицы в свёртках и в .settings-table — крайние 24, как везде (было 8). */
.settings-table table.fit th:first-child, .settings-table table.fit td:first-child,
details.how table.fit th:first-child, details.how table.fit td:first-child { padding-left: 24px; }
.settings-table table.fit th:last-child, .settings-table table.fit td:last-child,
details.how table.fit th:last-child, details.how table.fit td:last-child { padding-right: 24px; }

/* Перепроверка 24.09.2026 в раскрытом виде. Раскрытая доска «Лидеров»
   держала три разные левые линии: заголовок свёртки в 43 px (24 поля
   карточки + 18 своих), подпись таблицы в 49, «Место» в 25, а номер места
   по центру столбца в ~45. Теперь всё на поле карточки, 25 px. */
details.board-fold > summary { padding-left: 0; padding-right: 0; }
details.board-fold .board caption { padding-left: 0; padding-right: 0; }
details.board-fold .board td.place, details.board-fold .board th.place { text-align: left; }
/* Строка сравнения в плитке главного ряда: «↓ 14,9 % к августу 2025»
   не переносилась и упиралась в правую кромку плитки шириной 167 px
   (текст до края 0 px при поле 15 слева). Разрешён перенос между
   процентом и «к августу 2025»; сама подпись «к августу 2025» цела. */
.tiles .tile .cmp .cmp-row > span { white-space: normal; }
/* Таблица разбора «из-за чего» (_why.html) на «Сводке» и «Клиентах»:
   стояла в карточке с полями 24 и сама держала поля 24 — «Визиты» в 49 px
   при названии точки над ней в 25, вложенные «↳» в 45 и 63, число справа
   в 50 px от кромки при ссылке «К разговору» в 25 (замер 24.09.2026).
   Теперь крайние столбцы на поле карточки, вложенность — шагом 16 px. */
.card .why-table th:first-child, .card .why-table td:first-child { padding-left: 0; }
.card .why-table th:last-child, .card .why-table td:last-child { padding-right: 0; }
.card .why-table tr.why-line td { padding-left: 0; padding-right: 0; }
.card .why-table .deeper .what { padding-left: 16px; }
.card .why-table .deeper-2 .what { padding-left: 32px; }
/* «Динамика», таблица годов под графиком: столбец «Год» стоял в 49 px от
   кромки при тексте карточки над ним в 25 — таблица с полями 24 внутри
   карточки с полями 24 (замер 24.09.2026). */
.card.years .years-legend th:first-child, .card.years .years-legend td:first-child { padding-left: 0; }
.card.years .years-legend th:last-child, .card.years .years-legend td:last-child { padding-right: 0; }

/* «Клиенты → Где именно теряем гостей»: подпись «изменение» стояла по
   левому краю столбца (1 062 px), а проценты под ней — по правому
   (1 231 px): подпись висела в 170 px от своих чисел. */
.loss-head span:nth-child(5) { text-align: right; }
/* Доски «Лидеров»: месяц в шапке стоял по центру столбца (июн — 438–463 px),
   а суммы под ним — по правому краю (555 px). Подпись над числом — по его краю. */
.board thead th.num { text-align: right; }

/* Один край на весь лист (владелец 24.09.2026)
   «Отступ должен быть одинаковый везде… не может быть один прилеплен, а
   второй сильно к боку». Замер на «Товарах», 1280 px: текст плиток стоял
   в 15 px от кромки, вывода — в 17, карточек и таблиц — в 24; число
   «7 893 117» начиналось на 9 px левее «50 брендов за август 2026» под
   ним. Теперь край один — 20 px — у плиток, вывода, карточек, подписей
   таблиц и крайних ячеек. Правило в самом конце и с !important: у листов
   свои слои с id-селекторами, и любой из них иначе вернул бы свой край. */
/* Остальные !important ниже оставлены сознательно (проверено 05.10.2026
   снятием по одному и снимками 25 листов): без них поля карточек, таблиц
   и повестки «Сегодня», отступ свёрток «Услуг» и ширина первого столбца
   «Загрузки» меняются — их перебивают правила той же или большей
   специфичности выше по файлу (в том числе в @media). У «Горит» перенос
   держится на !important потому, что «.pair .outlook.risk .facts > span»
   специфичнее и ставит nowrap. */
:root { --edge: 20px; }
.tiles .tile.card, .tiles.kpi .tile, .verdict,
.card:not(.scroll):not(.tile) { padding-left: var(--edge) !important; padding-right: var(--edge) !important; }
.card > table > caption, .card > .scroll > table > caption,
.card.scroll > table > caption { padding-left: var(--edge) !important; padding-right: var(--edge) !important; }
.card.scroll > table > * > tr > :first-child,
.card > table > * > tr > :first-child { padding-left: var(--edge) !important; }
.card.scroll > table > * > tr > :last-child,
.card > table > * > tr > :last-child { padding-right: var(--edge) !important; }
/* Карточка с полями, внутри которой таблица: край задала карточка, у
   ячеек второго поля быть не должно — иначе текст на 40 px. */
.card:not(.scroll) > table > * > tr > :first-child { padding-left: 0 !important; }
.card:not(.scroll) > table > * > tr > :last-child { padding-right: 0 !important; }
.card:not(.scroll) > table > caption { padding-left: 0 !important; padding-right: 0 !important; }
/* Таблица в обёртке прокрутки внутри карточки с полями — тот же случай:
   «Сколько нам должны» на «Сегодня» стояла в 44 px при заголовке в 21. */
.card:not(.scroll) > .scroll > table > * > tr > :first-child { padding-left: 0; }
.card:not(.scroll) > .scroll > table > * > tr > :last-child { padding-right: 0; }
/* Повестка «Сегодня»: поля задают строки, а не карточка — иначе 20 + 24. */
.agenda > .card.card:not(.scroll):not(.tile) { padding-left: 0 !important; padding-right: 0 !important; }
.agenda .head, .agenda li { padding-left: var(--edge) !important; padding-right: var(--edge) !important; }
.agenda .more.foot { padding-left: var(--edge) !important; padding-right: var(--edge) !important; }
/* Доски «Барберов» — плитки, но со своим правилом полей (14 px). */
section.boards > .tile.card.card { padding-left: var(--edge); padding-right: var(--edge); }
/* «Услуги»: столбцы чисел — по ширине своей подписи, а не по 132 px.
   Было 5 × 132 + 170 = 830 px на числа: «Вариантов названия» упирался в
   край карточки, а «Моделирование бороды / бритьё — для новых гостей»
   ломалось надвое при пустом месте справа (владелец 24.09.2026:
   «компоновка просто жесть»). Стало 88+112+118+124+106+146 = 694 px,
   остальное — названию. */
table.services.wide:not(.editing) { table-layout: fixed; width: 100%; }
table.services.wide:not(.editing) th, table.services.wide:not(.editing) thead th { white-space: nowrap; }
table.services.wide:not(.editing) > * > tr > :nth-child(2) { width: 88px; }
table.services.wide:not(.editing) > * > tr > :nth-child(3) { width: 112px; }
table.services.wide:not(.editing) > * > tr > :nth-child(4) { width: 118px; }
table.services.wide:not(.editing) > * > tr > :nth-child(5) { width: 124px; }
table.services.wide:not(.editing) > * > tr > :nth-child(6) { width: 106px; }
table.services.wide:not(.editing) > * > tr > :nth-child(7), table.services.wide:not(.editing) th.num:last-child, table.services.wide:not(.editing) td.num:last-child { width: 146px; }
/* «Услуги», свёртки «Прочее» и «Тренды»: висели голыми строками с
   системным треугольником, на 55 и 60 px — между таблицами-карточками
   выше и «Откуда цифры» ниже. Теперь заголовок свёртки выглядит как
   «Откуда цифры»: белая полоса, шеврон «›», тот же край 20 px. */
details.minor-groups > summary, details.svc-charts > summary {
  list-style: none; cursor: pointer; display: flex; align-items: baseline; gap: 8px;
  padding: 16px var(--edge); margin: 0 0 14px; border-radius: 16px;
  background: var(--surface-solid); box-shadow: var(--mac-card-shadow, 0 1px 2px rgba(0,0,0,.06));
  font-size: var(--t-body-size, 14px); color: var(--ink-secondary);
}
details.minor-groups > summary::-webkit-details-marker,
details.svc-charts > summary::-webkit-details-marker { display: none; }
details.minor-groups > summary::before, details.svc-charts > summary::before {
  content: "›"; display: inline-block; width: 10px; text-align: center; flex: none;
  color: var(--ink-muted); font-size: 1.25em; line-height: 1; transition: transform .15s ease;
}
details.minor-groups[open] > summary::before, details.svc-charts[open] > summary::before { transform: rotate(90deg); }
details.svc-charts > summary { color: var(--ink); font-weight: 600; }
/* «Список поправлен вручную» — строкой на том же краю, что текст карточек. */
.edit-bar + p.note, .edit-bar ~ p.note { padding-left: var(--edge); padding-right: var(--edge); }
details.svc-charts, details.minor-groups.minor-foot { margin-top: 0 !important; margin-bottom: 0 !important; }
/* Широкие таблицы на 1280 px (замер 24.09.2026): «Учёт» 1049 из 990 —
   пометка «на отключение · долг 2 000 ₽» под названием не переносилась
   и держала столбец в 296 px; «Барберы» 1092 из 990 — «ПРЕМИУМ по чеку»
   в строку и длинные названия точек. Переносим пометки, названиям — предел. */
table.debts td .under .chip { white-space: normal; }
table.debts > tbody > tr > td:nth-child(2) { max-width: 230px; white-space: normal; }
table.staff td.rank { white-space: normal; max-width: 120px; }
table.staff td.rank .guess { display: block; }
table.staff td.one-line { max-width: 160px; overflow: hidden; text-overflow: ellipsis; }
/* «Долги»: название с пометкой «на отключение» в строку держало столбец
   в 399 px; «Загрузка»: заголовки «Гостей на кресло», «Выручка за час»
   в строку — таблица 1102 из 990 px. Пометку — под название, заголовки
   чисел — в две строки. */
table.debts td.one-line:first-child { max-width: 240px; white-space: normal; }
table.debts td.one-line:first-child .reason { display: block; width: max-content; margin: 6px 0 0; }
table.load thead th.num, table.load thead th { white-space: normal; line-height: 1.25; }
table.load > * > tr > :first-child { max-width: 200px !important; }
/* «Горит» на «Оборотах»: строка точки теперь несёт все причины и
   владельца — в одну строку она уходила за край карточки (24.09.2026).
   Переносится; свёртки — шевроном, как «Откуда цифры». */
.outlook.risk .facts { gap: 8px; min-width: 0; }
.outlook.risk .facts > span, .outlook.risk .facts > span * { white-space: normal !important; overflow-wrap: anywhere; line-height: 1.4; text-overflow: clip !important; }
.outlook.risk .facts > span { overflow: visible !important; max-width: 100%; }
.outlook.risk details.all > summary { list-style: none; cursor: pointer; }
.outlook.risk details.all > summary::-webkit-details-marker { display: none; }
.outlook.risk details.all > summary::before { content: "›"; display: inline-block; width: 12px; color: var(--ink-muted); transition: transform .15s; }
.outlook.risk details.all[open] > summary::before { transform: rotate(90deg); }

/* «Горит» на «Оборотах» — таблица, а не абзацы (владелица 28.09.2026).
   Красным только сильный признак (вес 3 и больше), слабый — обычным
   цветом: правило 5, цвет только на отклонениях. */
/* На телефоне восемь столбцов шире карточки (836 px против 474) — таблица листается вбок внутри карточки, а не режется. */
.risk-card { padding-top: 16px; overflow-x: auto; }
.risk-head { display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 10px; padding: 0 0 10px; }
.risk-title { font-size: var(--t-head-size); font-weight: 600; }
.risk-sum { font-size: var(--t-title-size); font-weight: 600; color: var(--critical); }
.risk-of { color: var(--ink-muted); }
.risk-hint { color: var(--ink-muted); font-size: var(--t-small-size); }
.risk-table td.risk-bad { color: var(--critical); font-weight: 600; }
.risk-table td.risk-mild { color: var(--ink-secondary); }
.risk-table .risk-city, .risk-table .risk-who { color: var(--ink-muted); font-size: var(--t-small-size); }
.risk-table .risk-who a { color: inherit; }
.risk-card details.all { margin-top: 6px; }
.risk-card details.all > summary { cursor: pointer; font-weight: 600; padding: 8px 0; list-style: none; }
.risk-card details.all > summary::-webkit-details-marker { display: none; }
.risk-card details.all > summary::before { content: "›"; display: inline-block; width: .8em; color: var(--ink-muted); transition: transform .15s; }
.risk-card details.all[open] > summary::before { transform: rotate(90deg); }
/* Четыре сегмента — четыре равных столбца: в строку с переносом
   «Зарубежные» уезжали на вторую строку (замер 28.09.2026, 1 280 px). */
.seg-strip-title { display: block; font-weight: 600; margin-bottom: 10px; }
/* Столбцы по ширине: четыре на компьютере, два на телефоне. Правило «два
   столбца» из блока узкого экрана стояло выше в файле и проигрывало — на
   390 px суммы четырёх сегментов наезжали друг на друга (05.10.2026). */
.seg-strip-items { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 8px 24px; }
.seg-item { display: flex; flex-direction: column; gap: 2px; }
.seg-item .seg-name { color: var(--ink-muted); font-size: var(--t-small-size); }
.seg-item b { font-size: var(--t-head-size); font-weight: 600; }
.seg-item .seg-share { color: var(--ink-muted); }
