/* Компоненты дизайн-системы Pronina school. Правила — ds/DESIGN.md, витрина —
   раздел «Дизайн-система» (/design). Подключается второй таблицей, после
   tokens.css и ДО dashboard.css: у страницы остаётся последнее слово, а
   компонент берёт только смысловые токены, поэтому тема и цвет акцента
   пользователя доходят до него сами.

   Классы без префикса ig-: ds/ не знает о вкладках. Старый класс страницы
   (ig-refresh-btn, ig-comp-remove…) остаётся на элементе рядом с
   компонентом — за него держится JS, а в CSS страницы под ним живут только
   раскладка и показ (display, margin), не вид. */

/* Атрибут hidden браузер прячет правилом слабее любого авторского display,
   а у компонентов display свой — без этой строки скрытая кнопка видна. */
.btn[hidden], .icon-btn[hidden], .chip[hidden], .chips[hidden], .seg[hidden], .seg-btn[hidden],
.check[hidden], .card[hidden], .badge[hidden], .trend[hidden], .note[hidden], .toast[hidden],
.nav-item[hidden], .audio[hidden], .lightbox[hidden], .lightbox-nav[hidden],
.lightbox-count[hidden]{display:none;}

/* ── Иконка ─────────────────────────────────────────────────────────────
   <svg class="icon"><use href="#i-имя"/></svg> — символ из ds/icons.svg
   (спрайт вставлен в <body>). Разметку даёт icon() билдера и dsIcon() из
   ds/ui.js — руками не писать. Цвет — currentColor: иконка берёт цвет
   текста кнопки, наведение и тема доходят сами.
   Размеры: .xs 12 · .sm 14 · .md 16 · по умолчанию 18 · .lg 20 · .xl 24.
   pointer-events:none — клик достаётся кнопке, а не <svg>/<use> внутри неё:
   обработчики вида e.target.id === "dm-lb-prev" иначе промахивались бы. */
.icon{width:var(--icon-base); height:var(--icon-base); flex-shrink:0; pointer-events:none;
      fill:none; stroke:currentColor; stroke-width:1.7; stroke-linecap:round; stroke-linejoin:round;}
.icon.xs{width:var(--icon-xs); height:var(--icon-xs);}
.icon.sm{width:var(--icon-sm); height:var(--icon-sm);}
.icon.md{width:var(--icon-md); height:var(--icon-md);}
.icon.lg{width:var(--icon-lg); height:var(--icon-lg);}
.icon.xl{width:var(--icon-xl); height:var(--icon-xl);}
.icon-sprite{display:none;}

/* ── Кнопка ─────────────────────────────────────────────────────────────
   .btn               вторичная по умолчанию нет — вид задаётся всегда:
   .btn.primary       основное действие экрана: заливка акцентом
   .btn.secondary     обычное действие: рамка, фон карточки
   .btn.ghost         тихое действие без рамки (в строке, в списке)
   .btn.danger        удаление — заливка --destructive (в окне подтверждения)
   .btn.secondary.danger / .btn.ghost.danger
                      удаление в ряду других кнопок: серое, краснеет при наведении
   Размеры: .sm 28 · по умолчанию 32 · .lg 36 (только контент-план).
   Ссылка тоже может быть кнопкой: <a class="btn secondary">. */
.btn{display:inline-flex; align-items:center; justify-content:center; gap:var(--space-6);
     height:var(--control-md); padding:0 var(--space-12); box-sizing:border-box;
     border:1px solid transparent; border-radius:var(--radius-md);
     font:inherit; font-size:var(--text-sm); font-weight:var(--weight-medium); line-height:var(--leading-none);
     white-space:nowrap; text-decoration:none; cursor:pointer; user-select:none;
     transition:background-color var(--dur-fast), border-color var(--dur-fast), color var(--dur-fast);}
.btn:focus-visible{outline:2px solid var(--accent-text); outline-offset:2px;}
.btn:disabled, .btn[aria-disabled="true"]{opacity:.55; cursor:default;}
.btn.sm{height:var(--control-sm); padding:0 var(--space-10);}
.btn.lg{height:var(--control-lg); padding:0 var(--space-16); font-size:var(--text-base);}
.btn svg{flex-shrink:0;}

.btn.primary{background:var(--accent); color:var(--accent-fg); border-color:var(--accent);
             font-weight:var(--weight-semibold);}
