itube-modern-player 0.8.4 → 0.8.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -30,6 +30,7 @@
30
30
  - [События](#события)
31
31
  - [События: `PlayerEventMap`](#события-playereventmap)
32
32
  - [Vue 3: `<ITubePlayer>`](#vue-3-itubeplayer)
33
+ - [Меню в дровере приложения: `menus: 'external'`](#меню-в-дровере-приложения-menus-external)
33
34
  - [Экспортируемые типы](#экспортируемые-типы)
34
35
  - [Экспортируемые утилиты](#экспортируемые-утилиты)
35
36
  - [Темизация](#темизация)
@@ -377,6 +378,10 @@ new Player('#mount', {
377
378
  playbackRates: [0.5, 0.75, 1, 1.25, 1.5, 2], // пункты меню скорости
378
379
  seekStep: 15, // секунды для стрелок/кнопок перемотки (по умолчанию 15; показывается на кнопке)
379
380
  keyboard: true, // горячие клавиши (на контейнере, не на document!)
381
+ // Кто рендерит меню контрол-бара: 'native' (дефолт) | 'external' | 'external-mobile'.
382
+ // 'external*' — вместо открытия меню плеер эмитит `menurequest`, рендерит приложение
383
+ // (см. раздел «Меню в дровере приложения»).
384
+ menus: 'native',
380
385
  className: 'my-player', // свой класс на контейнер — хук для темизации
381
386
  crossOrigin: 'anonymous', // если VTT/постеры на другом домене
382
387
  playsInline: true,
@@ -390,7 +395,7 @@ new Player('#mount', {
390
395
 
391
396
  // ---- стилизация ----
392
397
  styling: {
393
- themeColor: '#00b3a4', // акцентный цвет (вместо встроенного красного)
398
+ themeColor: '#6366f1', // акцентный цвет; с 0.8.6 дефолт — indigo из макета
394
399
  likeColor: 'forestgreen', // цвет активного лайка (default forestgreen)
395
400
  dislikeColor: '#e53935', // цвет активного дизлайка (не зависит от themeColor)
396
401
  borderRadius: 14, // скругление углов плеера (число → px), '0' — квадратные
@@ -430,22 +435,31 @@ new Player('#mount', {
430
435
  play: true,
431
436
  progress: true,
432
437
  time: true,
438
+ // Громкость (mute + слайдер): true — только десктоп (на мобиле скрыта,
439
+ // там аппаратные кнопки); 'always' — показывать и на мобиле; false — нигде.
433
440
  volume: true,
434
441
  fullscreen: true,
435
- pip: true,
442
+ pip: false, // с 0.8.6 выключена по умолчанию (в макете её нет)
436
443
  // Настройки. Каждая: 'gear' (в едином дропдауне-шестерёнке, по умолчанию) | 'bar'
437
444
  // (отдельной кнопкой в баре) | false (выкл). true = 'gear'. Шестерёнка скрыта,
438
445
  // если внутри нет ни одной доступной опции.
439
446
  speed: 'gear', // скорость воспроизведения (бывш. `settings`; алиас ещё работает)
440
447
  quality: 'gear', // качество; пункт/кнопка появляется при source.qualities или HLS-уровнях
441
448
  subtitles: 'gear', // субтитры; появляется только если у источника есть треки
442
- scenes: true, // кнопка списка сцен (появляется при наличии chapters)
449
+ scenes: false, // кнопка списка сцен; с 0.8.6 выключена по умолчанию
443
450
  sceneTypes: true, // контрол выбора типа сцен (появляется при source.sceneGroups)
444
- heatmap: true, // диаграмма популярности (нужны данные source.heatmap)
451
+ // Хитмап популярности (нужны данные source.heatmap): true — только десктоп
452
+ // (на мобиле скрыт); 'always' — и на мобиле; false — нигде.
453
+ heatmap: true,
445
454
  seekButtons: true, // или { back: 5, forward: 15, label: (sec, dir) => `${dir==='back'?'−':'+'}${sec}s` }
446
- // порядок правых контролов слева направо; пусто/нет встроенный порядок.
447
- // перечисленные идут первыми, остальные сохраняют дефолтную позицию, всегда последняя.
448
- order: ['gear', 'pip', 'fullscreen'], // ControlBarItem[]: like|dislike|speed|quality|subtitles|gear|scenes|sceneTypes|playlist|pip|fullscreen|`custom:<id>`
455
+ // Меню «⋯» (экшены + динамический overflow-коллапс узкого бара).
456
+ // С 0.8.6 по умолчанию ВЫКЛЮЧЕНО макете «⋯» нет): контролы не
457
+ // сворачиваются, actions без placement: 'bar' не отображаются.
458
+ // true — вернуть «⋯» и overflow-коллапс.
459
+ more: false,
460
+ // порядок правых контролов слева направо; перечисленные идут первыми,
461
+ // остальные сохраняют встроенную позицию, ⋯ (если включён) всегда последняя.
462
+ order: ['sceneTypes', 'gear', 'fullscreen'], // дефолт с 0.8.6 (макетный порядок); ControlBarItem[]: like|dislike|speed|quality|subtitles|gear|scenes|sceneTypes|playlist|pip|fullscreen|`custom:<id>`
449
463
  seekPlacement: 'overlay', // 'overlay' (default) — ±N и play поверх видео на всех экранах
450
464
  // | 'bar' — seek-кнопки в нижнем баре (поведение до 0.3)
451
465
  mobileLayout: 'bar', // мобильный (≤767px) лейаут:
@@ -456,6 +470,7 @@ new Player('#mount', {
456
470
  playlist: true, // prev/next/список — рендерятся ТОЛЬКО в режиме плейлиста
457
471
  hidePrev: true, // скрыть кнопку «prev» в баре (бар = только next); false — вернуть.
458
472
  // При mobileLayout: 'center' центр-кластер всё равно показывает prev
473
+ playlistButton: false, // кнопка списка плейлиста; с 0.8.6 выключена по умолчанию
459
474
  nextPreview: true, // ховер-превью следующего ролика над кнопкой next (десктоп).
460
475
  // объект: { thumbnail?, title?, duration?, meta? } — какие поля показывать
461
476
  // (иконки чанков задаются в самих source.previewMeta — см. { text, icon })
@@ -698,6 +713,7 @@ player.off('timeupdate', fn)
698
713
  | `relatedclick` | `{ item }` | Клик по related-карточке |
699
714
  | `action` | `{ id }` | Кнопки: `like`, `dislike`, `addTo`, `share` (если нет нативного шеринга), `report` |
700
715
  | `customaction` | `{ id }` | Ваша кнопка из `actions.custom` |
716
+ | `menurequest` | `MenuRequestPayload` | Нажата кнопка меню при `menus: 'external' \| 'external-mobile'` — приложение рендерит меню само ([раздел «Меню в дровере приложения»](#меню-в-дровере-приложения-menus-external)) |
701
717
  | `adstart` / `adend` / `adskip` / `adclick` | `{ ad: ResolvedAd }` | Жизненный цикл рекламы |
702
718
  | `adpause` / `adresume` | `{ ad: ResolvedAd }` | Креатив поставлен на паузу / возобновлён (+ VAST-пиксели pause/resume) |
703
719
  | `aderror` | `{ roll, error }` | Ролл не отыграл (контент продолжается автоматически) |
@@ -725,6 +741,149 @@ player.off('timeupdate', fn)
725
741
 
726
742
  CSS компонент подгружает сам (self-reference `itube-modern-player/style.css` через exports пакета); ручной импорт `style.css` в приложении продолжает работать и дедуплицируется бандлером — это фолбэк для экзотических сборок.
727
743
 
744
+ ## Меню в дровере приложения: `menus: 'external'`
745
+
746
+ У контрол-бара три меню: **настройки** (шестерёнка: автоплей / скорость / качество / субтитры), **тайм-коды** (типы сцен с аккордеоном) и **⋯** (переполнившиеся контролы + ваши экшены). Кто их рисует — решает опция `menus`:
747
+
748
+ | Значение | Поведение |
749
+ | --- | --- |
750
+ | `'native'` (по умолчанию) | Плеер рисует сам. Десктоп — попап у кнопки; мобила — дровер **снизу экрана**: открытое меню вместе с затемнением переезжает в фиксированный слой на `<body>` (порталится оно потому, что сам плеер обрезает fixed-потомков), страница под ним затемняется и не скроллится. Поверх шапки сайта дровер поднимает `z-index: var(--imp-sheet-z, 999)` — при конфликте задайте `--imp-sheet-z` на `:root` |
751
+ | `'external'` | Плеер **никогда** не открывает меню — по нажатию любой меню-кнопки эмитит `menurequest`, рендер за вами |
752
+ | `'external-mobile'` | Гибрид для приложений со своим дровером: десктоп — нативные попапы, мобильный вьюпорт (≤767px) — `menurequest` |
753
+
754
+ ### Контракт `menurequest`
755
+
756
+ Один payload — всё, что нужно для рендера, плюс колбэки, которые **сами применяют выбор** (плеер перемотает/переключит и отправит свои обычные события — ничего дёргать вручную не надо):
757
+
758
+ ```ts
759
+ type MenuRequestPayload =
760
+ // Шестерёнка. Строки трёх видов: тогл (toggle), дрилл в опции (options+select), просто значение.
761
+ | { kind: 'settings'; title: string; entries: ExternalSettingsEntry[] }
762
+ // Тайм-коды: группы сцен; active — текущий тип (в макете он раскрыт).
763
+ | { kind: 'sceneTypes'; title: string; groups: ExternalSceneGroup[];
764
+ activate(groupId: string): void // раскрыли группу = сделали её активным типом
765
+ select(groupId: string, start: number): void } // тап по сцене: активирует группу, seek + play
766
+ // Всё остальное (⋯, отдельные кнопки скорости/качества/субтитров, если включены).
767
+ | { kind: 'menu'; title: string; sections: ExternalMenuSection[] }
768
+
769
+ interface ExternalSettingsEntry {
770
+ key: string // 'autoplay' | 'speed' | 'quality' | 'subtitles'
771
+ label: string // локализованная подпись
772
+ value?: string // текущее значение для дрилл-строки («720p», «Обычная»)
773
+ toggle?: { value: boolean; set(on: boolean): void }
774
+ options?: { label: string; value: string; active: boolean }[]
775
+ select?(value: string): void
776
+ }
777
+
778
+ interface ExternalSceneGroup {
779
+ id: string; title: string; icon?: IconSource
780
+ active: boolean // текущий тип сцен — подсветите/раскройте его
781
+ scenes: { label: string; time: string; start: number; current: boolean }[]
782
+ } // current — сцена, играющая сейчас (подсветка в макете)
783
+
784
+ interface ExternalMenuSection {
785
+ title: string
786
+ items: { label: string; value: string; active: boolean }[]
787
+ select(value: string): void
788
+ }
789
+ ```
790
+
791
+ Три правила, чтобы не отстрелить ногу:
792
+
793
+ 1. **Payload — снимок на момент нажатия.** Колбэки живые (замыкания на плеер), но `active`/`current`/`value` не обновляются сами. После `select`/`toggle` либо закрывайте дровер (обычный UX), либо обновите свою копию модели — по событиям плеера `ratechange`, `qualitychange`, `subtitlechange`, `scenetypechange`, `chapterchange`.
794
+ 2. **Не дублируйте работу колбэков.** `select` у `sceneTypes` уже делает `setSceneGroup + seek + play` — не вызывайте `player.seek()` сами.
795
+ 3. `menurequest` эмитится **на каждое нажатие** кнопки — если ваш дровер уже открыт, просто перерисуйте контент.
796
+
797
+ ### Интеграция в Nuxt 3 (со своим дровером)
798
+
799
+ Пусть в приложении уже есть `<AppDrawer v-model="open" :title="...">` (любой ваш bottom-sheet). Вся интеграция — один обработчик и один рендер по `kind`:
800
+
801
+ ```vue
802
+ <script setup lang="ts">
803
+ import type { MenuRequestPayload } from 'itube-modern-player'
804
+
805
+ const video = { src: '...', sceneGroups: [/* ... */] }
806
+
807
+ const drawerOpen = ref(false)
808
+ const menu = shallowRef<MenuRequestPayload | null>(null)
809
+ // стек для дрилла «Настройки → Скорость» с кнопкой «назад»
810
+ const drill = shallowRef<{ label: string; options: { label: string; value: string; active: boolean }[]; select(v: string): void } | null>(null)
811
+
812
+ function onMenuRequest(req: MenuRequestPayload) {
813
+ menu.value = req
814
+ drill.value = null
815
+ drawerOpen.value = true
816
+ }
817
+
818
+ function pick(select: (v: string) => void, value: string) {
819
+ select(value) // плеер применит сам и отправит свои события
820
+ drawerOpen.value = false
821
+ }
822
+ </script>
823
+
824
+ <template>
825
+ <ITubePlayer
826
+ lazy
827
+ :source="video"
828
+ :options="{ menus: 'external-mobile' }"
829
+ @menurequest="onMenuRequest"
830
+ />
831
+
832
+ <AppDrawer v-model="drawerOpen" :title="drill?.label ?? menu?.title">
833
+ <!-- дрилл: список опций одной настройки -->
834
+ <template v-if="drill">
835
+ <button v-for="o in drill.options" :key="o.value"
836
+ :class="{ active: o.active }"
837
+ @click="pick(drill.select, o.value)">
838
+ {{ o.label }}
839
+ </button>
840
+ </template>
841
+
842
+ <!-- корень шестерёнки -->
843
+ <template v-else-if="menu?.kind === 'settings'">
844
+ <template v-for="e in menu.entries" :key="e.key">
845
+ <label v-if="e.toggle">
846
+ {{ e.label }}
847
+ <AppSwitch :model-value="e.toggle.value" @update:model-value="e.toggle.set($event)" />
848
+ </label>
849
+ <button v-else @click="drill = { label: e.label, options: e.options!, select: e.select! }">
850
+ {{ e.label }} <span class="value">{{ e.value }}</span>
851
+ </button>
852
+ </template>
853
+ </template>
854
+
855
+ <!-- тайм-коды: аккордеон типов сцен -->
856
+ <template v-else-if="menu?.kind === 'sceneTypes'">
857
+ <details v-for="g in menu.groups" :key="g.id" :open="g.active"
858
+ @toggle="(e: any) => e.target.open && !g.active && menu!.activate(g.id)">
859
+ <summary>{{ g.title }}</summary>
860
+ <button v-for="s in g.scenes" :key="s.start"
861
+ :class="{ current: s.current }"
862
+ @click="menu!.select(g.id, s.start); drawerOpen = false">
863
+ {{ s.label }} <span class="time">{{ s.time }}</span>
864
+ </button>
865
+ </details>
866
+ </template>
867
+
868
+ <!-- ⋯ и одиночные меню -->
869
+ <template v-else-if="menu?.kind === 'menu'">
870
+ <section v-for="(s, i) in menu.sections" :key="i">
871
+ <h3 v-if="s.title">{{ s.title }}</h3>
872
+ <button v-for="it in s.items" :key="it.value"
873
+ :class="{ active: it.active }"
874
+ @click="pick(s.select, it.value)">
875
+ {{ it.label }}
876
+ </button>
877
+ </section>
878
+ </template>
879
+ </AppDrawer>
880
+ </template>
881
+ ```
882
+
883
+ Дизайн строк под макет (те же значения, что в нативном дровере): строки настроек — 52px / 15px, разделители `rgba(39,39,42,.6)`; опции — 48px, активная `rgba(255,255,255,.10)` + белый бар 2px слева во всю высоту; шапка — 18px/700 uppercase на `#0d0d0d`.
884
+
885
+ Ванилла-версия без Vue — то же самое через `player.on('menurequest', req => ...)`.
886
+
728
887
  ## Изоморфный шаблон заглушки: `itube-modern-player/placeholder`
729
888
 
730
889
  `renderPlaceholder(options, flags?) → string` — чистая строка без DOM-зависимостей, единый источник правды по разметке «постер + кнопка play». Используется внутри `createLazyPlayer` и Vue-`lazy`; экспортируется для SSR любого фреймворка:
@@ -890,6 +1049,55 @@ npm publish # prepublishOnly прогонит typecheck + build
890
1049
 
891
1050
  Версионирование по [SemVer](https://semver.org/lang/ru/): `major.minor.patch`.
892
1051
 
1052
+ ### 0.8.6
1053
+
1054
+ **Макетный вид — дефолт «из коробки».** Пак настроек с дев-стенда стал значениями по умолчанию; интеграции, которым нужно прежнее поведение, включают его явно:
1055
+
1056
+ - `styling.themeColor` по умолчанию — **`#6366f1`** (indigo из макета; был красный `#e53935`).
1057
+ - `controls` по умолчанию: **`pip: false`**, **`scenes: false`** (кнопка списка сцен), **`playlistButton: false`**, **`more: false`** (без «⋯» и overflow-коллапса; `actions` без `placement: 'bar'` при этом не отображаются — включите `more: true`, если пользуетесь share/report из меню), **`order: ['sceneTypes', 'gear', 'fullscreen']`**.
1058
+ - Бар «из коробки»: play · next · громкость · время | тип сцен · шестерёнка · фуллскрин — ровно референс.
1059
+ - ⚠️ Поведенческие флипы дефолтов: если интеграция полагалась на PiP / «⋯» / кнопки сцен и плейлиста — верните их соответствующей опцией (`pip: true` и т.д.). `borderRadius` не менялся (8px уже был дефолтом).
1060
+
1061
+ ### 0.8.5
1062
+
1063
+ Дизайн-проход по макетам. Записи хронологические — макет уточнялся по ходу, поэтому при расхождении цифр (например, высота блока контролов 102 → 110px, зазоры 8 → 12px) верны **поздние** пункты; финальные значения также отражены в блоке опций выше.
1064
+
1065
+ - **Центр-кластер по макету**: play — тёмный круг 80px (accent-заливка убрана из дефолта; `playButtonStyle: 'inverted'` работает как раньше), кнопки ±N — тёмные круги 52px, **число шага внутри кольца** (голое «15» вместо подписи «−15с» под иконкой; кастомный `seekButtons.label` уважается).
1066
+ - **Контрол-бар**: иконка play 24px, остальные кнопки 20px (макетное соотношение); добавлен зазор между прогресс-баром и рядом кнопок.
1067
+ - **Затенение под контролами**: градиент выше и плотнее (`transparent → rgba(0,0,0,.85)`, старт с 48px) — бар читается на светлых кадрах.
1068
+ - **Хитмап по макету**: кривая с явным отступом над полосой прогресса (bottom 18px, высота 22px), профиль с высокой базовой линией (`FLOOR` 0.08 → 0.35) — толстая мягкая лента с холмами вместо острых пиков.
1069
+ - **Превью момента**: кадр на 15% меньше (`scale 0.85` от нативного спрайт-региона), полоска с названием сцены и временем — **под** кадром, а не поверх него; карточка с тонкой рамкой-инсетом.
1070
+ - **Меню таймкодов — аккордеон**: типы сцен как раскрывающиеся заголовки (иконка + название + шеврон), раскрытый тип показывает список сцен со временем начала; текущая сцена подсвечена (белый + accent-время), раскрытие типа делает его активным. Клик по сцене — переход + воспроизведение.
1071
+ - **Ползунок → подсветка позиции**: белая точка на прогресс-баре следует за курсором/пальцем (позиция наведения), а не за воспроизведением; ловится и по полосе, и по зоне хитмапа (хитмап теперь часть hit-области перемотки).
1072
+ - **Пиксель-перфект контрол-бара (десктоп)**: ряд кнопок 36px, отступы блока 32/16/10 — общая высота с хитмапом ровно 102px; хитмап ниже (лента 16px, зазор 16px).
1073
+ - **Превью момента**: кадр без полей — на всю ширину карточки; карточка поднята над хитмапом (не перекрывает его).
1074
+ - **Иконки**: play/next/prev со скруглёнными углами, pause с более круглыми плашками; новая иконка громкости (скруглённый динамик + дуги с round-cap).
1075
+ - **Меню настроек (шестерёнка) по макету**: карточка 224px, радиус 12px, тонкая обводка + глубокая тень, без внутреннего паддинга; строки 14px c ховером во всю ширину; свитч 40×24. Подменю (скорость/качество): заголовок «‹ Название», опции по центру, блеклый ховер, активная — светлая полоса + белая палочка у правого края.
1076
+ - **Шестерёнка** слегка проворачивается по часовой (35°) пока меню открыто.
1077
+ - **Прогресс-бар**: высота всегда 4px — по ховеру не утолщается, а подсвечивается мягким белым свечением.
1078
+ - **Синхронизация с дизайн-референсом**: кнопки бара подсвечиваются цветом (серый → белый, play → accent) вместо фоновой плашки; зазоры в ряду 8px; время 12px/500; градиент подложки `transparent → rgba(0,0,0,.7)`; центр-кластер — кольца 48px с зазорами 24px и полупрозрачными чёрными фонами; превью следующего видео 320px с pop-анимацией (fade + scale).
1079
+ - **Мобильные меню — bottom-sheet по референсу**: шапка на тёмной подложке `#0d0d0d` с центрированным заголовком (18px, uppercase) и кнопкой ✕, в подменю — стрелка «назад» в шапке; карточка `#18181b` со скруглённым верхом 16px и линией-гранью; тап-строки 52px с разделителями, опции 48px (активная — светлая полоса + 2px бар слева во всю высоту), свитчи 44×24; затемнение подложки до 70%.
1080
+ - Исправлен `labels.settings` во всех локалях: легаси «Скорость воспроизведения» → «Настройки» (шестерёнка давно открывает общее меню настроек).
1081
+ - **Мобильный дровер на уровне экрана**: открытое меню (+ затемнение) порталится из плеера в фиксированный слой на `<body>` — дровер выезжает из низа экрана и затемняет всю страницу, как в макете; скролл страницы блокируется, тема (accent и пр.) переносится на портал автоматически; z-index настраивается через `--imp-sheet-z`; в фуллскрине хостом становится fullscreen-элемент.
1082
+ - **`menus: 'external' | 'external-mobile'` + событие `menurequest`**: режим, в котором меню рендерит приложение (свой дровер в Nuxt и т.п.) — плеер отдаёт типизированную модель (настройки/тайм-коды/⋯) с колбэками, применяющими выбор. Полный контракт и Nuxt-пример — в разделе README «Меню в дровере приложения».
1083
+ - **Текущая сцена подсвечивается во всех типах**: позиция воспроизведения попадает в сцену каждой группы — в меню тайм-кодов (и в `menurequest`-модели) подсветка теперь считается по позиции для каждой группы, а не только для активной.
1084
+ - **Шрифты по макету**: заголовки аккордеона и подпись кнопки типа сцен 14px/500 (были 13/600), активная сцена — 500, ширина аккордеона 256px, время в тултипе превью 12px.
1085
+ - **Превью сика поверх всего**: карточка превью получила `z-index: 9` — перекрывает центр-кластер и остальные оверлеи (раньше кнопки центра рисовались поверх неё).
1086
+ - **Полоса прогресса не утолщается и на мобиле**: убран остаток «трек 10px под пальцем» — состояние скраба показывает то же белое свечение; тач-зона и увеличение точки-ползунка сохранены.
1087
+ - **Хитмап и громкость на мобиле скрыты по умолчанию**: `controls.heatmap` / `controls.volume` теперь `boolean | 'always'` — `true` (дефолт) = только десктоп, `'always'` = принудительно и на мобильном вьюпорте, `false` = нигде.
1088
+ - **Ховер-свечение прогресс-бара приглушено** до макетного (blur 3px / 35% вместо 8px / 45%).
1089
+ - **Постер-стейт = рабочий плеер**: до первого запуска бар сидируется полностью — время «0:00 / длительность» (из `source.duration` или метаданных), лейбл стартовой главы, интерактивный прогресс с главами/хитмапом/превью; кнопка play на постере теперь идентична центральной кнопке рабочего плеера (тёмный круг 80px; `playButtonStyle: 'inverted'` работает как раньше).
1090
+ - **Мобильная версия заново сверена с макетом**: шестерёнка — плавающая круглая кнопка 36px в правом верхнем углу видео (из бара убрана, аватар канала сдвигается левее, в ⋯ не коллапсится); кнопки бара 36px и зазоры 8px как на десктопе (компакт-уменьшение убрано); центр-кластер держит десктопные размеры (play 80 / кольца 48 / зазор 24) и центрируется в зоне НАД контрол-баром (десктоп −64px, мобила −40px снизу); подпись у кнопки типа сцен снова видна (паддинги 8px, иконка 16px); время 12px; отступы низа 32/16/12.
1091
+ - **Иконка шестерёнки** заменена на макетную — контурная с округлыми лепестками и кружком в центре (lucide settings); поворот при открытии меню работает как прежде.
1092
+ - **`controls.more: false`** — полное отключение меню «⋯» (как в макете): кнопка не рендерится, overflow-коллапс выключен, контролы никогда не сворачиваются. Дефолт — `true` (прежнее поведение).
1093
+ - Фикс: на мобиле кнопка настроек «прыгала» вниз к бару при показе контролов до старта — режим peek делал низ оверлея positioned, и абсолютная шестерёнка начинала позиционироваться от бара. Подъём над постером теперь через z-index grid-элемента без `position` (containing block не меняется), а центр-кластер до старта остаётся скрыт за постером — без задвоения кнопки play.
1094
+ - **Синхронизация с обновлённым макетом (июльская ревизия)**: кнопки контрол-бара 44×44 (иконки прежние 24/20), зазоры ряда 12px, общая высота блока 110px; иконки центр-кластера укрупнены до 36px (play на мобиле 64px с 28px-иконкой, постер-кнопка зеркалит); мобильная шестерёнка 40×40; градиент подложки по новым стопам `0 → .35@35% → .7@70% → .92`; у времени лёгкая тень для светлых кадров.
1095
+ - **Мобильный бар справа как в макете**: тип сцен — квадратная 44px кнопка только с иконкой (20px, подпись скрыта на мобиле), рядом фуллскрин. Заодно это вернуло фуллскрин на узких экранах — широкая кнопка с текстом выталкивала его за край плеера.
1096
+ - **Постерная кнопка play центрируется как кластерная** — в зоне над контрол-баром (−64px десктоп / −40px мобила), а не по всему плееру: при раскрытии контролов кнопка больше не «переезжает».
1097
+ - **Мобила: контрол-бар виден в заглушке сразу** — при показе постера бар раскрывается автоматически (без тапа) и не автоскрывается до первого запуска; на десктопе прежнее поведение (появление по ховеру).
1098
+ - Фикс «клика насквозь» у дровера: тап по оверлею над кнопкой (например, шестерёнкой) закрывал дровер, но клик проваливался в кнопку под ним и срабатывал (меню тут же переоткрывалось). Бэкдроп теперь закрывается по `click`, а не `pointerdown`, и поглощает касание целиком.
1099
+ - Фикс collapse-логики ⋯: замер переполнения больше не учитывает сам ⋯, оставшийся с прошлого прохода — уходит цикл «спрятали кнопку ради ⋯, который жив только из-за спрятанной кнопки».
1100
+
893
1101
  ### 0.8.4
894
1102
 
895
1103
  - **Прогресс на тепловой карте.** Кривая хитмапа больше не одноцветная: проигранная часть (до плейхеда) подсвечивается ярче (`rgba(255,255,255,.75)` против базовых `.35`; кастомизируется по классу `imp-progress__heatmap-played`). Заполнение следует и за воспроизведением, и за скрабом.