/* Извлечено из admin.html <style> (шаг 9 плана Астры, 11.09.2026) — базовые
   элементы и все стили компонентов. Подключается ПОСЛЕ tokens.css. */

  * { box-sizing: border-box; }
  html {
    scrollbar-gutter: stable;
  }
  body {
    margin: 0;
    font-family: var(--font-body);
    background: var(--paper);
    color: var(--ink);
    -webkit-font-smoothing: antialiased;
  }
  h1, h2, h3, .hero-value, .stat-value { font-family: var(--font-heading); }
  .tnum { font-variant-numeric: tabular-nums; }
  :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--r-xs); }
  @media (prefers-reduced-motion: no-preference) {
    .card, .stat-tile, .nav-item, button { transition: transform 160ms ease, box-shadow 160ms ease, background 160ms ease; }
  }

  /* ---------- каркас: сайдбар + основная область ---------- */
  .shell { display: flex; align-items: stretch; min-height: 100vh; }

  .sidebar {
    width: 232px; flex-shrink: 0; background: var(--dark-bg); color: var(--dark-text);
    padding: 18px 12px; display: flex; flex-direction: column; gap: var(--sp-4);
    /* Раньше сайдбар был обычным flex-элементом без своей высоты — .shell
       растягивал его (align-items:stretch) до высоты ГЛАВНОЙ колонки, а
       она на длинных страницах (например, «Сотрудники» с многими
       строками) намного выше экрана. Из-за этого низ сайдбара (имя
       пользователя, «Сменить пароль», «Выйти») уезжал далеко вниз вместе
       со всем сайдбаром и был виден только после прокрутки всей страницы
       до конца. Теперь сайдбар "прилипает" к вьюпорту и не растягивается
       выше него — низ сайдбара всегда виден, скроллится только основная
       область. 30.08.2026, по запросу пользователя. */
    position: sticky; top: 0; align-self: flex-start;
    height: 100vh; overflow-y: auto; overflow-x: hidden;
  }
  .nav-brand { display: flex; align-items: center; gap: var(--sp-2); padding: 2px 8px 4px; }
  .nav-brand img { height: 39px; border-radius: var(--r-xs); }
  .nav-brand-mark {
    width: 42px; height: 42px; border-radius: var(--r-sm); flex-shrink: 0;
    background: var(--accent); color: var(--accent-ink); font-weight: var(--fw-bold); font-size: var(--fs-lg);
    display: flex; align-items: center; justify-content: center;
  }
  /* Название клиники: если не помещается в одну строку, переносится на
     вторую, а не обрезается многоточием — так видно название целиком. */
  .nav-brand-name { font-size: var(--fs-base); font-weight: var(--fw-bold); letter-spacing: 0.01em; white-space: normal; overflow-wrap: break-word; line-height: 1.25; }

  /* Заглушка бренда, пока не пришёл ответ /clinic/settings (v39) — вместо
     мгновенного показа неверного названия/эмодзи (которые через долю
     секунды сами перескакивали на настоящий бренд клиники, создавая
     заметное мигание вкладки и сайдбара при каждой загрузке/перезагрузке),
     показываем нейтральный знак Anexis с "бегущим" пульсом — а когда данные
     приходят, обе части (значок и текст) мягко перетекают в бренд клиники.
     См. loadHeaderBranding(). */
  .nav-brand-mark.brand-loading {
    background: #0e1a19; color: #57b3a6;
  }
  .nav-brand-mark.brand-loading svg { width: 26px; height: 26px; display: block; }
  .brand-pulse-ring { fill: none; stroke: rgba(244,246,245,0.22); stroke-width: 2.2; }
  .brand-pulse-trace { fill: none; stroke: rgba(87,179,166,0.32); stroke-width: 5.5; stroke-linecap: round; stroke-linejoin: round; }
  .brand-pulse-comet {
    fill: none; stroke: #57b3a6; stroke-width: 5.5; stroke-linecap: round; stroke-linejoin: round;
    stroke-dasharray: 24 84.2238;
    animation: brand-pulse-chase 1.6s linear infinite;
  }
  @keyframes brand-pulse-chase { to { stroke-dashoffset: -108.2238; } }
  @media (prefers-reduced-motion: reduce) {
    .brand-pulse-comet { animation: none; stroke-dasharray: none; }
  }
  .nav-brand-mark, .nav-brand-name { transition: opacity 220ms ease; }
  .nav-brand-name .nav-brand-sub {
    display: block; font-size: var(--fs-micro); font-weight: var(--fw-medium); letter-spacing: 0.08em; text-transform: uppercase;
    opacity: 0.55; margin-top: 1px;
  }

  .tabs { display: flex; flex-direction: column; gap: var(--sp-1); }
  .tab-btn {
    display: flex; align-items: center; gap: var(--sp-3); text-align: left;
    padding: 10px 12px; border-radius: var(--r-sm); border: 1px solid transparent; background: transparent;
    color: var(--dark-text-soft); font-size: var(--fs-md); font-weight: var(--fw-normal); font-family: inherit; cursor: pointer;
  }
  /* Обводка/полоска по мотивам референса пользователя (сайдбар v2, 10.10.2026):
     раньше активный раздел был сплошной заливкой акцентом; теперь — мягкая
     полупрозрачная обводка (из тона самого текста меню, не из акцента) и
     тонкая 3px-полоска слева у активного пункта, как у записей в календаре. */
  .tab-btn:hover {
    background: color-mix(in srgb, var(--dark-text) 6%, transparent);
    border-color: color-mix(in srgb, var(--dark-text) 14%, transparent);
    color: var(--dark-text);
  }
  .tab-btn.active {
    background: color-mix(in srgb, var(--dark-text) 11%, transparent);
    border-color: color-mix(in srgb, var(--dark-text) 16%, transparent);
    color: var(--dark-text); font-weight: var(--fw-medium);
    box-shadow: inset 3px 0 0 var(--accent);
  }
  .tab-icon { width: 18px; height: 18px; flex-shrink: 0; }

/* Курс USD в сайдбаре — видим только у клиник с включённой оплатой в
   валюте (см. loadSidebarUsdRate() в shell.js). 10.10.2026, по запросу
   пользователя: показывать без доп. слов (без "ЦБ"/даты в строке), дата
   обновления курса — только в подсказке по наведению. */