/* Наведение — чуть к цвету текста страницы: светлее в тёмной теме, темнее в светлой.
   Смесь в oklab: в oklch серый с оттенком 0 уводил мяту в коричневый. */
.btn.primary:hover:not(:disabled){background:color-mix(in oklab, var(--accent) 88%, var(--fg));
                                  border-color:color-mix(in oklab, var(--accent) 88%, var(--fg));}

.btn.secondary{background:var(--card); color:var(--fg); border-color:var(--input);}
.btn.secondary:hover:not(:disabled){background:var(--muted);}

.btn.ghost{background:transparent; color:var(--muted-fg);}
.btn.ghost:hover:not(:disabled){background:var(--hover); color:var(--fg);}

.btn.danger{background:var(--destructive); color:var(--destructive-fg); border-color:var(--destructive);
            font-weight:var(--weight-semibold);}
.btn.danger:hover:not(:disabled){background:color-mix(in oklab, var(--destructive) 88%, var(--fg));}
.btn.secondary.danger, .btn.ghost.danger{color:var(--muted-fg); font-weight:var(--weight-medium);}
.btn.secondary.danger{background:var(--card); border-color:var(--input);}
.btn.ghost.danger{background:transparent; border-color:transparent;}
.btn.secondary.danger:hover:not(:disabled), .btn.ghost.danger:hover:not(:disabled){
  background:transparent; color:var(--destructive); border-color:var(--destructive);}

/* ── Кнопка-значок ──────────────────────────────────────────────────────
   Квадрат с одной иконкой; подпись — в title и aria-label.
   .icon-btn          рамка, приглушённый значок
   .icon-btn.ghost    без рамки (закрыть панель, крестик)
   .icon-btn.on-media поверх фото и видео: круг на тёмной подложке в любой теме
   Размеры: .sm 28 · по умолчанию 32. */
.icon-btn{display:inline-flex; align-items:center; justify-content:center; flex-shrink:0;
          width:var(--control-md); height:var(--control-md); padding:0; box-sizing:border-box;
          border:1px solid var(--border); border-radius:var(--radius-md);
          background:transparent; color:var(--muted-fg); font:inherit; font-size:var(--text-md);
          line-height:var(--leading-none); cursor:pointer;
          transition:background-color var(--dur-fast), border-color var(--dur-fast), color var(--dur-fast);}
.icon-btn:hover:not(:disabled){color:var(--fg); border-color:var(--input);}
.icon-btn:focus-visible{outline:2px solid var(--accent-text); outline-offset:2px;}
.icon-btn:disabled{opacity:.55; cursor:default;}
.icon-btn.sm{width:var(--control-sm); height:var(--control-sm); border-radius:var(--radius-sm);}
.icon-btn.ghost{border-color:transparent;}
.icon-btn.ghost:hover:not(:disabled){background:var(--hover); border-color:transparent;}
.icon-btn.on-media{border:none; border-radius:var(--radius-circle); background:var(--scrim);
                   color:var(--on-media); font-size:var(--text-xl);}
.icon-btn.on-media:hover:not(:disabled){background:var(--accent); color:var(--accent-fg);}

/* ── Чип ────────────────────────────────────────────────────────────────
   .chip    выбор одного значения в фильтре: период, сортировка, тип, метрика.
            Пилюля в рамке; выбранный — заливка акцентом. Выбранный отмечает JS
            страницы классом .active (или aria-pressed="true").
   .chips   ряд чипов: перенос строк, зазор 6. Ставится рядом со старым классом
            ряда (ig-sort-bar chips) — за старый держится JS.
   Одна высота --chip-h 28 на весь дашборд (утверждено 20.10.2026). */
.chips{display:flex; flex-wrap:wrap; align-items:center; gap:var(--space-6);}
.chip{display:inline-flex; align-items:center; justify-content:center; gap:var(--space-6);
      height:var(--chip-h); padding:0 var(--space-12); box-sizing:border-box;
      border:1px solid var(--border); border-radius:var(--radius-full);
      background:transparent; color:var(--muted-fg);
      font:inherit; font-size:var(--text-sm); font-weight:var(--weight-medium); line-height:var(--leading-none);
      white-space:nowrap; cursor:pointer; user-select:none;
      transition:background-color var(--dur-fast), border-color var(--dur-fast), color var(--dur-fast);}
