diff --git a/VERSION b/VERSION index ea34848..e625394 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.153 +0.0.154 diff --git a/frontend/mobile.html b/frontend/mobile.html index 6638260..480554c 100644 --- a/frontend/mobile.html +++ b/frontend/mobile.html @@ -22,33 +22,23 @@ - +
-
- -
Нет камер
-
-
- -
+
- + - diff --git a/frontend/static/mobile.css b/frontend/static/mobile.css index a13de06..72d64aa 100644 --- a/frontend/static/mobile.css +++ b/frontend/static/mobile.css @@ -1,4 +1,4 @@ -/* CoRE.Vizion+ — мобильный dashboard (отдельный порт). Тёмная тема, нижние вкладки. */ +/* CoRE.Vizion+ — мобильный dashboard (отдельный порт). Тёмная тема, бутерброд-меню, фирменный плеер. */ * { box-sizing: border-box; -webkit-tap-highlight-color: transparent; } html, body { margin: 0; height: 100%; background: #0f1115; color: #e6e9ef; @@ -7,21 +7,19 @@ html, body { } .m-app { display: flex; flex-direction: column; height: 100%; } +/* ── шапка с бутербродом ── */ .m-head { height: 48px; flex: none; display: flex; align-items: center; gap: 10px; padding: 0 8px; background: #161a22; border-bottom: 1px solid #272d3a; position: relative; z-index: 30; } -.m-burger { - background: none; border: 0; color: #e6e9ef; font-size: 22px; line-height: 1; - padding: 6px 10px; border-radius: 8px; cursor: pointer; -} +.m-burger { background: none; border: 0; color: #e6e9ef; font-size: 22px; line-height: 1; padding: 6px 10px; border-radius: 8px; cursor: pointer; } .m-burger:active { background: #1d2230; } .m-title { font-weight: 600; font-size: 16px; letter-spacing: .3px; } .m-brand { font-weight: 700; font-size: 17px; letter-spacing: .3px; } .m-brand span { color: #c0392b; } -/* бутерброд-меню: боковая шторка + затемнение */ +/* ── бутерброд-меню: боковая шторка + затемнение ── */ .m-scrim { position: fixed; inset: 0; background: rgba(0,0,0,.5); z-index: 40; } .m-scrim[hidden] { display: none; } .m-drawer { @@ -31,10 +29,7 @@ html, body { transform: translateX(-100%); transition: transform .22s ease; } .m-drawer.open { transform: translateX(0); } -.m-drawer-head { - height: 56px; flex: none; display: flex; align-items: center; padding: 0 18px; - border-bottom: 1px solid #272d3a; -} +.m-drawer-head { height: 56px; flex: none; display: flex; align-items: center; padding: 0 18px; border-bottom: 1px solid #272d3a; } .m-nav { display: flex; align-items: center; gap: 14px; width: 100%; text-align: left; background: none; border: 0; border-left: 3px solid transparent; color: #cdd3e0; @@ -44,28 +39,24 @@ html, body { .m-nav:active { background: #1d2230; } .m-nav.active { color: #fff; background: #1d2230; border-left-color: #c0392b; } +/* ── режимы ── */ .m-view { flex: 1; min-height: 0; display: flex; flex-direction: column; overflow: hidden; } -/* hidden-атрибут должен реально скрывать режим: иначе display:flex (та же специфичность, - но идёт позже) перебивает [hidden]{display:none} и оба режима видны разом */ -.m-view[hidden] { display: none; } +.m-view[hidden] { display: none; } /* hidden должен реально скрывать (иначе display:flex перебивает) */ -.m-bar { - flex: none; display: flex; gap: 8px; padding: 8px; - background: #161a22; border-bottom: 1px solid #272d3a; -} +.m-bar { flex: none; display: flex; gap: 8px; padding: 8px; background: #161a22; border-bottom: 1px solid #272d3a; } .m-sel, .m-date { flex: 1; min-width: 0; background: #1d2230; color: #e6e9ef; border: 1px solid #272d3a; border-radius: 8px; padding: 10px 12px; font-size: 15px; -webkit-appearance: none; appearance: none; } .m-date { flex: 0 0 auto; } -.m-player { position: relative; background: #000; flex: none; } -.m-player video { width: 100%; aspect-ratio: 16 / 9; display: block; background: #000; object-fit: contain; } -.m-hint { - position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; - color: #8b93a7; font-size: 14px; pointer-events: none; text-align: center; padding: 0 16px; -} +/* сцена плеера: live — на всё доступное место, архив — 16:9 (ниже список записей) */ +.m-stage { background: #000; position: relative; min-height: 0; } +#view-live .m-stage { flex: 1; } +#view-arch .m-stage { flex: none; aspect-ratio: 16 / 9; } +.player-wrap { width: 100%; height: 100%; } +/* список записей архива */ .m-seglist { flex: 1; min-height: 0; overflow-y: auto; padding: 8px; -webkit-overflow-scrolling: touch; } .m-seg { display: flex; align-items: center; justify-content: space-between; width: 100%; text-align: left; @@ -78,16 +69,38 @@ html, body { .m-seg.active .m-dur { color: #fff; } .m-empty { color: #8b93a7; text-align: center; padding: 28px 16px; font-size: 14px; } -/* live: видео по центру + панель только с пауза/воспроизведение */ -#view-live .m-player { flex: 1; display: flex; align-items: center; justify-content: center; } -.m-ctrl { - flex: none; display: flex; align-items: center; justify-content: center; - background: #161a22; border-top: 1px solid #272d3a; - padding: 12px; padding-bottom: calc(12px + env(safe-area-inset-bottom, 0)); +/* ── фирменный плеер (.pjs) — единый для live и архива ── */ +.player-wrap .pjs { position: relative; width: 100%; height: 100%; background: #000; outline: none; overflow: hidden; } +.pjs-video { width: 100%; height: 100%; display: block; object-fit: contain; background: #000; cursor: pointer; } +.pjs-center { position: absolute; inset: 0; pointer-events: none; } +.pjs-bigplay { + position: absolute; top: 50%; left: 50%; width: 74px; height: 74px; border-radius: 50%; + border: none; cursor: pointer; pointer-events: auto; background: rgba(10,12,18,.55); color: #fff; + font-size: 26px; padding-left: 5px; opacity: 0; transition: opacity .15s, transform .15s; + transform: translate(-50%, -50%) scale(.9); } -.m-cbtn { - width: 58px; height: 58px; border-radius: 50%; background: #c0392b; color: #fff; - border: 0; font-size: 22px; line-height: 1; cursor: pointer; - display: flex; align-items: center; justify-content: center; +.pjs.paused:not(.buffering) .pjs-bigplay { opacity: 1; transform: translate(-50%, -50%) scale(1); } +.pjs-spinner { + position: absolute; top: 50%; left: 50%; width: 46px; height: 46px; margin: -23px 0 0 -23px; + border: 3px solid rgba(255,255,255,.22); border-top-color: #fff; border-radius: 50%; opacity: 0; } -.m-cbtn:active { background: #a93226; } +.pjs.buffering .pjs-spinner { opacity: 1; animation: pjs-spin .8s linear infinite; } +@keyframes pjs-spin { to { transform: rotate(360deg); } } +/* нижняя панель — на тач всегда видима */ +.pjs-bar { + position: absolute; left: 0; right: 0; bottom: 0; z-index: 5; + display: flex; align-items: center; gap: 12px; + padding: 26px 12px calc(11px + env(safe-area-inset-bottom, 0)); + background: linear-gradient(transparent, rgba(0,0,0,.85)); +} +.pjs-btn { background: none; border: none; color: #fff; cursor: pointer; font-size: 20px; line-height: 1; padding: 6px 8px; border-radius: 6px; } +.pjs-btn:active { background: rgba(255,255,255,.18); } +.pjs-time, .pjs-dur { color: #fff; font-size: 12px; font-variant-numeric: tabular-nums; min-width: 40px; text-align: center; } +.pjs-seek { position: relative; flex: 1; height: 6px; border-radius: 3px; background: rgba(255,255,255,.25); cursor: pointer; touch-action: none; } +.pjs-buf { position: absolute; left: 0; top: 0; bottom: 0; width: 0; background: rgba(255,255,255,.35); border-radius: 3px; } +.pjs-prog { position: absolute; left: 0; top: 0; bottom: 0; width: 0; background: #e23b3b; border-radius: 3px; } +.pjs-knob { position: absolute; top: 50%; width: 16px; height: 16px; margin-left: -8px; border-radius: 50%; background: #e23b3b; box-shadow: 0 0 4px rgba(0,0,0,.5); transform: translateY(-50%); } + +/* live: только пауза/плей + во весь экран (без шкалы/времени/скорости) */ +.pjs.live .pjs-time, .pjs.live .pjs-dur, .pjs.live .pjs-seek, .pjs.live .pjs-speed { display: none; } +.pjs.live .pjs-bar { justify-content: center; gap: 30px; } diff --git a/frontend/static/mobile.js b/frontend/static/mobile.js index 3282438..89c3dc0 100644 --- a/frontend/static/mobile.js +++ b/frontend/static/mobile.js @@ -1,11 +1,17 @@ -// CoRE.Vizion+ — мобильный dashboard. 2 режима: Live (1 камера + выбор) и Архив. -// Видео отдаётся сервером уже перекодированным в H.264 (обычный