.sidebar-usd-rate {
  position: relative; display: flex; align-items: center; gap: var(--sp-2);
  padding: 8px 10px; border-radius: var(--r-sm); max-width: 100%; box-sizing: border-box;
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  font-size: var(--fs-base); color: var(--dark-text); cursor: pointer;
}
/* Мини-калькулятор сум/$ — всплывает над виджетом курса (11.10.2026). */
.usd-calc-popover {
  position: absolute; bottom: calc(100% + 8px); left: 0; right: 0;
  background: color-mix(in srgb, var(--dark-bg) 60%, black);
  border: 1px solid color-mix(in srgb, var(--dark-text) 20%, transparent);
  border-radius: var(--r-md); padding: 14px; box-shadow: 0 10px 28px rgba(0,0,0,.35);
  opacity: 0; transform: translateY(4px); pointer-events: none;
  transition: opacity .14s ease, transform .14s ease; z-index: 20; cursor: default;
}
.usd-calc-popover.open { opacity: 1; transform: translateY(0); pointer-events: auto; }
.usd-calc-row { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.usd-calc-row:last-child { margin-bottom: 0; }
.usd-calc-row input {
  flex: 1; min-width: 0; background: transparent; border: none;
  border-bottom: 1px solid color-mix(in srgb, var(--dark-text) 25%, transparent);
  color: var(--dark-text); font-size: var(--fs-md); font-variant-numeric: tabular-nums;
  padding: 4px 2px; font-family: inherit;
}
.usd-calc-row input:focus { outline: none; border-bottom-color: var(--accent); }
.usd-calc-row .usd-calc-unit { font-size: var(--fs-caption); color: var(--dark-text); opacity: .6; flex-shrink: 0; width: 34px; }


.sidebar-usd-rate-icon {
  width: 19px; height: 19px; border-radius: 50%; flex-shrink: 0;
  background: var(--accent); color: var(--accent-ink);
  display: flex; align-items: center; justify-content: center;
  font-size: 10px; font-weight: var(--fw-bold);
}
.sidebar-usd-rate-label { font-weight: var(--fw-medium); }
.sidebar-usd-rate-value { margin-left: auto; font-weight: 400; font-variant-numeric: tabular-nums; }
.sidebar-usd-rate-tip {
  position: absolute; bottom: calc(100% + 6px); left: 50%; transform: translateX(-50%);
  width: max-content; max-width: 200px; white-space: normal; text-align: center;
  background: color-mix(in srgb, var(--dark-bg) 55%, black);
  color: var(--dark-text); font-size: 10.5px; font-weight: 400; line-height: 1.3;
  padding: 5px 8px; border-radius: var(--r-xs); box-shadow: 0 4px 12px rgba(0,0,0,.3);
  opacity: 0; pointer-events: none; transition: opacity .12s ease; z-index: 5;
}
.sidebar-usd-rate-tip::after {
  content: ""; position: absolute; top: 100%; left: 50%; transform: translateX(-50%);
  border: 5px solid transparent; border-top-color: color-mix(in srgb, var(--dark-bg) 55%, black);
}
.sidebar-usd-rate:hover .sidebar-usd-rate-tip { opacity: 1; }

  .sidebar-foot { margin-top: auto; display: flex; flex-direction: column; gap: var(--sp-2); }
  /* Строка "сервер: ..." скрыта по просьбе пользователя (04.09.2026) — сам
     элемент и обновляющий его JS (updateConnStatus()) оставлены как есть,
     чтобы не трогать логику; просто не показываем в сайдбаре. */
  .sidebar-foot .status { display: none; }
  .sidebar-foot button { width: 100%; display: flex; align-items: center; justify-content: center; gap: var(--sp-2); }
  /* Кнопка "Выйти" использует класс button.secondary (светлый фон для
     карточек на белой подложке) — на тёмном сайдбаре это выглядело
     белым пятном. Переопределяем в тон сайдбара (10.10.2026). */
  .sidebar-foot button.secondary {
    background: transparent; color: var(--dark-text);
    border: 1px solid color-mix(in srgb, var(--dark-text) 22%, transparent);
  }
  .sidebar-foot button.secondary:hover {
    background: color-mix(in srgb, var(--dark-text) 10%, transparent);
  }
  /* Мелкая подпись платформы внизу сайдбара — тот же приём, что и в подвале
     печатных документов («Powered by Anexis»), 04.09.2026. Цвета жёстко
     заданы (не через var(--dark-text-soft) и т.п.), чтобы знак Anexis не
     перекрашивался вместе с темой клиники — это подпись платформы, а не
     элемент бренда клиники. */
  .powered-by-anexis {
    display: flex; align-items: flex-end; gap: var(--sp-2); padding: 0 8px;
    font-size: 10px; font-weight: var(--fw-medium); letter-spacing: 0.02em;
    color: rgba(238,243,242,0.75);
  }
  .powered-by-anexis svg { width: 22px; height: 22px; flex-shrink: 0; }
  .powered-by-anexis span { white-space: nowrap; }
  .powered-by-anexis .anexis-wordmark {
    font-family: 'Barlow Condensed', 'Barlow', sans-serif;
    font-size: 24px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.17em;
    line-height: 1;
  }

  .main { flex: 1; min-width: 0; }

  /* 24.09.2026 (живая проверка после приёмки Астры — пользователь сообщил
     «на телефоне не работает вовсе»). Причина: .shell — обычный flex-ряд,
     .sidebar — фиксированные 232px, flex-shrink:0, и НИГДЕ в файле не было
     ни одного media-запроса, который бы на узком экране сворачивал сайдбар
     — .cal-layout/.cal-side (выше) и внутренние .ui-table-scroll-обёртки
     уже умели адаптироваться, но сам каркас приложения — нет. На реальном
     телефоне это означало: сайдбар всегда съедает часть и без того узкого
     экрана, а сама страница получает горизонтальный скролл целиком.
     Решение — стандартный паттерн «бургер + выезжающая панель», только
     под этим брейкпоинтом (760px — тот же, что уже используют
     .cal-layout/patient-page.css), десктоп не тронут вообще. */
  .mobile-topbar { display: none; }
  .mobile-nav-overlay { display: none; }
  @media (max-width: 760px) {
    .mobile-topbar {
      display: flex; align-items: center; gap: var(--sp-3);
      position: sticky; top: 0; z-index: 210;
      background: var(--dark-bg); color: var(--dark-text);
      padding: 12px 14px;
    }
    .mobile-nav-toggle {
      background: transparent; border: 1px solid color-mix(in srgb, var(--dark-text) 30%, transparent);
      color: inherit; width: 40px; height: 40px; border-radius: var(--r-sm);
      display: flex; align-items: center; justify-content: center; flex: none; padding: 0;
    }
    .mobile-nav-toggle svg { width: 20px; height: 20px; }
    .mobile-topbar-title { font-size: var(--fs-md); font-weight: var(--fw-bold); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

    .shell { display: block; }
    .sidebar {
      position: fixed; top: 0; left: 0; height: 100vh; width: min(280px, 82vw);
      z-index: 230; transform: translateX(-100%); transition: transform 220ms ease;
      box-shadow: var(--shadow-lg);
    }
    .shell.mobile-nav-open .sidebar { transform: translateX(0); }
    .mobile-nav-overlay {
      position: fixed; inset: 0; z-index: 220; background: color-mix(in srgb, black 45%, transparent);
    }
    .shell.mobile-nav-open .mobile-nav-overlay { display: block; }
    .main { width: 100%; }
    /* Сама верстка внутри страниц (широкие таблицы/сетка календаря) не
       переделывается этим фиксом — только каркас (сайдбар/шапка). Если
       внутри конкретной страницы останется своя горизонтальная прокрутка
       (например, много врачей в календаре сразу) — это её собственная
       .ui-table-scroll/.cal-*-обёртка, чинится отдельно по месту. */

    /* 24.09.2026 (продолжение фикса выше — «Докрутить сетку календаря на
       телефоне»). После того как сайдбар свернули, страница всё равно
       ползла в сторону на ~490px против viewport 375px. Причина — не
       сама сетка (.day-grid уже была с overflow:auto), а то, что
       .cal-layout/.cal-main — оба display:flex; flex-direction:column
       под 980px, и без явного width:100%/min-width:0 на каждом уровне
       браузер растягивал .cal-main вслед за содержимым .day-grid
       (minmax(140px,1fr) на каждого врача не даёт сетке сжаться), а не
       наоборот — сжимал .cal-main до ширины экрана и отдавал излишек
       внутренней прокрутке .day-grid. Явно фиксируем width:100% и
       min-width:0 на каждом звене цепочки — тогда именно .day-grid (а
       не вся страница) получает свою внутреннюю горизонтальную
       прокрутку при 2+ врачах. Один врач — сетка и так укладывается,
       прокрутка внутри .day-grid не появляется. */
    .cal-layout, .cal-main, .cal-side, .day-grid {
      min-width: 0; width: 100%; max-width: 100%; box-sizing: border-box;
    }
  }
  .container { max-width: 1180px; margin: 0 auto; padding: 22px 24px 60px; }
  /* На вкладке «Клиника» слева появилась вертикальная суб-навигация
     (Сотрудники/Настройки/Способы оплаты/Журнал) — при центрированном
     .container на широких экранах между тёмным сайдбаром и этой колонкой
     оставался большой пустой промежуток (замечено пользователем,
     04.09.2026). Пока открыта «Клиника», контейнер прижимается к левому
     краю (без auto-центрирования), а сама содержательная часть справа от
     суб-навигации ограничена по ширине отдельно — так карточки не
     растягиваются на весь широкий экран. */
  .container.ct-active { max-width: none; margin: 0; }
  .container.ct-active .panel { max-width: 1512px; margin-inline: auto; min-width: 0; }

  #panel-profile {
    position: fixed; top: 0; right: 0; bottom: 0; width: 500px; max-width: 92vw;
    background: var(--paper); border-left: 1px solid var(--border);
    box-shadow: -12px 0 32px rgba(0,0,0,.18);
    transform: translateX(100%); transition: transform .22s ease;
    z-index: 240; overflow-y: auto; padding: 20px;
  }
  #panel-profile.profile-drawer-open { transform: translateX(0); display: flex; flex-direction: column; gap: var(--sp-4); }
  .profile-drawer-overlay {
    position: fixed; inset: 0; background: rgba(0,0,0,.28);
    opacity: 0; pointer-events: none; transition: opacity .22s ease; z-index: 239;
  }
  .profile-drawer-overlay.open { opacity: 1; pointer-events: auto; }
  .profile-drawer-close {
    position: absolute; top: 14px; right: 14px; width: 30px; height: 30px; border-radius: var(--r-sm);
    border: 1px solid var(--border); background: var(--card); color: var(--ink-soft);
    display: flex; align-items: center; justify-content: center; cursor: pointer; font-size: 14px; z-index: 1;
  }

  .ct-content { max-width: 980px; }

  .panel { display: none; }
  .panel.active { display: flex; flex-direction: column; gap: var(--sp-4); }

  /* Плавное появление вкладок и суб-вкладок при переключении (главное меню
     слева и суб-навигация «Клиники») — лёгкое затухание + едва заметный
     сдвиг вместо мгновенной подмены контента, 04.09.2026. Класс fade-in
     добавляется через JS (switchTab/switchClinicTab) с принудительным
     reflow, чтобы анимация запускалась заново при каждом переключении, а
     не только один раз. */
  @media (prefers-reduced-motion: no-preference) {
    .fade-in { animation: panelFadeIn 200ms ease; }
  }
  @keyframes panelFadeIn {
    from { opacity: 0; transform: translateY(4px); }
    to { opacity: 1; transform: translateY(0); }
  }

  /* ---------- тёмные карточки (сводка на дашборде) ---------- */
  .header-card, .hero-card { background: var(--dark-bg); color: var(--dark-text); border-radius: var(--r-lg); padding: 20px 22px; box-shadow: var(--shadow-lg); }
  .header-card h1 { font-size: var(--fs-lg); font-weight: var(--fw-bold); margin: 0 0 2px; }
  .header-card .sub { font-size: var(--fs-sm); color: var(--dark-text-soft); margin: 0; }

  .hero-top { display: flex; justify-content: space-between; gap: var(--sp-5); flex-wrap: wrap; }
  .hero-label { display: inline-flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-caption); font-weight: var(--fw-bold); letter-spacing: 0.05em; text-transform: uppercase; color: var(--accent); margin-bottom: 8px; }
  .hero-value { font-size: var(--fs-hero); font-weight: var(--fw-bold); letter-spacing: -0.01em; }
  .hero-controls { display: flex; flex-direction: column; align-items: flex-end; gap: var(--sp-2); }
  .hero-stats { display: flex; gap: var(--sp-6); flex-wrap: wrap; margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--dark-border); }
  .hero-stat-label { font-size: var(--fs-micro); font-weight: var(--fw-bold); letter-spacing: 0.05em; text-transform: uppercase; color: var(--dark-text-soft); margin-bottom: 3px; }
  .hero-stat-value { font-size: var(--fs-md); font-weight: var(--fw-bold); }

  .segmented { display: inline-flex; background: var(--dark-bg-soft); border-radius: var(--r-sm); padding: 3px; gap: var(--sp-1); }
  .segmented button { border: none; background: transparent; color: var(--dark-text-soft); font-size: var(--fs-sm); font-weight: var(--fw-bold); font-family: inherit; padding: 7px 12px; border-radius: var(--r-xs); cursor: pointer; }
  .segmented button.active-period { background: var(--accent); color: var(--accent-ink); }

  /* ---------- Дашборд: две зоны (09.09.2026) ----------
     Раньше весь дашборд управлялся одним переключателем периода
     (Сегодня/Неделя/Месяц/Год), из-за чего в пресете "Сегодня" (по
     умолчанию) все финансовые метрики были нулём до первой оплаты, а после
     введения помесячного оклада (08.09.2026) чистая прибыль вообще не
     считалась для пресетов короче месяца. Разделили на зону "Сегодня" (без
     переключателя — операционные метрики, ежедневный чек-лист) и зону
     "Месяц" (свой выбор месяца — денежные метрики). См. claude/tdi-clinic-
     status.md — план обсуждён и согласован с пользователем 09.09.2026. */
  /* v112 (09.09.2026) — дата в шапке дашборда была var(--fs-sm) (13px), терялась
     на странице (пользователь отметил на скриншоте, что читается с трудом).
     Подняли до var(--fs-lg) (20px) и веса 700, цвет — var(--ink), а не
     приглушённый --ink-soft: это единственная строка над всей сводкой и
     должна быть заметна сразу. */
  /* v117 (09.09.2026) — отступ под датой уменьшали дважды (18px→8px в v115),
     пользователь на скриншоте после деплоя v115 показал, что разрыв
     по-прежнему выглядит большим. Убрали margin-bottom совсем — следующий
     блок ("СЕГОДНЯ") идёт вплотную, разделяясь только естественным
     интервалом между строками текста. */
  .dz-topline { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-4); margin-bottom: 0; flex-wrap: wrap; font-size: var(--fs-lg); font-weight: var(--fw-bold); color: var(--ink); }
  .dz-eyebrow { display: flex; align-items: center; gap: var(--sp-3); margin: 26px 0 12px; }
  /* v118 (09.09.2026) — НАЙДЕНА РЕАЛЬНАЯ ПРИЧИНА большого отступа под датой
     (то, что чинили в v113/v115/v117, было не тем местом). `:first-of-type`
     сравнивает по тегу (div), а не по классу — первый <div> внутри
     #panel-dashboard это .dz-topline (дата), а не .dz-eyebrow, поэтому
     `.dz-eyebrow:first-of-type` не находил вообще ничего и margin-top:26px
     у "Сегодня" никогда не обнулялся. Правильный селектор — по соседству
     с .dz-topline, а не по общему первенству среди div. */
  .dz-topline + .dz-eyebrow { margin-top: 0; }
  .dz-eyebrow .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--accent-strong); flex-shrink: 0; }
  .dz-eyebrow h2 { font-size: var(--fs-sm); font-weight: var(--fw-bold); text-transform: uppercase; letter-spacing: 0.06em; color: var(--ink-soft); margin: 0; }
  .dz-eyebrow .dz-sub { font-size: var(--fs-sm); color: var(--ink-faint); }
  .dz-grid { display: grid; gap: var(--sp-3); }
  .dz-grid-2 { grid-template-columns: 1.3fr 1fr; }
  .dz-grid-3 { grid-template-columns: repeat(3, 1fr); }
  @media (max-width: 980px) { .dz-grid-2, .dz-grid-3 { grid-template-columns: 1fr; } }

  .dz-month-switch { display: flex; align-items: center; gap: var(--sp-2); background: var(--card); border: 1px solid var(--border); border-radius: var(--r-pill); padding: 4px 6px; margin-left: auto; }
  .dz-month-switch button { width: 24px; height: 24px; border-radius: 50%; border: none; background: transparent; color: var(--ink-soft); font-size: var(--fs-md); display: flex; align-items: center; justify-content: center; cursor: pointer; padding: 0; }
  .dz-month-switch button:hover { background: var(--paper); }
  .dz-month-switch .dz-month-label { font-family: var(--font-heading); font-weight: var(--fw-bold); font-size: var(--fs-sm); padding: 0 4px; min-width: 108px; text-align: center; }

  .dz-summary { display: flex; gap: var(--sp-5); flex-wrap: wrap; margin-bottom: 4px; }
  .dz-summary b { font-family: var(--font-heading); font-size: var(--fs-lg); font-weight: var(--fw-bold); display: block; }
  .dz-summary span { font-size: var(--fs-micro); color: var(--ink-faint); text-transform: uppercase; letter-spacing: 0.03em; font-weight: var(--fw-bold); }
  .dz-list { display: flex; flex-direction: column; margin-top: 12px; max-height: 320px; overflow-y: auto; }
  .dz-appt-row { display: grid; grid-template-columns: 50px 24px 1fr auto; align-items: center; gap: var(--sp-3); padding: 8px 6px; border-radius: var(--r-sm); cursor: pointer; border-bottom: 1px solid var(--border); }
  .dz-appt-row:last-child { border-bottom: none; }
  .dz-appt-row:hover { background: color-mix(in srgb, var(--accent) 10%, transparent); }
  .dz-appt-time { font-family: var(--font-heading); font-weight: var(--fw-bold); font-size: var(--fs-sm); color: var(--ink-soft); }
  .dz-avatar { width: 22px; height: 22px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-family: var(--font-heading); font-weight: var(--fw-bold); font-size: var(--fs-micro); flex-shrink: 0; }
  .dz-appt-patient { font-weight: var(--fw-bold); font-size: var(--fs-sm); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .dz-appt-doc { font-size: var(--fs-micro); color: var(--ink-faint); }
  .dz-status { font-size: var(--fs-micro); font-weight: var(--fw-bold); padding: 3px 8px; border-radius: var(--r-pill); white-space: nowrap; }
  .dz-st-completed { background: var(--money-good-soft); color: var(--money-good-ink); }
  .dz-st-scheduled { background: var(--money-info-soft); color: var(--money-info-ink); }
  .dz-st-cancelled, .dz-st-no_show { background: var(--money-bad-soft); color: var(--money-bad-ink); }
  .dz-empty { padding: 16px 4px; color: var(--ink-faint); font-size: var(--fs-sm); text-align: center; }

  .dz-unpaid-row { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); padding: 8px 6px; border-bottom: 1px solid var(--border); cursor: pointer; border-radius: var(--r-sm); }
  .dz-unpaid-row:hover { background: var(--money-bad-soft); }
  .dz-unpaid-row:last-child { border-bottom: none; }
  .dz-unpaid-who { font-size: var(--fs-sm); font-weight: var(--fw-bold); }
  .dz-unpaid-when { font-size: var(--fs-caption); color: var(--ink-faint); }

  .dz-hero-num { font-family: var(--font-heading); font-weight: var(--fw-bold); font-size: var(--fs-xl); }
  .dz-delta { display: inline-flex; align-items: center; gap: var(--sp-1); font-family: var(--font-heading); font-weight: var(--fw-bold); font-size: var(--fs-caption); padding: 3px 8px; border-radius: var(--r-pill); }
  .dz-delta-up { background: var(--money-good-soft); color: var(--money-good-ink); }
  .dz-delta-down { background: var(--money-bad-soft); color: var(--money-bad-ink); }
  .dz-rev-split { display: flex; gap: var(--sp-5); margin-top: 12px; flex-wrap: wrap; }
  .dz-rev-split .col .lbl { font-size: var(--fs-micro); color: var(--ink-faint); text-transform: uppercase; letter-spacing: 0.03em; font-weight: var(--fw-bold); margin-bottom: 3px; }
  .dz-rev-split .col .val { font-family: var(--font-heading); font-weight: var(--fw-bold); font-size: var(--fs-lg); }
  .dz-rev-bar { display: flex; height: 9px; border-radius: var(--r-pill); overflow: hidden; margin-top: 12px; background: var(--paper); border: 1px solid var(--border); }
  .dz-rev-bar .got { background: var(--money-good); }
  .dz-rev-bar .owed { background: var(--money-warn); }
  .dz-rev-legend { display: flex; gap: var(--sp-4); margin-top: 7px; font-size: var(--fs-micro); color: var(--ink-soft); flex-wrap: wrap; }
  .dz-rev-legend .sw { display: inline-block; width: 8px; height: 8px; border-radius: var(--r-xs); margin-right: 5px; vertical-align: -1px; }

  table.dz-debtors { width: 100%; border-collapse: collapse; margin-top: 10px; font-size: var(--fs-sm); }
  /* v111 — та же правка шапки, что и в общих table th (п.11): --ink-soft/600/0.06em. */
  table.dz-debtors th { text-align: left; font-size: var(--fs-micro); text-transform: uppercase; letter-spacing: 0.06em; color: var(--ink-soft); font-weight: var(--fw-medium); padding: 0 6px 7px; border-bottom: 1px solid var(--border); }
  table.dz-debtors td { padding: 8px 6px; border-bottom: 1px solid color-mix(in srgb, var(--border) 55%, var(--card)); }
  table.dz-debtors tr:last-child td { border-bottom: none; }
  table.dz-debtors tr.dz-rowlink { cursor: pointer; }
  table.dz-debtors tr.dz-rowlink:hover td { background: var(--paper); }
  table.dz-debtors td.amt { font-family: var(--font-heading); font-weight: var(--fw-bold); color: var(--money-bad-ink); text-align: right; white-space: nowrap; }
  table.dz-debtors td.muted { color: var(--ink-faint); font-size: var(--fs-caption); }
  table.dz-debtors td.act { text-align: right; white-space: nowrap; }
  /* ---------- Погашение долга (v108, 08.09.2026) ---------- */
  .btn-debtpay {
    border: none; background: var(--money-good-soft); color: var(--money-good-ink);
    font-family: var(--font-heading); font-weight: var(--fw-bold); font-size: var(--fs-caption);
    padding: 5px 11px; border-radius: var(--r-pill); cursor: pointer; transition: background .12s;
  }
  .btn-debtpay:hover { background: var(--money-good); color: #fff; }
  .debt-chip {
    display: inline-flex; align-items: center; gap: var(--sp-2); background: var(--money-bad-soft); color: var(--money-bad-ink);
    font-family: var(--font-heading); font-weight: var(--fw-bold); font-size: var(--fs-sm); padding: 6px 12px; border-radius: var(--r-pill); margin-bottom: 16px;
  }
  .debt-quick-full { font-size: var(--fs-caption); color: var(--accent-strong); cursor: pointer; text-decoration: underline; margin-left: auto; }
  .debt-history-row { display: flex; align-items: center; gap: var(--sp-3); padding: 9px 0; border-bottom: 1px solid var(--border); font-size: var(--fs-sm); }
  .debt-history-row:last-child { border-bottom: none; }
  .debt-history-amt { font-family: var(--font-heading); font-weight: var(--fw-bold); color: var(--money-good-ink); white-space: nowrap; }
  .debt-history-meta { color: var(--ink-faint); font-size: var(--fs-caption); flex: 1; }
  .debt-history-who { color: var(--ink-soft); font-size: var(--fs-caption); }

  .dz-month-warn { display: inline-flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-micro); font-weight: var(--fw-bold); color: var(--money-warn-ink); background: var(--money-warn-soft); padding: 4px 9px; border-radius: var(--r-pill); margin-top: 10px; }

  /* ---- Загрузка врачей сегодня (метры, 09.09.2026) ----
     Метр, а не столбик, намеренно (см. обсуждённый план): у каждого врача
     свой знаменатель (рабочие часы), столбики между разными врачами
     сравнивать некорректно — только заполненность СВОЕЙ шкалы. */
  .dz-load-row { display: grid; grid-template-columns: 24px 96px 1fr 96px; align-items: center; gap: var(--sp-3); padding: 9px 0; border-bottom: 1px solid var(--border); }
  .dz-load-row:last-child { border-bottom: none; }
  .dz-load-name { font-size: var(--fs-sm); font-weight: var(--fw-bold); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .dz-meter { height: 9px; border-radius: var(--r-pill); background: var(--paper); border: 1px solid var(--border); overflow: hidden; }
  .dz-meter-fill { height: 100%; border-radius: var(--r-pill); }
  .dz-load-free { font-size: var(--fs-caption); color: var(--ink-soft); text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
  .dz-load-off { font-size: var(--fs-caption); color: var(--ink-faint); font-style: italic; }

  /* ---------- светлые карточки ---------- */
  /* v113 — .card padding было 18px 20px (P2 п.17): асимметрия 18/20 ничего не
   даёт визуально, но глаз её ловит на стыке двух карточек. Симметрично. */
.card { background: var(--card); border: 1px solid var(--border); border-radius: var(--r-md); padding: var(--sp-5); box-shadow: var(--shadow-sm); }
  .card:hover { box-shadow: var(--shadow-md); }
  .card h2 { margin-top: 0; font-size: var(--fs-md); font-weight: var(--fw-bold); }
  .row { display: flex; gap: var(--sp-3); flex-wrap: wrap; align-items: flex-end; }
  /* v113 — .field было gap: var(--sp-1)/margin-bottom:10px (P2 п.16): подпись поля
   прижата к полю почти так же тесно (4px), как расстояние до соседнего
   поля (10px) — глаз не сразу различает, какая подпись к какому полю.
   Разнесли: gap 6px (ближе к своему полю), margin-bottom 16px (заметно
   дальше от соседнего). */
.field { display: flex; flex-direction: column; gap: var(--sp-2); margin-bottom: 16px; }
  .field label { font-size: var(--fs-caption); color: var(--ink-soft); font-weight: var(--fw-medium); }
  /* Раздел "Профиль" (04.09.2026) — личные данные только для просмотра,
     менять их может лишь администратор через "Клиника → Сотрудники", так
     что здесь просто статичный текст, а не поле ввода. */
  .profile-view-value { font-size: var(--fs-md); font-weight: var(--fw-bold); color: var(--ink); padding: 6px 0; }
  /* Сетка вместо .row (flex) — с flex все поля на широком экране сжимались
     в одну строку почти впритык друг к другу (найдено пользователем,
     04.09.2026); тут каждое поле — своя ячейка минимум 200px с понятным
     отступом между колонками. */
  .profile-info-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--sp-1) 32px; max-width: 760px; }
  /* Дни недели в "Моём графике работы" (профиль врача, 04.09.2026) —
     только просмотр, настраивает администратор в карточке врача. */
  .weekday-chip {
    display: inline-flex; align-items: center; justify-content: center;
    width: 36px; height: 30px; border-radius: var(--r-sm); font-size: var(--fs-sm); font-weight: var(--fw-bold);
    background: var(--paper); color: var(--ink-faint); border: 1px solid var(--border);
  }
  .weekday-chip.active { background: var(--accent); color: var(--accent-ink); border-color: transparent; }
  input, select, textarea {
    padding: 8px 10px; border: 1px solid var(--border); border-radius: var(--r-xs);
    font-size: var(--fs-base); font-family: inherit; background: var(--card); color: var(--ink);
    /* v111 — было #fff: в тёплой бежевой теме карточка #fffdf8, а поле
       внутри было чистым белым — холодная "заплатка" на тёплой бумаге. */
  }
  textarea { resize: vertical; min-height: 50px; }

  button {
    padding: 9px 16px; border: none; border-radius: var(--r-xs); background: var(--accent); color: var(--accent-ink);
    font-size: var(--fs-base); font-weight: var(--fw-bold); font-family: inherit; cursor: pointer;
  }
  button:hover { background: var(--accent-strong); }
  button.secondary { background: var(--paper); color: var(--ink); border: 1px solid var(--border); }
  button.secondary:hover { background: color-mix(in srgb, var(--accent) 14%, var(--paper)); }
  button.danger { background: var(--money-bad-soft); color: var(--money-bad-ink); }
  button.danger:hover { background: var(--money-bad); color: #fff; }
  button.small { padding: 5px 10px; font-size: var(--fs-caption); }
  button.secondary.active-period { background: var(--accent); color: var(--accent-ink); border-color: transparent; }

  /* 13.09.2026 — Астра, п.5 её ответа: спиннер 16×16 для индикации "в
     процессе" на кнопках (см. setButtonBusy() в shell.js). Цвет — currentColor,
     чтобы автоматически совпадал с текстом кнопки (в т.ч. у .danger/.secondary,
     без отдельного правила на каждый вариант). */
  .btn-spinner {
    display: inline-block; width: 14px; height: 14px; margin-right: 7px;
    vertical-align: -2px; border-radius: 50%;
    border: 2px solid currentColor; border-right-color: transparent;
    opacity: 0.85; animation: btn-spinner-rotate 700ms linear infinite;
  }
  @keyframes btn-spinner-rotate { to { transform: rotate(360deg); } }
  @media (prefers-reduced-motion: reduce) {
    .btn-spinner { animation-duration: 1400ms; }
  }

  /* 13.09.2026 — Астра, п.1 её ответа: единый диалог подтверждения (см.
     uiConfirm() в shell.js). Визуальная спецификация — её формулировка
     дословно: ширина до 480px, отступ до края экрана 16px, padding 24px,
     радиус 12px, фон --card, текст --ink, тень --shadow-lg; заголовок
     20px/600, описание 14px, gap кнопок 8px, высота кнопок 36/44px;
     деструктивная кнопка — фон --money-bad-ink, текст белый (не всё окно
     красным). */
  dialog.ui-confirm-dialog {
    width: min(480px, calc(100vw - 32px));
    padding: 24px; border: none; border-radius: 12px;
    background: var(--card); color: var(--ink); box-shadow: var(--shadow-lg);
  }
  dialog.ui-confirm-dialog::backdrop { background: color-mix(in srgb, var(--ink) 45%, transparent); }
  .ui-confirm-close {
    position: absolute; top: 14px; right: 14px; width: 28px; height: 28px; padding: 0;
    background: transparent; color: var(--ink-soft); border-radius: 6px; font-size: var(--fs-base);
    display: flex; align-items: center; justify-content: center;
  }
  .ui-confirm-close:hover { background: var(--paper); color: var(--ink); }
  .ui-confirm-title { margin: 0 28px 8px 0; font-size: 20px; font-weight: 600; line-height: 1.3; color: var(--ink); }
  .ui-confirm-message { margin: 0 0 20px; font-size: 14px; line-height: 1.5; color: var(--ink-soft); white-space: pre-line; }
  .ui-confirm-actions { display: flex; justify-content: flex-end; gap: 8px; }
  .ui-confirm-actions button { min-height: 36px; padding: 8px 16px; }
  .ui-confirm-ok.danger { background: var(--money-bad-ink); color: #fff; }
  .ui-confirm-ok.danger:hover { background: var(--money-bad-ink); filter: brightness(0.92); }
  @media (max-width: 480px) {
    .ui-confirm-actions button { min-height: 44px; flex: 1; }
  }

  /* ======= «Режим приёма» в карточке врача (14.09.2026, макет Астры) =======
     Размеры/токены — точно по её спецификации (см.
     tdi-clinic-questions-for-astra.md): padding 16px, gap 8px, фон --card,
     рамка 1px solid --border, радиус --r-md (8px); подпись группы и
     название варианта 14px/600; пояснение 12px/400, line-height 18px,
     --ink-soft; вся строка варианта — область нажатия минимум 44px;
     выбранный вариант — фон color-mix(var(--accent) 12%, var(--card)),
     рамка --accent-strong, ТОЛЬКО название/фон меняются, не весь текст. */
  .dc-mode-fieldset {
    margin: 14px 0 0; padding: 16px; border: 1px solid var(--border);
    border-radius: var(--r-md); container-type: inline-size;
  }
  .dc-mode-fieldset legend { font-size: var(--fs-base); font-weight: 600; padding: 0 5px; }
  .dc-mode-choices { display: grid; gap: 8px; }
  .dc-mode-choice {
    display: flex; gap: 10px; align-items: flex-start; min-height: 44px;
    padding: 12px; border: 1px solid var(--border); border-radius: var(--r-sm);
    cursor: pointer;
  }
  .dc-mode-choice input { width: auto; margin: 3px 0 0; flex-shrink: 0; }
  .dc-mode-choice:has(input:checked) {
    background: color-mix(in srgb, var(--accent) 12%, var(--card));
    border-color: var(--accent-strong);
  }
  .dc-mode-choice strong { display: block; font-size: var(--fs-base); font-weight: 600; color: var(--ink); }
  .dc-mode-choice small { display: block; font-size: var(--fs-caption); line-height: 18px; color: var(--ink-soft); margin-top: 4px; }
  /* Блок обычно открыт внутри модалки карточки врача — колонки только при
     достаточной ширине самого блока (не окна), поэтому @container, не
     @media (её явное требование, т.к. модалка уже узкая сама по себе). */
  @container (min-width: 520px) {
    .dc-mode-choices { grid-template-columns: 1fr 1fr; }
  }

  /* v137 (.btn/.control, opt-in) перенесён в css/visual-v2.css под флаг
     data-ui-controls="v2" — фаза 2 плана Астры (11.09.2026, её ревью
     tdi-current-state.zip). Причина переноса: без гейта выключение v2 не
     возвращало бы прежний вид — старые правила .btn продолжали бы
     действовать. Сейчас .btn/.control нигде в разметке не используются,
     так что перенос не меняет ничего видимого. */

  /* Вертикальная суб-навигация внутри вкладки «Клиника» (Настройки /
     Способы оплаты / Сотрудники / Журнал) — колонка кнопок слева от
     контента, сразу справа от тёмного сайдбара, а не горизонтальный ряд
     пилюль сверху. Кнопки крупнее обычных "small", но заметно меньше
     пунктов главного сайдбара. */
  .ct-layout { display: flex; align-items: flex-start; gap: var(--sp-5); }
  .ct-tabs-vertical { display: flex; flex-direction: column; gap: var(--sp-1); width: 172px; flex-shrink: 0; position: sticky; top: 22px; }
  .ct-tabs-vertical .ct-tab-btn { width: 100%; text-align: left; padding: 9px 14px; font-size: var(--fs-base); }
  .ct-content { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--sp-4); }

  /* Календарь (07.09.2026, по замечанию пользователя) — раньше страница
     была центрирована узким .container (max-width:1180px), из-за чего по
     бокам на широких экранах пустовали большие поля. Теперь страница
     календаря тоже "на всю ширину" (.container.ct-active — тот же приём,
     что и у «Клиника»/«Финансы»), высвободившееся место справа занимает
     колонка со сводкой персонала.
     08.09.2026 здесь пробовали ограничивать сверху и центрировать ширину
     колонок врачей (minmax(140px, 200px) с доп. пределом и центрированием),
     чтобы при малом числе врачей колонки не растягивались на всю ширину.
     09.09.2026, по прямой просьбе пользователя, это ОТКАЧЕНО обратно —
     колонки в day-grid-header/day-grid-columns (ниже) снова простое
     minmax(140px, 1fr): один врач занимает всю доступную ширину, при
     нескольких врачах она делится между ними поровну — так было раньше и
     так пользователю привычнее/понятнее, даже с одним врачом на всю
     ширину широкого монитора. */
  .cal-layout { display: flex; align-items: flex-start; gap: var(--sp-5); }
  .cal-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--sp-4); }
  /* Ширина сужена с 260 до 230px (шаг 7 плана Астры, 11.09.2026) — карточки
     врачей/ассистентов объединены в одну и переживают чуть меньше места,
     сетке дня остаётся больше ширины. */
  .cal-side { width: 230px; flex-shrink: 0; position: sticky; top: 22px; display: flex; flex-direction: column; gap: var(--sp-4); }
  .cal-roster-summary { cursor: pointer; font-size: 13.5px; font-weight: 700; color: var(--ink); display: flex; align-items: center; gap: 6px; }
  .cal-roster-summary::-webkit-details-marker { color: var(--ink-muted); }
  @media (max-width: 980px) {
    /* На узких экранах боковая колонка под сводку персонала не помещается
       рядом с сеткой — переносим её под календарь, как было раньше. */
    .cal-layout { flex-direction: column; }
    .cal-side { width: 100%; position: static; }
  }

  /* Стрелки ← → рядом с полем "Дата" в календаре — шаг на день, вместо
     единственного способа (только сам input) переключать даты, 03.09.2026. */
  .cal-nav-arrow {
    width: 32px; height: 32px; border-radius: var(--r-xs); border: 1px solid var(--border);
    background: var(--card); color: var(--ink-soft); display: flex; align-items: center;
    justify-content: center; cursor: pointer; flex: none; padding: 0;
  }
  .cal-nav-arrow:hover { background: var(--paper); }
  .cal-nav-arrow svg { width: 14px; height: 14px; }

  /* Кнопка "Обновить" (иконка) на «Отчётах» — заменяет "Сформировать
     отчёт", 03.09.2026: отчёт теперь подгружается сам при открытии
     вкладки, кнопка нужна только чтобы перечитать данные после смены
     фильтров. Тот же квадратный вид, что и у .icon-select-wrap, но как
     обычная кнопка (без спрятанного <select> внутри). */
  .icon-action-btn {
    width: 36px; height: 36px; border-radius: var(--r-xs); border: 1px solid var(--border);
    background: var(--card); color: var(--ink-soft); display: flex; align-items: center;
    justify-content: center; cursor: pointer; flex: none; padding: 0;
  }
  .icon-action-btn:hover { background: var(--paper); }
  .icon-action-btn svg { width: 17px; height: 17px; transition: transform 0.4s ease; }
  .icon-action-btn.spinning svg { animation: icon-btn-spin 0.6s linear; }
  @keyframes icon-btn-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }

  /* Иконка-кнопка с квадратной рамкой вместо подписи "Сортировка" + видимого
     выпадающего списка, 03.09.2026 (по просьбе пользователя: список
     сортировки "не красиво смотрится"). <select> внутри по-прежнему
     проходит через общую тематизацию (.tsel — см. ниже в файле), поэтому
     тут только переопределяем вид уже готового .tsel-trigger: прячем его
     текст/стрелку, растягиваем на всю площадь квадрата — сама иконка
     (funnel) рисуется отдельным <svg>, лежащим под прозрачной кнопкой. */
  .icon-select-wrap {
    position: relative; width: 36px; height: 36px; border-radius: var(--r-xs);
    border: 1px solid var(--border); background: var(--card); color: var(--ink-soft);
    display: flex; align-items: center; justify-content: center; flex: none;
  }
  .icon-select-wrap:hover { background: var(--paper); }
  .icon-select-wrap > svg { width: 17px; height: 17px; pointer-events: none; position: relative; z-index: 1; }
  .icon-select-wrap .tsel { position: absolute; inset: 0; width: 100%; height: 100%; }
  .icon-select-wrap .tsel-trigger {
    position: absolute; inset: 0; width: 100%; height: 100%; padding: 0;
    border: none; background: transparent; border-radius: var(--r-xs);
  }
  .icon-select-wrap .tsel-trigger .tsel-label, .icon-select-wrap .tsel-trigger .tsel-caret { display: none; }
  .icon-select-wrap.active { border-color: var(--accent); color: var(--accent-strong); background: var(--card); }
  .icon-select-wrap.active::after {
    content: ''; position: absolute; top: -3px; right: -3px; width: 8px; height: 8px;
    border-radius: 50%; background: var(--accent); border: 2px solid var(--paper);
  }

  /* Компактные плашки дат в "Быстром переходе" — день недели + число,
     вместо текста "Вчера (02.09)"/"Сегодня (03.09)"/"Завтра (04.09)",
     по образцу макета календаря, понравившегося пользователю, 03.09.2026. */
  .day-pill {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    width: 46px; padding: 6px 4px; border-radius: var(--r-xs); border: 1px solid var(--border);
    background: #fff; cursor: pointer; font-family: inherit; flex: none;
  }
  .day-pill .wd { font-size: var(--fs-micro); font-weight: var(--fw-bold); color: var(--ink-faint); text-transform: uppercase; letter-spacing: .3px; }
  .day-pill .dd { font-size: var(--fs-base); font-weight: var(--fw-bold); color: var(--ink); margin-top: 1px; }
  .day-pill:hover { background: var(--paper); }
  .day-pill.today { border-color: var(--accent-strong); }
  .day-pill.today .wd, .day-pill.today .dd { color: var(--accent-strong); }
  .day-pill .dot { width: 4px; height: 4px; border-radius: 50%; background: var(--accent-strong); margin-top: 2px; }
  .day-pill.active { background: var(--accent); border-color: var(--accent); }
  .day-pill.active .wd, .day-pill.active .dd { color: var(--accent-ink); }
  .day-pill.active .dot { background: var(--accent-ink); }

  /* Аватарка-кружок с инициалом у врача в шапке календаря — легче найти
     взглядом нужную колонку. Цвет фона/текста задаётся инлайн-стилем в
     JS (doctorAvatarColor) — свой у каждого врача, см. renderDayGrid. */
  .doc-avatar {
    width: 22px; height: 22px; border-radius: 50%;
    font-size: var(--fs-micro); font-weight: var(--fw-bold); display: flex; align-items: center; justify-content: center;
    flex: none;
  }

  /* Сводная таблица «Клиника → Расписание» (04.09.2026) — тот же приём
     аватарки-кружка с инициалом, что и в шапке календаря (.doc-avatar),
     только крупнее, т.к. тут это не мелкая метка колонки, а основной
     идентификатор строки таблицы. */
  .doc-cell { display: flex; align-items: center; gap: var(--sp-3); }
  .doc-avatar-lg {
    width: 34px; height: 34px; border-radius: 50%; flex: none;
    font-size: var(--fs-sm); font-weight: var(--fw-bold); display: flex; align-items: center; justify-content: center;
  }
  .doc-name { font-weight: var(--fw-bold); font-size: var(--fs-base); }
  .doc-spec { font-size: var(--fs-caption); color: var(--ink-soft); }
  .hours-cell { font-size: var(--fs-base); font-weight: var(--fw-medium); }
  .hours-cell.inherited { color: var(--ink-soft); font-weight: var(--fw-medium); }
  .no-off-dash { color: var(--ink-faint); font-size: var(--fs-sm); }

  /* Иконки-действия у настраиваемых списков (категории расходов, способы
     оплаты) — "изменить"/"удалить" вместо текстовой кнопки "Удалить",
     30.08.2026, по запросу пользователя. */
  .icon-btn { display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 30px; border: none; border-radius: var(--r-xs); cursor: pointer; background: transparent; padding: 0; flex: none; transition: background-color .12s ease; }
  .icon-btn svg { width: 16px; height: 16px; }
  .icon-btn.edit { color: var(--accent); }
  .icon-btn.edit:hover { background: color-mix(in srgb, var(--accent) 14%, transparent); }
  .icon-btn.delete { color: var(--money-bad-ink); }
  .icon-btn.delete:hover { background: var(--money-bad-soft); }
  .icon-btn.save { color: var(--money-good-ink); }
  .icon-btn.save:hover { background: var(--money-good-soft); }
  .icon-btn.cancel { color: var(--ink-faint); }
  .icon-btn.cancel:hover { background: color-mix(in srgb, var(--ink-faint) 16%, transparent); }
  .icon-actions { display: inline-flex; gap: var(--sp-1); vertical-align: middle; }
  /* «Записать выплату»/«Премии»/«Штрафы» в «Финансы → Выплата персоналу»
     прошли путь: три кнопки под строкой → три иконки в ряд (упирались в
     ширину таблицы) → кебаб-меню в строке → кнопки внутри модалки (клик по
     строке всё равно нужен для просмотра сводки, а прямого доступа одним
     кликом не стало). Итог (08.09.2026): три иконки ВЕРТИКАЛЬНО одна под
     другой — колонка действий занимает по ширине один icon-btn (30px), как
     кебаб, но без промежуточного меню, и клик по строке отдельно открывает
     карточку сотрудника с подробностями. */
  .icon-actions-v { display: inline-flex; flex-direction: column; gap: var(--sp-1); }
  .icon-btn.payout { color: var(--accent); }
  .icon-btn.payout:hover { background: color-mix(in srgb, var(--accent) 14%, transparent); }
  .icon-btn.bonus { color: var(--money-good-ink); }
  .icon-btn.bonus:hover { background: var(--money-good-soft); }
  .icon-btn.penalty { color: var(--money-bad-ink); }
  .icon-btn.penalty:hover { background: var(--money-bad-soft); }
  /* Ряды «Выплата персоналу» стали неровными, как только колонка действий
     тоже стала занимать место по высоте (три иконки в столбик выше, чем
     обычная строка текста) — фиксируем высоту ВСЕХ ячеек этой таблицы
     одним значением, чтобы строки были одинаковыми независимо от того,
     сколько строк текста в имени/штрафах и т.п. (см. .fin-name-cell —
     имя и так обрезано в одну строку). */
  #fin-table-body td { height: 100px; box-sizing: border-box; vertical-align: middle; }
  #fin-table-body .fin-total-row td { height: auto; }
  /* «Выплата персоналу»: при наведении на строку подсветка (tbody
     tr:hover) шла ровно до правого края таблицы, а таблица шла ровно до
     правого края внутренней области карточки — без зазора между ними и
     скруглённой рамкой .card подсветка визуально касалась/вылезала за
     рамку (найдено пользователем по скриншоту, 08.09.2026). Даём карточке
     запас справа и overflow:hidden — так рамка гарантированно ничего не
     выпускает наружу, даже если что-то внутри чуть шире расчётного. */
  #fin-payroll-card { padding-right: 30px; overflow: hidden; }
  .named-list-row-actions { display: flex; gap: var(--sp-1); justify-content: flex-end; }
  .named-list-rename-input { flex: 1; min-width: 0; padding: 6px 9px; border: 1px solid var(--accent); border-radius: var(--r-xs); font-size: var(--fs-base); font-family: inherit; background: var(--card); color: var(--ink); }

  table { width: 100%; border-collapse: collapse; font-size: var(--fs-base); }
  /* v111 (09.09.2026) — таблицы (аудит графического дизайна, P1 п.11). Было:
     шапка --ink-faint/800/11px/капс/разрядка 0.04em — три усилителя (капс,
     вес 800, разрядка) и один гаситель (самый бледный цвет) одновременно,
     из-за чего шапка читалась хуже, а не лучше. Линия под шапкой и линия
     между строками были одной и той же силы, и таблица выглядела как сетка,
     а не список. Стало: цвет --ink-soft (контрастнее), вес 600, разрядка
     0.06em; граница под шапкой — полной силы var(--border), между строками —
     ослабленная тем же цветом (color-mix 55%). */
  th, td { text-align: left; padding: 10px 12px; }
  th { color: var(--ink-soft); font-weight: var(--fw-medium); font-size: var(--fs-micro); text-transform: uppercase; letter-spacing: 0.06em; border-bottom: 1px solid var(--border); }
  td { border-bottom: 1px solid color-mix(in srgb, var(--border) 55%, var(--card)); }
  tr:last-child td { border-bottom: none; }
  tbody tr:nth-child(even) { background: color-mix(in srgb, var(--accent) 4%, transparent); }
  tbody tr:hover { background: color-mix(in srgb, var(--accent) 8%, var(--card)); }

  .badge { padding: 4px 10px; border-radius: var(--r-pill); font-size: var(--fs-caption); font-weight: var(--fw-bold); display: inline-flex; align-items: center; gap: var(--sp-2); }
  .badge::before { content: ''; width: 5px; height: 5px; border-radius: 50%; background: currentColor; }
  .badge.scheduled { background: var(--money-info-soft); color: var(--money-info-ink); }
  .badge.completed { background: var(--money-good-soft); color: var(--money-good-ink); }
  .badge.cancelled { background: var(--money-bad-soft); color: var(--money-bad-ink); }
  .badge.no_show { background: var(--money-warn-soft); color: var(--money-warn-ink); }
  .actions-cell { display: flex; gap: var(--sp-2); flex-wrap: wrap; }

  /* ---- Детали визита по клику на строку (v43) ---- */
  .visit-row { cursor: pointer; }
  .visit-row:hover td { background: color-mix(in srgb, var(--accent) 8%, var(--card)); }
  .visit-caret { display: inline-block; margin-right: 6px; color: var(--ink-faint); transition: transform 0.15s ease; font-size: var(--fs-micro); }
  .visit-row.expanded .visit-caret { transform: rotate(90deg); color: var(--accent-strong); }
  .visit-detail-wrap td { padding: 0; border-bottom: 1px solid var(--border); }
  .visit-detail {
    background: var(--paper); border-top: 1px dashed var(--border); padding: 16px 18px 18px;
    animation: visitDetailOpen 0.15s ease;
  }
  @keyframes visitDetailOpen { from { opacity: 0; transform: translateY(-4px);} to { opacity: 1; transform: translateY(0);} }
  .visit-detail-heading { font-size: var(--fs-sm); font-weight: var(--fw-bold); color: var(--ink-soft); text-transform: uppercase; letter-spacing: 0.04em; margin: 0 0 10px; }
  .visit-detail-empty { font-size: var(--fs-sm); color: var(--ink-faint); }

  .tooth-chip-row { display: flex; gap: var(--sp-3); flex-wrap: wrap; margin-bottom: 14px; }
  .tooth-chip { display: flex; flex-direction: column; align-items: center; gap: var(--sp-1); }
  .tooth-chip-square {
    width: 34px; height: 34px; border-radius: var(--r-xs); display: flex; align-items: center; justify-content: center;
    font-weight: var(--fw-bold); font-size: var(--fs-sm); border: 2px solid var(--accent-strong);
    background: color-mix(in srgb, var(--accent) 22%, var(--card)); color: var(--accent-ink, var(--ink));
  }
  .tooth-chip-square.planned-variant { border-color: var(--money-info); background: color-mix(in srgb, var(--money-info) 18%, var(--card)); color: var(--money-info-ink); }
  .tooth-chip-lbl { font-size: var(--fs-micro); color: var(--ink-faint); font-weight: var(--fw-bold); }

  .visit-proc-item { display: flex; gap: var(--sp-3); align-items: flex-start; padding: 8px 0; border-bottom: 1px solid var(--border); }
  .visit-proc-item:last-child { border-bottom: none; }
  .visit-proc-num {
    flex: none; width: 24px; height: 24px; border-radius: 50%; background: var(--ink-soft);
    color: var(--card); font-weight: var(--fw-bold); font-size: var(--fs-caption);
    display: flex; align-items: center; justify-content: center; margin-top: 1px;
  }
  .visit-proc-body { flex: 1; min-width: 0; }
  .visit-proc-title { font-weight: var(--fw-bold); font-size: var(--fs-base); }
  .visit-proc-meta { font-size: var(--fs-caption); color: var(--ink-faint); margin-top: 2px; }
  .visit-proc-price { flex: none; font-weight: var(--fw-bold); white-space: nowrap; font-size: var(--fs-base); }

  .visit-detail-footer { margin-top: 12px; display: flex; justify-content: flex-end; }
  .visit-detail-link { font-size: var(--fs-sm); color: var(--accent-strong); text-decoration: none; font-weight: var(--fw-bold); cursor: pointer; }
  .visit-detail-link:hover { text-decoration: underline; }

  .visit-callout {
    background: var(--card); border: 1px dashed var(--accent-strong); border-radius: var(--r-sm);
    padding: 12px 14px; font-size: var(--fs-sm); color: var(--ink-soft); margin-top: 16px;
  }
  .visit-callout b { color: var(--ink); }
  .visit-export-row { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); margin-bottom: 14px; flex-wrap: wrap; }

  .modal-backdrop {
    display: none;
    position: fixed; inset: 0; background: rgba(36,28,19,0.45);
    /* z-index выше, чем у .login-screen (100) — иначе модалки "Технический
       доступ" и "Первый вход", открытые прямо с экрана логина, рисуются под
       непрозрачным фоном экрана входа и выглядят так, будто клик ничего не
       делает (баг, найденный вживую 26.08.2026). */
    align-items: center; justify-content: center; z-index: 150; padding: 16px;
  }
  .modal-backdrop.active { display: flex; }

  /* ---------- экран входа (v20, роли сотрудников) ---------- */
  /* Экран входа — единственный момент, когда ещё не известно, какая это
     клиника (и её тема ещё не применилась, а то и применился ЧУЖОЙ
     закэшированный цвет с прошлого раза, см. кэш data-palette в <head>).
     Поэтому здесь фирменные цвета Anexis жёстко зашиты, а не через
     var(--dark-bg)/var(--card) (те принадлежат теме конкретной клиники и
     тут не подходят), 04.09.2026.
     v111 (09.09.2026) — аудит графического дизайна (п.10) предлагал перевести
     этот экран на var(--dark-bg)/var(--card)/var(--accent). Сознательно НЕ
     сделано: это ровно тот баг, который фикс от 04.09.2026 выше устраняет —
     до входа клиника не известна, и токены темы могли бы взять чужой
     закэшированный цвет. Пункт 10 из аудита оставлен нереализованным. */
  .login-screen {
    display: none;
    position: fixed; inset: 0; z-index: 100;
    background: #0E1A19; color: #F4F6F5;
    align-items: center; justify-content: center; padding: 20px;
  }
  .login-screen.active { display: flex; }
  /* 23.09.2026 — экран входа «Приёмная»: эти правила раньше были на
     `.login-card` (голый класс), но новая разметка `#login-v2` тоже
     использует класс `.login-card` для своей карточки формы (Астра,
     integration-notes.md, п.1: "убедиться, что более поздний старый CSS
     входа не перекрывает эти правила... не лечить это общим !important").
     #login-legacy — тот же элемент, что и .login-card у старого экрана
     (см. admin.html: <div class="login-card" id="login-legacy">), поэтому
     замена селектора на #login-legacy ничего не меняет для легаси и
     полностью убирает пересечение с #login-v2 .login-card. */
  #login-legacy {
    position: relative;
    width: 100%; max-width: 360px; background: #F4F6F5; color: #131C1B;
    border-radius: var(--r-lg); padding: 28px 26px;
    box-shadow: 0 16px 30px -16px rgba(6,14,13,0.45), 0 3px 8px rgba(6,14,13,0.22);
  }
  #login-legacy .field label { color: #5B6D69; }
  #login-legacy input { border-color: #D7E4E3; color: #131C1B; background: #fff; }
  #login-legacy button:not(.login-tech-gear) { background: #135E56; color: #F4F6F5; }
  #login-legacy button:not(.login-tech-gear):hover { background: #0d443e; }
  /* Малозаметная шестерёнка в углу экрана входа — единственный путь к
     техническому доступу (вход по сырому Admin API Key клиники в обход
     обычного входа по телефону/паролю). Специально без подписи и почти
     прозрачная: это аварийный люк для владельца, знающего, что искать, а
     не то, что должно бросаться в глаза обычному сотруднику. */
  .login-tech-gear {
    position: absolute; top: 14px; right: 16px;
    width: 20px; height: 20px; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    color: #5B6D69; opacity: 0.28; cursor: pointer;
    background: none; border: none; padding: 0;
    transition: opacity 0.15s;
  }
  .login-tech-gear svg { width: 13px; height: 13px; }
  .login-tech-gear:hover { opacity: 0.55; }
  #login-legacy .nav-brand-mark { margin: 0 auto 14px; }
  #login-legacy h2 { text-align: center; margin: 0 0 4px; font-size: var(--fs-lg); }
  #login-legacy .hint { text-align: center; margin-bottom: 18px; }
  /* Раньше это правило (без :not) заодно растягивало .login-tech-gear на
     всю ширину карточки — из-за border-radius:50% это давало вытянутый
     "стадион" вместо кружка, который проявлялся как большое цветное пятно
     сверху карточки при фокусе/наведении (баг, найден пользователем
     04.09.2026). */
  #login-legacy button:not(.login-tech-gear) { width: 100%; }
  .login-links { display: flex; flex-direction: column; gap: var(--sp-2); margin-top: 14px; text-align: center; }
  .login-links a { font-size: var(--fs-sm); color: #5B6D69; cursor: pointer; text-decoration: underline; }
  #current-user-name { display: block; font-weight: var(--fw-bold); color: var(--dark-text); }
  .modal {
    background: var(--card); border-radius: var(--r-lg); padding: 22px; box-shadow: var(--shadow-md);
    max-width: 520px; width: 100%; max-height: 90vh; overflow-y: auto;
  }
  .modal h3 { margin-top: 0; font-family: var(--font-heading); }
  #modal-doctor-earnings .modal { max-width: 860px; }

  /* v141 (Астра, шаг 7) — «контекстный drawer без вложенных модалок» для
     записи на приём: #modal-appointment/#modal-appt-detail получают этот
     модификатор (см. openDrawer/closeDrawer в JS) и превращаются в
     боковую панель справа во всю высоту вместо центральной модалки.
     ТОЛЬКО эти два диалога — остальные модалки (карточки пациента/врача,
     настройки и т.п.) не тронуты, у них обычный .modal-backdrop/.modal. */
  .modal-backdrop.drawer-mode { justify-content: flex-end; align-items: stretch; padding: 0; }
  .modal-backdrop.drawer-mode .modal {
    width: 420px; max-width: 100%; height: 100%; max-height: 100%;
    border-radius: 0; margin: 0; box-shadow: -16px 0 40px -16px color-mix(in srgb, var(--ink) 35%, transparent);
    animation: drawer-slide-in var(--motion-base, 180ms) ease-out;
  }
  @keyframes drawer-slide-in { from { transform: translateX(24px); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
  @media (prefers-reduced-motion: reduce) { .modal-backdrop.drawer-mode .modal { animation: none; } }
  @media (max-width: 560px) { .modal-backdrop.drawer-mode .modal { width: 100%; } }
  .de-summary-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--sp-3); margin: 12px 0 18px; }
  .de-summary-tiles .summary-tile { margin: 0; }
  .de-works-wrap { max-height: 320px; overflow-y: auto; border: 1px solid var(--border); border-radius: var(--r-sm); }
  .de-works-wrap table { margin: 0; }
  .msg { padding: 10px 12px; border-radius: var(--r-xs); font-size: var(--fs-sm); margin-bottom: 12px; }
  .msg.error { background: var(--money-bad-soft); color: var(--money-bad-ink); }
  .msg.ok { background: var(--money-good-soft); color: var(--money-good-ink); }
  .hint { font-size: var(--fs-caption); color: var(--ink-faint); margin-top: 4px; }

  /* Кнопка-подсказка (?) — прячет статичный текст "как это работает" за
     клик, вместо того чтобы всегда показывать его на экране (03.09.2026,
     по просьбе пользователя: "инструкции во всех разделах нужно скрыть
     под кнопку"). Только для НЕИЗМЕНЯЕМЫХ обучающих подсказок — живые
     данные и пояснения к конкретным цифрам/полям (например, "Рабочие
     часы: ...", подпись под суммой в отчёте) намеренно оставлены видимыми
     как раньше, через обычный .hint. */
  .info-tip-btn {
    display: inline-flex; align-items: center; justify-content: center;
    width: 20px; height: 20px; border-radius: 50%; flex: none;
    border: 1px solid var(--border); background: var(--paper); color: var(--ink-soft);
    font-size: var(--fs-caption); font-weight: var(--fw-bold); line-height: 1; padding: 0; cursor: pointer;
    font-family: inherit; vertical-align: middle;
  }
  .info-tip-btn:hover, .info-tip-btn.open { border-color: var(--accent); color: var(--accent-strong); background: var(--card); }
  .info-tip-popover {
    position: fixed; z-index: 210; background: var(--card); border: 1px solid var(--border);
    border-radius: var(--r-sm); box-shadow: var(--shadow-md); padding: 10px 12px;
    font-size: var(--fs-sm); line-height: 1.45; color: var(--ink-soft); max-width: min(340px, calc(100vw - 24px));
  }
  .item-row { display: flex; gap: var(--sp-2); align-items: flex-end; margin-bottom: 8px; flex-wrap: wrap; }
  .item-row .field { margin-bottom: 0; flex: 1; min-width: 120px; }
  /* 14.09.2026 — строки «Способы оплаты» в модалке оплаты визита (по
     спецификации Астры). Способ растягивается, сумма — фиксированной
     ширины (её ограничивает inline style max-width:160px в разметке),
     кнопка удаления — узкая; на ≤480px .item-row и так переносит поля по
     min-width:120px, здесь только чуть увеличиваем зазор между строками. */
  .pay-part-row { margin-bottom: 12px; }
  .pay-part-row .pp-error { font-size: var(--fs-caption, 12px); }
  .checklist { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: var(--sp-2) 16px; margin: 6px 0; }
  .checklist label { display: flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-base); font-weight: var(--fw-normal); }
  .checklist input { width: auto; }
  .plan-phase-label { font-weight: var(--fw-bold); margin: 14px 0 6px; }
  /* Важно: колонки заданы через minmax(0, ...), а не голыми ...fr — иначе
     сетка не может сжать <select> с длинными названиями услуг ниже их
     "естественной" ширины, и вся строка (а с ней кнопка ✕ удаления)
     вылезает за пределы модалки шириной 860px, из-за чего появляется
     горизонтальный скролл и карточка пациента визуально "скачет" в
     размере при переходе на вкладку «План лечения» (баг, замеченный
     28.08.2026). #plan-items дополнительно завёрнут в overflow-x:auto
     как страховка на маленьких экранах — сама модалка при этом не растёт. */
  .plan-item-row {
    display: grid;
    grid-template-columns: minmax(0,1.6fr) minmax(0,1fr) minmax(0,0.7fr) minmax(0,0.7fr) minmax(0,0.9fr) minmax(0,1.1fr) auto;
    gap: var(--sp-2); align-items: end; margin-bottom: 8px; min-width: 0;
  }
  .plan-item-row .field { margin-bottom: 0; min-width: 0; }
  .plan-item-row .field select, .plan-item-row .field input { min-width: 0; width: 100%; box-sizing: border-box; }
  #plan-items-wrap { overflow-x: auto; }

  /* Мини-схема зубов у поля "Зуб/область" в плане лечения (v35.1,
     02.09.2026) — кнопка рядом с полем открывает попап с сеткой 32 зубов,
     клик по зубу подставляет его номер в поле и делает связь со схемой
     структурной (row.dataset.toothNumber), как и при добавлении со вкладки
     "Зубная формула". Портализуем в body и переиспользуем z-index класса
     .tsel-panel-portal, чтобы попап не перекрывался модалками — та же
     проблема, что уже решалась для .tsel-panel (см. комментарий выше). */
  .pl-tooth-wrap, .pi-tooth-wrap { display: flex; gap: var(--sp-2); align-items: center; }
  .pl-tooth-wrap .pl-tooth, .pi-tooth-wrap .pi-area { flex: 1; min-width: 0; }
  .pl-tooth-pick-btn, .pi-tooth-pick-btn {
    flex: none; width: 34px; height: 34px; border-radius: var(--r-xs);
    border: 1px solid var(--border); background: var(--card); cursor: pointer;
    font-size: var(--fs-md); line-height: 1; padding: 0;
  }
  .pl-tooth-pick-btn:hover, .pi-tooth-pick-btn:hover { border-color: var(--accent); }
  .pl-tooth-popover-portal {
    position: fixed; z-index: 200; background: var(--card);
    border: 1px solid var(--border); border-radius: var(--r-sm);
    box-shadow: var(--shadow-md); padding: 14px; width: 600px;
    max-width: min(600px, calc(100vw - 24px));
    box-sizing: border-box;
  }
  .pl-tooth-popover-title { font-size: var(--fs-sm); color: var(--ink-soft); font-weight: var(--fw-bold); margin-bottom: 10px; }
  .pl-tooth-popover-jaw { display: flex; flex-direction: column; gap: var(--sp-2); overflow-x: auto; }
  .pl-tooth-popover-row { display: flex; align-items: center; gap: var(--sp-2); min-width: 0; }
  .pl-tp-quad { display: flex; gap: var(--sp-1); flex: 1; justify-content: space-between; min-width: 0; }
  .pl-tp-mid { width: 1px; align-self: stretch; background: var(--border); flex: none; }
  .pl-mini-tooth {
    width: 30px; height: 30px; flex: none; border-radius: var(--r-xs); border: 1px solid var(--border);
    background: var(--paper); font-size: var(--fs-caption); font-weight: var(--fw-bold); color: var(--ink-soft);
    cursor: pointer; padding: 0; display: flex; align-items: center; justify-content: center;
  }
  .pl-mini-tooth:hover { border-color: var(--accent); }
  .pl-mini-tooth.selected { background: var(--accent); border-color: var(--accent-strong); color: var(--card); }
  .pl-mini-tooth.has-plan { box-shadow: 0 0 0 1.5px var(--money-info) inset; }
  .pl-tooth-popover-hint { margin-top: 10px; font-size: var(--fs-caption); color: var(--ink-faint); line-height: 1.4; }
  .pl-tooth-popover-actions { display: flex; gap: var(--sp-2); margin-top: 10px; }
  .pl-tooth-popover-clear, .pl-tooth-popover-apply { flex: 1; padding: 8px; border-radius: var(--r-xs); border: 1px solid var(--border); cursor: pointer; font-size: var(--fs-sm); }
  .pl-tooth-popover-clear { background: var(--card); color: var(--ink-soft); }
  .pl-tooth-popover-clear:hover { border-color: var(--money-bad); color: var(--money-bad-ink); }
  .pl-tooth-popover-apply { background: var(--accent); border-color: var(--accent-strong); color: var(--card); font-weight: var(--fw-bold); }
  .pl-tooth-popover-apply:hover { background: var(--accent-strong); }
  /* Кастомный «themed select» (tsel) — заменяет нативный вид <select> на
     оформление в цвет текущей палитры клиники (var(--accent) и т.п.),
     сохраняя сам <select> в DOM как единственный источник истины (см.
     enhanceSelect() в JS): скрытый select остаётся кликабельным для
     скринридеров/автотестов, а визуально показывается .tsel-trigger +
     всплывающая .tsel-panel со списком .tsel-opt, построенным из живых
     <option> select'а. 29.08.2026, по запросу пользователя — единый стиль
     выпадающих списков по всему приложению, в цвет выбранной темы. */
  .tsel { position: relative; display: inline-block; width: 100%; }
  .tsel select.tsel-native, .tsel input.tsel-native {
    position: absolute; inset: 0; width: 100%; height: 100%;
    opacity: 0; pointer-events: none;
  }
  .tsel-trigger {
    width: 100%; display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2);
    box-sizing: border-box; text-align: left; cursor: pointer;
    background: var(--card); color: var(--ink, #222);
    border: 1px solid var(--border); border-radius: var(--r-xs);
    padding: 8px 10px; font-size: var(--fs-base); font-family: inherit;
  }
  .tsel-trigger:hover { border-color: var(--accent); }
  .tsel.open .tsel-trigger { border-color: var(--accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 20%, transparent); }
  .tsel-trigger .tsel-caret { flex: none; width: 9px; height: 9px; border-right: 2px solid var(--ink-faint); border-bottom: 2px solid var(--ink-faint); transform: rotate(45deg); margin-top: -3px; transition: transform 0.15s ease; }
  .tsel.open .tsel-trigger .tsel-caret { transform: rotate(-135deg); margin-top: 3px; }
  .tsel-trigger .tsel-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .tsel-trigger:disabled, .tsel.disabled .tsel-trigger { opacity: 0.55; cursor: not-allowed; }
  .tsel-panel {
    position: absolute; z-index: 60; top: calc(100% + 4px); left: 0; right: 0;
    max-height: 260px; overflow-y: auto;
    background: var(--card); border: 1px solid var(--border); border-radius: var(--r-xs);
    box-shadow: var(--shadow-md);
    padding: 4px; display: none;
  }
  /* Портализованная панель (см. enhanceSelect → openPanel в JS) лежит прямо
     в <body>, а не внутри .tsel — её z-index должен перекрывать модалки
     (.modal-backdrop: 150), иначе она рисуется корректно визуально, но клики
     перехватывает элемент модалки под ней (баг, найденный 29.08.2026 на
     выпадающем списке «Манипуляция» в плане лечения — панель была видна, но
     некликабельна). */
  .tsel-panel-portal { z-index: 200; }
  .tsel.open .tsel-panel { display: block; }
  .tsel-opt { padding: 7px 10px; border-radius: var(--r-xs); font-size: var(--fs-base); cursor: pointer; white-space: normal; overflow-wrap: anywhere; line-height: 1.3; }
  .tsel-opt:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
  .tsel-opt.selected { background: color-mix(in srgb, var(--accent) 18%, transparent); font-weight: var(--fw-medium); color: var(--accent); }
  /* Поле поиска у длинных выпадающих списков (30.08.2026, по запросу
     пользователя — список услуг в форме оплаты визита с десятками пунктов
     неудобно листать вручную). Появляется автоматически у ЛЮБОГО tsel,
     где вариантов больше TSEL_SEARCH_THRESHOLD (см. JS) — не только у
     конкретного списка услуг, единый компонент для всего приложения.
     Когда поиск показан, .tsel-panel становится flex-колонкой: поле
     поиска закреплено сверху (flex:none), скроллится только список
     вариантов ниже него (.tsel-opt-list) — а не вся панель целиком,
     иначе поиск при скролле уезжал бы из виду вместе со списком. */
  .tsel-panel.has-search { display: flex; flex-direction: column; max-height: 320px; overflow: hidden; }
  .tsel-search-wrap { flex: none; padding: 2px 2px 6px; margin-bottom: 4px; border-bottom: 1px solid var(--border); }
  .tsel-search-input { width: 100%; padding: 6px 9px; border: 1px solid var(--border); border-radius: var(--r-xs); font-size: var(--fs-sm); font-family: inherit; background: var(--card); color: var(--ink); }
  .tsel-search-input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 20%, transparent); }
  .tsel-opt-list { overflow-y: auto; flex: 1 1 auto; min-height: 0; }
  .tsel-empty { padding: 10px; font-size: var(--fs-sm); color: var(--ink-faint); text-align: center; }
  /* Ссылки внутри ЛЮБОЙ панели tsel ("Выбрать всех"/"Сбросить" у чекбоксов
     врачей, "Сегодня"/"Очистить" у календаря) раньше оставались стандартного
     браузерного синего цвета — не были явно стилизованы под тему.
     30.08.2026, найдено пользователем на проде. */
  .tsel-panel a { color: var(--accent); text-decoration: none; font-weight: var(--fw-medium); }
  .tsel-panel a:hover { text-decoration: underline; }
  /* Чекбоксы (мульти-выбор врачей, анкета здоровья и т.п.) — в цвет темы,
     а не системный синий по умолчанию браузера. */
  input[type="checkbox"] { accent-color: var(--accent); }
  /* Оклад считается помесячно (08.09.2026) — на «Финансах», «Отчётах» и в
     окне «Зарплата врача» период выбирается месяцами, а не двумя датами;
     чуть шире обычного date-поля, чтобы подпись месяца не обрезалась. */
  input[type="month"] { min-width: 150px; }

  /* ---- Тематизированные дата/время (dsel/tmsel, 30.08.2026) ----
     Тот же принцип, что и у tsel: настоящий <input type="date"/"time">
     остаётся в DOM визуально скрытым и единственным источником истины
     (значение — простая строка "ГГГГ-ММ-ДД"/"ЧЧ:ММ", БЕЗ часового пояса,
     ровно как у нативного инпута — поэтому смена виджета не меняет то,
     что уходит на сервер). Показываем кнопку-триггер + портализованную в
     body панель: календарь-сетка для даты, прокручиваемый список для
     времени. "Сегодня" в календаре считается явно по Ташкенту (Intl,
     timeZone: Asia/Tashkent), а не по часам устройства — см. tashkentToday(). */
  /* max-height/overflow-y у базового .tsel-panel рассчитаны на прокручиваемые
     списки вариантов — календарю-сетке (шапка + 6 строк дней + футер) это
     не подходит: весь месяц должен быть виден сразу, без внутренней
     прокрутки внутри всплывающей панели (найдено пользователем 30.08.2026). */
  .dsel-panel { padding: 8px; width: 264px; max-height: none; overflow: visible; }
  .dsel-cal-header { display: flex; align-items: center; justify-content: space-between; padding: 2px 2px 8px; }
  .dsel-cal-title { font-weight: var(--fw-bold); font-size: var(--fs-base); }
  /* Заголовок-кнопка «Месяц Год» / «Год» — клик уводит на уровень выше
     (дни → месяцы → годы), см. enhanceDateInput/enhanceMonthRangeControl,
     24.09.2026 (жалоба: нельзя было быстро проскроллить календарь до нужного
     года рождения пациента — раньше только по одному месяцу за клик). */
  .dsel-cal-title-btn { cursor: pointer; padding: 2px 8px; border-radius: var(--r-xs); }
  .dsel-cal-title-btn:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); color: var(--accent); }
  .dsel-cal-nav { border: none; background: transparent; cursor: pointer; width: 28px; height: 28px; border-radius: var(--r-xs); display: flex; align-items: center; justify-content: center; color: var(--ink-soft); font-size: var(--fs-md); }
  .dsel-cal-nav:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); color: var(--accent); }
  .dsel-cal-weekdays { display: grid; grid-template-columns: repeat(7, 1fr); text-align: center; font-size: var(--fs-micro); color: var(--ink-faint); margin-bottom: 4px; text-transform: uppercase; letter-spacing: .02em; }
  .dsel-cal-days { display: grid; grid-template-columns: repeat(7, 1fr); gap: var(--sp-1); }
  .dsel-cal-day { aspect-ratio: 1; display: flex; align-items: center; justify-content: center; border-radius: var(--r-xs); cursor: pointer; font-size: var(--fs-sm); }
  .dsel-cal-day:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
  .dsel-cal-day.muted { color: var(--ink-faint); opacity: .45; }
  .dsel-cal-day.today { font-weight: var(--fw-bold); color: var(--accent); box-shadow: inset 0 0 0 1.5px var(--accent); }
  .dsel-cal-day.selected { background: var(--accent); color: #fff; font-weight: var(--fw-bold); }
  /* Сетка месяцев (уровень «месяцы», 24.09.2026) — те же цвета/акценты, что
     у .dsel-cal-day, но 3 колонки на 4 строки (12 месяцев). */
  .dsel-cal-monthgrid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--sp-1); margin: 2px 0 6px; }
  .dsel-cal-month { padding: 12px 4px; text-align: center; border-radius: var(--r-xs); cursor: pointer; font-size: var(--fs-sm); }
  .dsel-cal-month:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
  .dsel-cal-month.today { font-weight: var(--fw-bold); color: var(--accent); box-shadow: inset 0 0 0 1.5px var(--accent); }
  .dsel-cal-month.selected { background: var(--accent); color: #fff; font-weight: var(--fw-bold); }
  /* Сетка лет (уровень «годы», 24.09.2026) — постранично по
     DSEL_YEARS_PER_PAGE (12 лет), стрелки заголовка листают целую страницу
     лет за раз, а не по одному году, чтобы быстро долистать до старой даты
     рождения. */
  .dsel-cal-yeargrid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--sp-1); margin: 2px 0 6px; }
  .dsel-cal-year { padding: 12px 4px; text-align: center; border-radius: var(--r-xs); cursor: pointer; font-size: var(--fs-sm); }
  .dsel-cal-year:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
  .dsel-cal-year.today { font-weight: var(--fw-bold); color: var(--accent); box-shadow: inset 0 0 0 1.5px var(--accent); }
  .dsel-cal-year.selected { background: var(--accent); color: #fff; font-weight: var(--fw-bold); }
  .dsel-cal-footer { display: flex; justify-content: space-between; gap: var(--sp-2); padding-top: 8px; margin-top: 6px; border-top: 1px solid var(--border); }
  .dsel-cal-footer a { font-size: var(--fs-sm); }
  .tmsel-panel { width: 130px; padding: 4px; }
  .tmsel-opt { padding: 7px 10px; border-radius: var(--r-xs); font-size: var(--fs-base); cursor: pointer; text-align: center; }
  .tmsel-opt:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }

  /* ---- Выбор МЕСЯЦА/ПЕРИОДА МЕСЯЦЕВ (08.09.2026, mrsel) ----
     Раньше «С месяца»/«По месяц» были двумя нативными <input type="month">
     — по жалобе пользователя: (1) браузерный виджет месяца выглядит чужим
     на фоне остального интерфейса (обычный <select> года/месяца без
     темизации, см. остальные dsel/tsel поля), и (2) чтобы выбрать ОДИН
     месяц (обычный случай) приходилось открывать и выбирать в обоих полях
     по отдельности. Теперь это один тематизированный виджет: клик по
     месяцу в быстром режиме сразу выставляет И начало, И конец периода
     этим месяцем и закрывает панель; режим «Период» (тумблер в подвале)
     включает выбор двух точек (начало/конец) для многомесячных отчётов —
     резервный случай, но не мешает основному. Сами нативные <input
     type="month"> остаются в DOM (просто скрыты) как единственный источник
     истины значения — весь остальной код (loadFinancePanel/loadReport/...)
     продолжает читать их .value и слушать их 'change' без изменений. */
  .mrsel-panel { padding: 8px; width: 244px; max-height: none; overflow: visible; }
  .mrsel-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--sp-1); margin: 2px 0 6px; }
  .mrsel-month { padding: 9px 4px; text-align: center; border-radius: var(--r-xs); cursor: pointer; font-size: var(--fs-sm); }
  .mrsel-month:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
  .mrsel-month.today { font-weight: var(--fw-bold); color: var(--accent); box-shadow: inset 0 0 0 1.5px var(--accent); }
  .mrsel-month.selected { background: var(--accent); color: #fff; font-weight: var(--fw-bold); }
  .mrsel-month.in-range { background: color-mix(in srgb, var(--accent) 18%, transparent); }
  .mrsel-month.pending { box-shadow: inset 0 0 0 1.5px var(--accent); }
  .mrsel-hint { font-size: var(--fs-caption); color: var(--ink-faint); text-align: center; margin-bottom: 4px; }
  .mrsel-footer { display: flex; flex-wrap: wrap; justify-content: space-between; gap: var(--sp-2) 10px; padding-top: 8px; margin-top: 4px; border-top: 1px solid var(--border); }
  .mrsel-footer a { font-size: var(--fs-sm); }
  .mrsel { min-width: 170px; max-width: 220px; }

  /* Строки таблицы «Финансы → Выплата персоналу» были неровными по высоте
     (08.09.2026, по жалобе пользователя — «полосы не ровные»): длинное ФИО
     сотрудника заворачивалось в 2-3 строки, а короткое — в одну, из-за
     этого чередующиеся полосы фона (tbody tr:nth-child(even)) выглядели
     рваными. Ограничиваем имя одной строкой с "…" — полное ФИО остаётся
     доступно во всплывающей подсказке (title) и в открывающемся по клику
     окне «Зарплата». */
  .fin-name-cell { max-width: 190px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .tmsel-opt.selected { background: color-mix(in srgb, var(--accent) 18%, transparent); font-weight: var(--fw-medium); color: var(--accent); }

  .status-pill { display: inline-block; padding: 2px 9px; border-radius: var(--r-pill); font-size: var(--fs-caption); font-weight: var(--fw-medium); }
  .status-pill.not_started { background: #f1ede3; color: #7a6f5c; }
  .status-pill.planned { background: #fdeecb; color: #8a5a12; }
  .status-pill.done { background: var(--money-good-soft); color: var(--money-good-ink); }
  /* Тематизированный виджет статуса пункта плана (startEditPlanItemStatus,
     30.08.2026) — тот же .tsel/.tsel-trigger/.tsel-panel, что и везде в
     приложении, но компактный, в форме пилюли (а не полноразмерного поля
     формы), чтобы визуально не "распухать" внутри строки плана лечения.
     Базовый .tsel задаёт width:100% (рассчитан на поля форм на всю
     ширину) — здесь это переопределяем на auto, иначе пилюля растянулась
     бы на всю доступную ширину строки. */
  .status-pill-tsel { width: auto !important; display: inline-flex !important; vertical-align: middle; }
  .status-pill-tsel .tsel-trigger {
    width: auto; padding: 2px 9px 2px 10px; border-radius: var(--r-pill); font-size: var(--fs-caption); font-weight: var(--fw-medium); gap: var(--sp-2);
  }
  .status-pill-tsel .tsel-trigger .tsel-caret { width: 7px; height: 7px; margin-top: -2px; }
  .status-pill-tsel.open .tsel-trigger .tsel-caret { margin-top: 2px; }
  .status-pill-tsel .tsel-panel { min-width: 150px; }
  .plan-view-item {
    display: flex; gap: var(--sp-3); align-items: flex-start; padding: 10px 0;
    border-bottom: 1px solid var(--border);
  }
  .plan-view-item:last-child { border-bottom: none; }
  .plan-view-num {
    flex: none; width: 26px; height: 26px; border-radius: 50%; background: var(--dark-bg-soft);
    color: var(--dark-text); font-weight: var(--fw-bold); font-size: var(--fs-sm);
    display: flex; align-items: center; justify-content: center; margin-top: 1px;
  }
  .plan-view-body { flex: 1; min-width: 0; }
  .plan-view-title { font-weight: var(--fw-bold); }
  .plan-view-meta { font-size: var(--fs-sm); color: var(--ink-faint); margin-top: 2px; }
  .plan-view-price { flex: none; font-weight: var(--fw-bold); white-space: nowrap; }
  .plan-view-phase { font-weight: var(--fw-bold); margin: 16px 0 4px; color: var(--ink-soft); font-size: var(--fs-sm); text-transform: uppercase; letter-spacing: 0.03em; }
  .plan-view-phase:first-child { margin-top: 0; }

  /* Зубная формула (одонтограмма, v35). Цвета здесь — экранная версия;
     канонический словарь статусов зуба живёт в web/js/clinical-tokens.js
     (зеркало на Python-стороне — app/clinical_tokens.py). Меняете цвет
     статуса — синхронизируйте все три места (25.09.2026). */
  .odo-jaw-label { font-size: var(--fs-micro); font-weight: var(--fw-bold); letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-faint); text-align: center; margin: 4px 0 8px; }
  .odo-quad-row { display: flex; justify-content: center; gap: var(--sp-5); margin-bottom: 6px; }
  .odo-quadrant { display: flex; gap: var(--sp-1); }
  .odo-quadrant.odo-right { flex-direction: row-reverse; }
  .odo-midline { width: 1px; background: var(--border); align-self: stretch; margin: 0 2px; }
  .odo-tooth-slot { display: flex; flex-direction: column; align-items: center; gap: var(--sp-1); width: 34px; }
  .odo-tooth-num-lbl { font-size: var(--fs-micro); color: var(--ink-faint); font-weight: var(--fw-bold); }
  .odo-tooth {
    width: 30px; height: 30px; border-radius: var(--r-xs); border: 2px solid var(--border); background: var(--card);
    cursor: pointer; position: relative; display: flex; align-items: center; justify-content: center;
    transition: transform 0.1s ease, box-shadow 0.15s ease;
  }
  .odo-tooth:hover { transform: translateY(-2px); box-shadow: var(--shadow-sm); }
  .odo-tooth.odo-selected { outline: 2px solid var(--accent-strong); outline-offset: 2px; }
  .odo-tooth.odo-st-caries { background: color-mix(in srgb, var(--money-bad) 22%, var(--card)); border-color: var(--money-bad); }
  .odo-tooth.odo-st-caries::after { content: ''; width: 9px; height: 9px; border-radius: 50%; background: var(--money-bad); }
  .odo-tooth.odo-st-filled { background: color-mix(in srgb, var(--money-info) 20%, var(--card)); border-color: var(--money-info); }
  .odo-tooth.odo-st-filled::after { content: ''; width: 12px; height: 12px; border-radius: var(--r-xs); background: var(--money-info); }
  /* v138 (Астра, шаг 4) — коронка использовала var(--accent)/var(--accent-strong):
     клинический цвет менялся вместе с выбранной клиникой палитрой панели
     (найдено в audit.html, раздел 3). Клинический статус не должен зависеть
     от темы — переведено на фиксированные --tooth-crown/--tooth-crown-ink
     (те же значения, что уже в JS TOOTH_STATUS_COLORS.crown для печати —
     теперь экран и печать совпадают буквально, не только по счастливому
     совпадению, как раньше было в бежевой палитре). */
  .odo-tooth.odo-st-crown { background: color-mix(in srgb, var(--tooth-crown) 30%, var(--card)); border-color: var(--tooth-crown); }
  .odo-tooth.odo-st-crown::after { content: ''; width: 22px; height: 22px; border-radius: var(--r-xs); border: 2px solid var(--tooth-crown); }
  /* v138 — было var(--ink-faint) (меняется по палитре, ранее к тому же
     совпадало со старым дообразцовым значением --ink-faint до фикса v111).
     Теперь фиксированный --tooth-missing — тот же цвет, что и в печатной
     версии (после исправления TOOTH_STATUS_COLORS.missing ниже). */
  .odo-tooth.odo-st-missing { background: var(--tooth-missing); border-color: var(--tooth-missing); border-style: dashed; }
  .odo-tooth.odo-st-missing::after { content: '×'; color: var(--card); font-weight: var(--fw-bold); font-size: var(--fs-md); }
  .odo-tooth.odo-st-implant { background: color-mix(in srgb, var(--tooth-implant) 22%, var(--card)); border-color: var(--tooth-implant); }
  .odo-tooth.odo-st-implant::after { content: ''; width: 4px; height: 18px; background: var(--tooth-implant); border-radius: var(--r-xs); }
  .odo-tooth.odo-st-canal { background: color-mix(in srgb, var(--money-warn) 20%, var(--card)); border-color: var(--money-warn); }
  .odo-tooth.odo-st-canal::after { content: ''; width: 2px; height: 16px; background: var(--money-warn); }
  .odo-tooth.odo-st-extract { background: var(--card); border-color: var(--money-bad); border-style: dashed; }
  .odo-tooth.odo-st-extract::after { content: '!'; color: var(--money-bad-ink); font-weight: var(--fw-bold); font-size: var(--fs-base); }
  .odo-plan-dot { position: absolute; top: -4px; right: -4px; width: 9px; height: 9px; border-radius: 50%; background: var(--money-info); border: 1.5px solid var(--card); }
  .odo-legend { display: flex; flex-wrap: wrap; gap: var(--sp-2) 16px; margin: 18px 0; padding-top: 14px; border-top: 1px solid var(--border); }
  .odo-legend-item { display: flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-caption); color: var(--ink-soft); }
  .odo-swatch { width: 15px; height: 15px; border-radius: var(--r-xs); flex-shrink: 0; }
  .odo-dot-swatch { border-radius: 50%; background: var(--money-info); width: 9px; height: 9px; }
  .odo-tooth-card { background: var(--paper); border: 1px solid var(--border); border-radius: var(--r-sm); padding: 14px 16px; }
  .odo-tooth-head { display: flex; align-items: center; gap: var(--sp-3); }
  .odo-tooth-num { width: 38px; height: 38px; border-radius: var(--r-xs); background: var(--dark-bg); color: var(--accent); display: flex; align-items: center; justify-content: center; font-weight: var(--fw-bold); font-size: var(--fs-md); flex-shrink: 0; }
  .odo-tooth-title { font-weight: var(--fw-bold); font-size: var(--fs-md); }

  /* Карточка пациента — фиксированный размер окна независимо от вкладки.
     Раньше .modal просто обтекал контент (max-height + overflow-y на всей
     модалке) — на вкладках с малым количеством полей окно было ниже, чем
     на вкладках с длинными таблицами/формами, и это выглядело как
     "скачущий" размер при переключении (замечено пользователем 28.08.2026,
     уже чинили горизонтальное переполнение строки плана в v26 — здесь тот
     же принцип, но для высоты и для ВСЕХ вкладок сразу). Решение: сама
     карточка (шапка + вкладки + подвал) — фиксированной высоты и не
     скроллится сама, а скроллится только внутренняя область конкретной
     вкладки (#pc-panels-scroll). */
  #modal-patient-card .modal {
    display: flex; flex-direction: column;
    height: min(720px, 88vh); max-height: none; overflow: hidden;
  }
  #modal-patient-card .modal > h3, #modal-patient-card .modal > #pc-tabs { flex: none; }
  #pc-panels-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding-right: 2px; }
  #pc-panels-scroll .pc-panel:last-child { padding-bottom: 2px; }
  #modal-patient-card .modal > .pc-modal-footer { flex: none; }

  /* Карточка врача (v36) — тот же принцип фиксированной высоты окна, что и
     у карточки пациента выше, плюс собственные компоненты вкладок
     "Ставки по услугам" (массовое применение ставки), "Записи" (поиск +
     пагинация) и "Выполненные работы" (плашки со сводкой за период). */
  #modal-doctor-card .modal {
    display: flex; flex-direction: column;
    height: min(720px, 88vh); max-height: none; overflow: hidden;
  }
  #modal-doctor-card .modal > h3, #modal-doctor-card .modal > #dc-tabs { flex: none; }
  #dc-panels-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding-right: 2px; }
  #modal-doctor-card .modal > .dc-modal-footer { flex: none; }
  button.danger-soft { background: var(--money-bad-soft); color: var(--money-bad-ink); border: 1px solid transparent; }
  button.danger-soft:hover { background: var(--money-bad); color: #fff; }
  .bulk-rate-panel { background: var(--paper); border: 1px solid var(--border); border-radius: var(--r-sm); padding: 14px 16px; margin-bottom: 16px; }
  .bulk-rate-title { font-family: var(--font-heading); font-weight: var(--fw-bold); font-size: var(--fs-md); }
  .bulk-rate-panel .row .field { flex: 1; min-width: 160px; margin-bottom: 0; }
  .rate-empty { color: var(--ink-faint); font-style: italic; }
  .rate-add-row { display: flex; gap: var(--sp-3); align-items: flex-end; margin-top: 14px; padding-top: 14px; border-top: 1px dashed var(--border); flex-wrap: wrap; }
  .rate-add-row .field { flex: 1; min-width: 140px; margin-bottom: 0; }
  .search-row { display: flex; gap: var(--sp-3); margin-bottom: 12px; align-items: flex-end; flex-wrap: wrap; }
  .pager { display: flex; align-items: center; justify-content: center; gap: var(--sp-3); margin-top: 12px; font-size: var(--fs-sm); color: var(--ink-soft); }
  .stat-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: var(--sp-3); margin-bottom: 16px; }
  .stat-tile { background: var(--paper); border: 1px solid var(--border); border-radius: var(--r-sm); padding: 12px 14px; }
  .stat-tile-label { font-size: var(--fs-caption); color: var(--ink-soft); font-weight: var(--fw-bold); text-transform: uppercase; letter-spacing: 0.02em; }
  .stat-tile-value { font-family: var(--font-heading); font-size: var(--fs-lg); font-weight: var(--fw-bold); margin-top: 2px; }
  .stat-tile-value.good { color: var(--money-good-ink); }
  .invoice-status-pill { display: inline-block; padding: 2px 9px; border-radius: var(--r-pill); font-size: var(--fs-caption); font-weight: var(--fw-medium); }
  .invoice-status-pill.paid { background: var(--money-good-soft); color: var(--money-good-ink); }
  .invoice-status-pill.unpaid { background: var(--money-warn-soft); color: var(--money-warn-ink); }
  .invoice-status-pill.underpaid { background: var(--money-bad-soft); color: var(--money-bad-ink); }
  .invoice-status-pill.overpaid { background: var(--money-info-soft); color: var(--money-info-ink); }
  .balance-pill { display: inline-block; padding: 3px 11px; border-radius: var(--r-pill); font-size: var(--fs-sm); font-weight: var(--fw-bold); }
  .balance-pill.positive { background: var(--money-info-soft); color: var(--money-info-ink); }
  .balance-pill.negative { background: var(--money-bad-soft); color: var(--money-bad-ink); }
  .balance-pill.zero { background: var(--dark-bg-soft); color: var(--ink-soft); }
  .balance-pill.small { padding: 2px 8px; font-size: var(--fs-caption); }
  /* Крупный вариант — для карточки пациента (вкладка «Счета»), пользователь
     отметил, что стандартная пилюля там слишком незаметная. */
  .balance-pill.large { display: block; width: fit-content; padding: 10px 20px; border-radius: var(--r-sm); font-size: var(--fs-lg); }

  /* ---- Скидки (v30): оплата визита и план лечения ---- */
  .discount-block {
    border: 1.5px dashed var(--money-warn); background: var(--money-warn-soft);
    border-radius: var(--r-sm); padding: 10px 12px; display: flex; flex-direction: column;
    gap: var(--sp-2); margin: 12px 0;
  }
  .discount-block .discount-top { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); flex-wrap: wrap; }
  .discount-toggle { display: inline-flex; background: var(--card); border: 1px solid var(--money-warn); border-radius: var(--r-xs); padding: 2px; gap: var(--sp-1); }
  .discount-toggle button {
    border: none; background: transparent; font-family: var(--font-heading); font-size: var(--fs-caption);
    font-weight: var(--fw-bold); padding: 5px 11px; border-radius: var(--r-xs); color: var(--money-warn-ink); cursor: pointer;
  }
  .discount-toggle button.active { background: var(--money-warn); color: #fff; }
  .discount-value-input {
    width: 92px; padding: 7px 9px; border-radius: var(--r-xs); border: 1px solid var(--money-warn);
    font-family: var(--font-heading); font-weight: var(--fw-bold); font-size: var(--fs-base); text-align: right; background: var(--card);
  }
  .discount-value-input:disabled { opacity: .5; background: var(--paper); }
  .elig-chip {
    display: inline-flex; align-items: center; gap: var(--sp-1); padding: 4px 10px; border-radius: var(--r-pill);
    font-size: var(--fs-micro); font-weight: var(--fw-bold); letter-spacing: .01em; white-space: nowrap;
    border: 1.5px solid transparent; cursor: pointer; user-select: none;
  }
  .elig-chip.on { background: var(--money-good-soft); color: var(--money-good-ink); border-color: var(--money-good); }
  .elig-chip.off { background: transparent; color: var(--ink-faint); border-color: var(--border); }
  .sum-row { display: flex; justify-content: space-between; gap: var(--sp-3); font-size: var(--fs-base); padding: 2px 0; }
  .sum-row .neg { color: var(--money-bad-ink); font-weight: var(--fw-bold); }
  .summary-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: var(--sp-3); margin-bottom: 16px; }
  .summary-tile { background: var(--card); border: 1px solid var(--border); border-radius: var(--r-sm); padding: 13px 15px; box-shadow: var(--shadow-sm); }
  .summary-tile .label { font-size: var(--fs-caption); color: var(--ink-soft); font-weight: var(--fw-medium); }
  .summary-tile .value { font-size: var(--fs-lg); font-weight: var(--fw-bold); margin-top: 4px; font-family: var(--font-heading); }
  /* v140 (Астра, шаг 6) — плитки вкладки «Обзор» карточки пациента: те же
     .summary-tile/.summary-grid, что и в отчётах, но кликабельные (переход
     на вкладку с подробностями) и с текстовым, не крупно-цифровым значением
     — поэтому .value здесь переопределён на обычный размер шрифта. */
  .ov-tile-link { cursor: pointer; }
  .ov-tile-link:hover { box-shadow: var(--shadow-md); }
  .ov-tile-link .value { font-size: var(--fs-base); font-weight: var(--fw-medium); }

  /* ---------- переключатель цвета панели (раздел «Клиника») ---------- */
  .theme-swatches {
    display: inline-flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap;
    background: var(--paper); border: 1px solid var(--border); border-radius: var(--r-pill);
    padding: 9px 14px; width: fit-content;
  }
  .swatch-btn {
    width: 22px; height: 22px; border-radius: 50%; border: none; cursor: pointer;
    background: var(--sw-color); box-shadow: inset 0 0 0 1px rgba(0,0,0,0.12);
    display: flex; align-items: center; justify-content: center; padding: 0;
    transition: box-shadow 120ms ease;
  }
  .swatch-btn.active { box-shadow: inset 0 0 0 1px rgba(0,0,0,0.12), 0 0 0 2px var(--card), 0 0 0 3.5px var(--sw-color); }
  .swatch-btn svg { display: none; }

  /* ---------- Дневной дашборд записей ---------- */
  /* Фиксированная высота со своей прокруткой — иначе при длинном рабочем дне
     вся страница растягивается и панель управления (дата/врач/+запись)
     уезжает наверх при просмотре записей во второй половине дня. */
  .day-grid { overflow: auto; max-height: 65vh; min-height: 320px; border: 1px solid var(--border); border-radius: var(--r-sm); }
  .day-grid-header { display: grid; position: sticky; top: 0; background: var(--card); z-index: 3; }
  .day-grid-doctor-col-header {
    padding: 8px 6px; font-size: var(--fs-sm); font-weight: var(--fw-bold); text-align: center;
    border-bottom: 2px solid var(--border); border-left: 1px solid var(--border);
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--sp-1);
  }
  .day-grid-doctor-col-header .doc-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
  /* Личное расписание врача (04.09.2026) — мягкая, ненавязчивая подсказка о
     том, что выбранный день не входит в рабочие дни этого врача (по его
     личному расписанию, либо по отгулу/отпуску). Не запрет — колонка
     остаётся кликабельной, запись всё равно можно создать. */
  .doc-off-badge {
    font-size: var(--fs-micro); font-weight: var(--fw-bold); text-transform: uppercase; letter-spacing: 0.02em;
    padding: 1px 7px; border-radius: var(--r-pill); background: var(--money-warn-soft); color: var(--money-warn-ink);
  }
  /* 14.09.2026, по спецификации Астры — подпись «По записи» под именем
     сотрудника в режиме schedule_mode='by_appointment'. Намеренно НЕ пилюля
     и без яркого цвета (её формулировка: "спокойная подпись достаточна;
     отдельный яркий цвет не нужен") — обычный приглушённый текст. */
  .doc-mode-badge { font-size: 11px; font-weight: 600; line-height: 16px; color: var(--ink-soft); }
  .day-grid-col-off { background: rgba(0,0,0,0.02); }
  .day-grid-time-col-header { border-bottom: 2px solid var(--border); }
  .day-grid-body { display: flex; position: relative; }
  .day-grid-time-labels { position: relative; width: 60px; flex-shrink: 0; }
  .time-label { position: absolute; left: 0; right: 6px; text-align: right; font-size: var(--fs-micro); color: var(--ink-faint); transform: translateY(-6px); }
  .day-grid-columns { display: grid; flex: 1; position: relative; }
  .day-grid-col { position: relative; border-left: 1px solid var(--border); cursor: pointer; }
  .day-grid-col:hover { background: rgba(194,138,62,0.05); }
  .hour-line { position: absolute; left: 0; right: 0; border-top: 1px solid var(--border); }
  /* Лёгкая "зебра" по часам (каждый второй час чуть темнее) — помогает не
     терять строку взглядом при переходе через несколько колонок врачей,
     особенно пока сетка пустая (без записей), 03.09.2026. */
  .hour-band { position: absolute; left: 0; right: 0; background: rgba(74,58,38,0.04); pointer-events: none; }
  /* Подсветка целой "строки" часа при наведении — один элемент поверх
     всех колонок сразу (не CSS :hover на колонке, т.к. колонки не делят
     между собой понятие "строки"), синхронизируется через JS по Y
     курсора, см. setupDayGridHover(). */
  .day-grid-row-hover { position: absolute; left: 60px; right: 0; background: rgba(194,138,62,0.16); pointer-events: none; z-index: 1; display: none; }
  .appt-block {
    position: absolute; left: 3px; right: 3px; border-radius: var(--r-xs); padding: 3px 6px;
    font-size: var(--fs-caption); line-height: 1.3; overflow: hidden; cursor: pointer; border: 1px solid rgba(0,0,0,0.08);
  }
  .appt-block-time { font-weight: var(--fw-bold); }
  /* Телефон пациента прямо в блоке календаря (10.09.2026, по просьбе
     пользователя — регистратуре нужно видеть номер без открытия карточки
     записи). Чуть тише по цвету, чтобы не спорить с именем пациента за
     внимание. */
  .appt-block-phone { opacity: 0.8; }
  /* Имя и телефон — всегда в одну строку с "…" вместо обрезания текста
     посередине слова (10.09.2026, по итогам макета "Адаптивная сетка
     календаря"): раньше при overflow:hidden у .appt-block длинное ФИО
     могло перенестись на вторую строку и обрезаться прямо посередине —
     сейчас видно ровно столько текста, сколько влезает, аккуратно. */
  .appt-block-patient, .appt-block-phone { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  /* Адаптивные размеры блока по высоте (короткие визиты — 15/30 минут) и
     по узким колонкам (много врачей на день) — см. applyApptBlockSizing().
     .appt-block-sz-short: высоты не хватает на все 3 строки — прячем
     телефон первым (наименее критичный для быстрого опознавания записи),
     оставляем время+ФИО. .appt-block-sz-tiny: высоты хватает только на
     одну строку — время и сокращённое ФИО в один ряд, телефон и полное
     имя остаются доступны по клику (карточка записи) и по наведению
     (title на самом блоке). */
  .appt-block.appt-block-sz-short .appt-block-phone { display: none; }
  .appt-block.appt-block-sz-tiny { display: flex; align-items: center; gap: 5px; padding: 1px 6px; }
  .appt-block.appt-block-sz-tiny .appt-block-time { flex: 0 0 auto; }
  .appt-block.appt-block-sz-tiny .appt-block-patient { flex: 1 1 auto; min-width: 0; }
  .appt-block.appt-block-sz-tiny .appt-block-phone { display: none; }
  .appt-block.scheduled { background: var(--money-info-soft); color: var(--money-info-ink); }
  .appt-block.completed { background: var(--money-good-soft); color: var(--money-good-ink); }
  .appt-block.cancelled { background: var(--money-bad-soft); color: var(--money-bad-ink); text-decoration: line-through; opacity: 0.75; }
  .appt-block.no_show { background: var(--money-warn-soft); color: var(--money-warn-ink); }
  /* Линия текущего времени (10.09.2026) — сделана полупрозрачной по
     просьбе пользователя: сплошной var(--money-bad) была слишком тёмной
     и визуально "спорила" с записями в календаре; opacity даёт тот же
     акцентный цвет, но мягче для глаз. */
  .now-line { position: absolute; left: 0; right: 0; height: 0; border-top: 2px solid var(--money-bad); z-index: 1; pointer-events: none; opacity: 0.45; }
  .now-line::before {
    content: ''; position: absolute; left: -4px; top: -4px; width: 8px; height: 8px;
    border-radius: 50%; background: var(--money-bad);
  }
/* 26.10.2026: ассистент (nurse) — только просмотр, денег не видит.
   Роль ставит applyRoleUI() в <html data-role>; сервер те же данные закрывает сам. */
html[data-role="nurse"] [data-money],
html[data-role="nurse"] .pt-table td:last-child { display: none !important; }