.chip:hover:not(.active):not([aria-pressed="true"]):not(:disabled){color:var(--fg); border-color:var(--input);}
.chip:focus-visible{outline:2px solid var(--accent-text); outline-offset:2px;}
.chip:disabled{opacity:.55; cursor:default;}
.chip.active, .chip[aria-pressed="true"]{background:var(--accent); color:var(--accent-fg);
      border-color:var(--accent); font-weight:var(--weight-semibold);}

/* ── Сегментный переключатель ───────────────────────────────────────────
   .seg       переключатель ВИДА или настройки, а не фильтр данных: «Карточки /
              Таблица / Скролл», тема, режим показа. Серая дорожка, выбранный
              сегмент — карточка с тенью. Общая высота 28 (--control-sm).
   .seg-btn   сегмент; выбранный — .active или aria-pressed="true"
   .seg-btn.icon  квадратный сегмент с одной иконкой (тема); подпись — в title. */
.seg{display:inline-flex; align-items:center; gap:var(--space-2); padding:var(--space-2);
     width:max-content; max-width:100%; box-sizing:border-box;
     background:var(--muted); border-radius:var(--radius-md);}
.seg-btn{display:inline-flex; align-items:center; justify-content:center; gap:var(--space-6);
         height:calc(var(--control-sm) - 2 * var(--space-2)); padding:0 var(--space-10); box-sizing:border-box;
         border:0; border-radius:var(--radius-sm); background:transparent; color:var(--muted-fg);
         font:inherit; font-size:var(--text-sm); font-weight:var(--weight-medium); line-height:var(--leading-none);
         white-space:nowrap; cursor:pointer; user-select:none;
         transition:background-color var(--dur-fast), color var(--dur-fast);}
.seg-btn:hover{color:var(--fg);}
.seg-btn:focus-visible{outline:2px solid var(--accent-text); outline-offset:1px;}
.seg-btn.active, .seg-btn[aria-pressed="true"]{background:var(--card); color:var(--fg);
         box-shadow:var(--shadow); font-weight:var(--weight-semibold);}
.seg-btn.icon{width:var(--control-sm); padding:0;}
.seg-btn svg{flex-shrink:0;}

/* ── Поле ───────────────────────────────────────────────────────────────
   .field     <input>, <select>, <textarea>: фон страницы, рамка --input,
              в фокусе рамка акцентом. Ширину задаёт раскладка страницы.
   .field.sm  28 — в ряду чипов и маленьких кнопок (даты периода)
   Ошибка — aria-invalid="true" (красная рамка), выключено — disabled.
   На телефоне (≤900px) текст поля 16px (--text-touch): мельче — iPhone
   при фокусе приближает страницу. Высота поля от этого не меняется. */
.field{height:var(--control-md); margin:0; padding:0 var(--space-10); box-sizing:border-box; min-width:0;
       background:var(--bg); color:var(--fg); border:1px solid var(--input); border-radius:var(--radius-md);
       font:inherit; font-size:var(--text-base); line-height:var(--leading-normal);
       transition:border-color var(--dur-fast);}
.field::placeholder{color:var(--muted-fg); opacity:1;}
.field:focus{outline:none; border-color:var(--accent-text);}
.field:disabled{opacity:.55; cursor:default;}
.field[aria-invalid="true"]{border-color:var(--destructive);}
.field.sm{height:var(--control-sm); padding:0 var(--space-8); font-size:var(--text-sm);}
textarea.field{height:auto; min-height:calc(3 * var(--control-md)); padding:var(--space-8) var(--space-10);
               resize:vertical;}
select.field{padding-right:var(--space-6); cursor:pointer;}
.field[type="date"], .field[type="time"]{font-variant-numeric:tabular-nums;}
@media (max-width:900px){ .field, .field.sm{font-size:var(--text-touch);} }

/* ── Галочка ────────────────────────────────────────────────────────────
   <label class="check"><input type="checkbox"><span>Подпись</span></label>
   Системная галочка (и кружок radio) в цвете акцента. Подпись — ОДНИМ
   <span>: у flex-метки каждый кусок текста становится отдельной колонкой.
   Размер текста — от места, где стоит метка. */
.check{display:inline-flex; align-items:center; gap:var(--space-8); cursor:pointer; user-select:none;}
.check > input{width:var(--control-check); height:var(--control-check); margin:0; flex:none;
               accent-color:var(--accent); cursor:pointer;}
.check > input:focus-visible{outline:2px solid var(--accent-text); outline-offset:2px;}
.check:has(> input:disabled){opacity:.55; cursor:default;}

/* ── Окно ───────────────────────────────────────────────────────────────
   <dialog class="dialog">: затемнение, Escape и фокус внутри даёт браузер
   (showModal). Внутри — .dialog-body: .dialog-title, .dialog-text,
   .dialog-actions (кнопки .btn справа). В фокусе при открытии — безопасная
   кнопка (autofocus на «Отмена»).
   .dialog     подтверждение, ширина 340
   .dialog.md  окно с формой, 640 */
.dialog{width:min(var(--dialog-sm), calc(100vw - 2 * var(--space-16)));
        max-height:calc(100dvh - 2 * var(--space-16)); padding:0; overflow:auto;
        background:var(--card); color:var(--fg); border:1px solid var(--border);
        border-radius:var(--radius-lg); box-shadow:var(--shadow-lg);}
.dialog.md{width:min(var(--dialog-md), calc(100vw - 2 * var(--space-16)));}
.dialog::backdrop{background:var(--scrim);}
.dialog-body{display:flex; flex-direction:column; gap:var(--space-12); padding:var(--space-20);}
.dialog-title{display:flex; align-items:center; gap:var(--space-10); margin:0;
              font-family:var(--font-head); font-size:var(--text-md); font-weight:var(--weight-bold);
              line-height:var(--leading-tight); letter-spacing:var(--tracking-tight);}
.dialog-text{margin:0; font-size:var(--text-base); color:var(--muted-fg);}
.dialog-actions{display:flex; justify-content:flex-end; gap:var(--space-6);}

/* .dialog.form — окно с формой (контент-план): .dialog-head (заголовок и
   крестик .icon-btn.ghost), .dialog-body прокручивается, .dialog-foot —
   кнопки. Шапка и подвал остаются на месте. display только у [open]:
   авторский display перебил бы скрытие закрытого <dialog> браузером.
   .dialog.mobile-full — на телефоне (≤900px) во весь экран: узкое окно с
   полями 16 по краям оставляло форме полэкрана. */
.dialog.form[open]{display:flex; flex-direction:column; overflow:hidden;}
.dialog-head{display:flex; align-items:center; gap:var(--space-12); padding:var(--space-16) var(--space-20) var(--space-12);
             border-bottom:1px solid var(--border);}
.dialog-head .dialog-title{flex:1; min-width:0;}
.dialog.form .dialog-body{flex:1; min-height:0; overflow-y:auto;}
.dialog-foot{display:flex; align-items:center; flex-wrap:wrap; gap:var(--space-10);
             padding:var(--space-14) var(--space-20); border-top:1px solid var(--border);}
@media (max-width:900px){
  .dialog.mobile-full{width:100%; max-width:none; height:100%; max-height:none; margin:0;
                      border:none; border-radius:0;}
}

/* ── Тост ───────────────────────────────────────────────────────────────
   Короткое сообщение о результате действия, само пропадает. Вид — .toast
   (.ok — получилось, .error — ошибка; точка слева в цвет статуса), место —
   .toast-region: внизу по центру, поверх окон. Ошибку, которую надо
   прочитать и исправить в форме, показывать у поля, а не тостом. */
.toast-region{position:fixed; left:0; right:0; bottom:var(--space-24); z-index:var(--z-toast);
              display:flex; flex-direction:column; align-items:center; gap:var(--space-8);
              padding:0 var(--space-16); pointer-events:none;}
.toast{display:flex; align-items:center; gap:var(--space-10); max-width:min(var(--dialog-md), 100%);
       padding:var(--space-10) var(--space-14); box-sizing:border-box;
       background:var(--popover); color:var(--fg); border:1px solid var(--border);
       border-radius:var(--radius-md); box-shadow:var(--shadow-lg);
       font-size:var(--text-base); line-height:var(--leading-tight); pointer-events:auto;}
.toast::before{content:""; flex:none; width:var(--space-8); height:var(--space-8);
               border-radius:var(--radius-circle); background:var(--muted-fg);}
.toast.ok::before{background:var(--ok);}
.toast.error::before{background:var(--destructive);}

/* ── Карточка ───────────────────────────────────────────────────────────
   .card       фон карточки, рамка, радиус --radius (10), тень. Только вид:
               отступы и раскладку внутри задаёт страница или компонент
               (.kpi). Публикация, блок разбивки, график, плитка KPI.
   .card.flat  без тени — карточка-панель внутри раздела (список, просмотрщик). */
.card{background:var(--card); color:var(--card-fg); border:1px solid var(--border);
      border-radius:var(--radius); box-shadow:var(--shadow);}
.card.flat{box-shadow:none;}

/* ── Плитка KPI ─────────────────────────────────────────────────────────
   <div class="card kpi"><div class="kpi-head"><div class="label">Подпись</div>
   <span class="trend up">+12 %</span></div><div class="value">14 756</div>
   <div class="kpi-note">пояснение</div></div>
   Имена .label и .value внутри .kpi держит режим показа (privacy.js) — не менять. */
.kpi{padding:var(--space-20);}
.kpi-head{display:flex; align-items:flex-start; justify-content:space-between; gap:var(--space-8);
          margin-bottom:var(--space-10);}
.kpi .label{color:var(--muted-fg); font-size:var(--text-sm);}
.kpi .value{font-family:var(--font-head); font-size:var(--text-3xl); font-weight:var(--weight-semibold);
            letter-spacing:var(--tracking-tight); font-variant-numeric:tabular-nums; color:var(--card-fg);}
.kpi-note{margin-top:var(--space-6); color:var(--muted-fg); font-size:var(--text-sm);}
@media (max-width:600px){
  .kpi{padding:var(--space-16);}
  .kpi .value{font-size:var(--text-2xl);}
}

/* ── Тренд ──────────────────────────────────────────────────────────────
   Изменение за период рядом с цифрой: .trend.up — рост (акцент), .trend.down —
   падение (красный), без класса — «без изменений». */
.trend{display:inline-flex; align-items:center; gap:var(--space-2); flex-shrink:0;
       padding:var(--space-2) var(--space-8); border:1px solid var(--border); border-radius:var(--radius-full);
       font-size:var(--text-xs); font-weight:var(--weight-semibold); line-height:var(--leading-normal);
       white-space:nowrap;}
.trend.up{color:var(--accent-text);}
.trend.down{color:var(--destructive); border-color:color-mix(in oklch, var(--destructive) 35%, transparent);}

/* ── Бейдж ──────────────────────────────────────────────────────────────
   .badge            метка в тексте: вид поста, тип. Серая пилюля 11px
   .badge.ok/.warn/.info/.danger  статус: цвет статуса, заливка им же 15 %
   .badge.on-media   поверх фото и видео: тёмная подложка, белый текст в любой
                     теме (под ней картинка, а не фон страницы)
   .badge.hot        поверх фото — красная («вирусный»)
   .badge.accent     заливка акцентом (место в топе)
   .badge.strong     жирный текст (цифра, на которую смотрят первой)
   Место (position, top/left) задаёт страница. */
.badge{display:inline-flex; align-items:center; gap:var(--space-4); padding:var(--space-2) var(--space-8);
       border-radius:var(--radius-full); background:var(--muted); color:var(--muted-fg);
       font-size:var(--text-xs); font-weight:var(--weight-regular); line-height:var(--leading-normal);
       white-space:nowrap;}
.badge.strong{font-weight:var(--weight-bold);}
.badge.ok{color:var(--ok); background:color-mix(in oklab, var(--ok) 15%, transparent);}
.badge.warn{color:var(--warn); background:color-mix(in oklab, var(--warn) 15%, transparent);}
.badge.info{color:var(--info); background:color-mix(in oklab, var(--info) 15%, transparent);}
.badge.danger{color:var(--destructive); background:color-mix(in oklab, var(--destructive) 15%, transparent);}
/* Рамка 1 + отступ 2 — та же высота, что была у плашек без рамки с отступом 3 */
.badge.on-media{padding:var(--space-2) var(--space-8); background:var(--media-badge); color:var(--on-media);
                border:1px solid var(--media-badge-border);}
.badge.hot{background:var(--destructive); color:var(--destructive-fg); border-color:var(--destructive);}
.badge.accent{background:var(--accent); color:var(--accent-fg);}

/* ── Таблица ────────────────────────────────────────────────────────────
   <table class="table">: 12px, заголовок серым, строки через линию,
   подсветка строки при наведении.
   .table.compact   плотнее (6×8) — таблица внутри карточки
   .num             ячейка с числом: вправо, цифры одной ширины
   Ячейки — через :where(.table): сила селектора как у голого td, и правило
   страницы с одним классом на ячейке (.ig-rd-day-none — серый текст) по-прежнему
   сильнее компонента. С .table td (класс + тег) компонент молча перебивал их. */
.table{width:100%; border-collapse:collapse; font-size:var(--text-sm);}
:where(.table) th{padding:var(--space-8) var(--space-10); border-bottom:1px solid var(--border);
                  text-align:left; color:var(--muted-fg); font-weight:var(--weight-semibold); white-space:nowrap;}
:where(.table) td{padding:var(--space-10); border-bottom:1px solid var(--border); vertical-align:middle; color:var(--fg);}
:where(.table) tr:last-child td{border-bottom:none;}
:where(.table) tbody tr:hover td{background:var(--muted);}
:where(.table.compact) th, :where(.table.compact) td{padding:var(--space-6) var(--space-8);}
:where(.table) .num{text-align:right; font-variant-numeric:tabular-nums;}

/* ── Пусто, загрузка, ошибка ────────────────────────────────────────────
   .note          пояснение или «пусто»: данных нет, раздел не настроен.
                  Серый блок с пунктирной рамкой
   .note.loading  загрузка: <div class="note loading"><span class="spinner"></span>Загружаю…</div>
   .note.error    не получилось: красный текст, красноватая рамка; рядом —
                  что делать («Обновить страницу»)
   .note.ok       получилось — итог отправленной формы, который остаётся на
                  странице после перехода («Роль сохранена»)
   .spinner       значок загрузки, размер — по тексту (1em) */
.note{padding:var(--space-14) var(--space-16); background:var(--muted); border:1px dashed var(--border);
      border-radius:var(--radius-md); color:var(--muted-fg); font-size:var(--text-base);}
.note.loading{display:flex; align-items:center; gap:var(--space-10);}
.note.error{color:var(--destructive); border-color:color-mix(in oklab, var(--destructive) 40%, transparent);}
.note.ok{color:var(--ok); border-color:color-mix(in oklab, var(--ok) 40%, transparent);}
.spinner{display:inline-block; flex:none; width:1em; height:1em; box-sizing:border-box;
         border:2px solid var(--accent-text); border-right-color:transparent; border-radius:var(--radius-circle);
         animation:ds-spin var(--dur-spin) linear infinite;}
@keyframes ds-spin{to{transform:rotate(360deg);}}

/* ── Пункт меню ─────────────────────────────────────────────────────────
   .nav-item          строка списка-навигации: вкладка сайдбара, раздел,
                      аккаунт, конкурент. Без рамки, серый текст; при
                      наведении — серая подложка и бледнее
   .nav-item.active   выбранный: серая подложка, основной текст, жирный
   .nav-item.primary  пункт верхнего уровня: выбранный залит акцентом
   Внутри — значок и подпись (<span>), зазор 10. Ссылка тоже может быть
   пунктом: <a class="nav-item">. Отступы в свёрнутом меню и на телефоне —
   у страницы. */
.nav-item{display:flex; align-items:center; gap:var(--space-10); text-align:left;
          background:transparent; border:none; color:var(--muted-fg); text-decoration:none;
          padding:var(--space-8) var(--space-10); border-radius:var(--radius-md);
          font-size:var(--text-base); cursor:pointer; white-space:nowrap;}
.nav-item.active{background:var(--muted); color:var(--fg); font-weight:var(--weight-semibold);}
.nav-item:hover:not(.active){background:var(--muted); opacity:.6;}
.nav-item.primary.active{background:var(--accent); color:var(--accent-fg);}

/* ── Плеер голосовых ────────────────────────────────────────────────────
   Свой вместо <audio controls>: в узкой колонке (карточка ~330 px, пузырь
   Директа) системный плеер прячет полосу перемотки, а записи до 22 минут.
   Разметка — audio_html() билдера и dsAudio() из ds/ui.js, логика —
   dsAudioInit() там же.
   .audio          ряд: круглая кнопка ▶ и колонка с полосой и временем
   .audio.filled   на подложке — в карточке (Telegram-канал)
   .playing        играет: вместо ▶ пауза (ставит JS); .failed — не загрузилось
   .audio-rate     скорость 1× → 1.25× → 1.5× → 2× */
.audio{display:flex; align-items:center; gap:var(--space-10); min-width:0;}
.audio.filled{padding:var(--space-8) var(--space-10); background:var(--input); border-radius:var(--radius-lg);}
.audio-play{flex:none; width:var(--control-md); height:var(--control-md); padding:0; cursor:pointer;
            display:flex; align-items:center; justify-content:center;
            border:none; border-radius:var(--radius-circle); background:var(--accent); color:var(--accent-fg);}
.audio-play:hover{filter:brightness(1.08);}
.audio-play:focus-visible{outline:2px solid var(--accent-text); outline-offset:2px;}
.audio-ic-pause{display:none;}
.audio.playing .audio-ic-play{display:none;}
.audio.playing .audio-ic-pause{display:block;}
.audio-body{flex:1; min-width:0; display:flex; flex-direction:column; gap:var(--space-4);}
/* Полоса с запасом по высоте и отрицательными отступами: дорожка тонкая,
   но попасть по ней пальцем надо наверняка. */
.audio-bar{position:relative; height:16px; margin:calc(var(--space-4) * -1) 0; cursor:pointer;
           display:flex; align-items:center; touch-action:none;}
.audio-bar::before{content:""; position:absolute; left:0; right:0; height:4px;
                   border-radius:var(--radius-xs); background:var(--border);}
.audio-fill{position:absolute; left:0; height:4px; width:0; border-radius:var(--radius-xs);
            background:var(--accent);}
.audio-knob{position:absolute; left:0; width:10px; height:10px; transform:translateX(-50%);
            border-radius:var(--radius-circle); background:var(--accent); pointer-events:none;}
.audio-bar:focus-visible{outline:2px solid var(--accent-text); outline-offset:3px; border-radius:var(--radius-xs);}
.audio-meta{display:flex; align-items:center; justify-content:space-between; gap:var(--space-8);
            color:var(--muted-fg); font-size:var(--text-xs); font-variant-numeric:tabular-nums;}
.audio-rate{font:inherit; font-size:var(--text-xs); font-variant-numeric:tabular-nums; cursor:pointer;
            background:transparent; border:1px solid var(--border); color:var(--muted-fg);
            border-radius:var(--radius-full); padding:var(--space-px) var(--space-8);}
.audio-rate:hover{color:var(--fg); border-color:var(--input);}
.audio.failed .audio-cur{color:var(--destructive);}

/* ── Просмотрщик фото ───────────────────────────────────────────────────
   Во весь экран поверх страницы: фото по центру, крестик справа вверху,
   стрелки по краям, счётчик внизу. Дашборд строит его в JS (igLightbox),
   Директ — разметкой в main.py; листание у каждого своё.
   .lightbox            затемнение --scrim-strong, фото по центру
   .lightbox-media      фото: вписано в экран
   .lightbox-close      место крестика; сам он — .icon-btn.on-media
   .lightbox-nav.prev / .next  стрелка: высокая полоса под палец на подложке
   .lightbox-count      «3 / 7» пилюлей внизу; одно фото — hidden */
.lightbox{position:fixed; inset:0; z-index:var(--z-lightbox); background:var(--scrim-strong);
          display:flex; align-items:center; justify-content:center; padding:var(--space-48) var(--space-64);}
.lightbox-media{max-width:100%; max-height:calc(100vh - 80px); width:auto; height:auto;
                object-fit:contain; display:block; border-radius:var(--radius-md);}
.lightbox-close{position:absolute; top:var(--space-16); right:var(--space-20);}
.lightbox-nav{position:absolute; top:50%; transform:translateY(-50%); width:46px; height:66px; padding:0;
              display:flex; align-items:center; justify-content:center; cursor:pointer;
              border:none; border-radius:var(--radius-md); background:var(--scrim); color:var(--on-media);}
.lightbox-nav:hover{background:var(--accent); color:var(--accent-fg);}
.lightbox-nav:focus-visible{outline:2px solid var(--on-media); outline-offset:2px;}
.lightbox-nav.prev{left:var(--space-10);}
.lightbox-nav.next{right:var(--space-10);}
.lightbox-count{position:absolute; bottom:var(--space-16); left:50%; transform:translateX(-50%);
                color:var(--on-media); font-size:var(--text-base); background:var(--scrim);
                padding:var(--space-4) var(--space-12); border-radius:var(--radius-full);}
