Добавление 8 каналов в ui
This commit is contained in:
@@ -120,6 +120,33 @@ html {
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
|
||||
/* ===== ГЛОБАЛЬНЫЕ СТИЛИ СКРОЛЛБАРОВ ===== */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: rgba(0, 255, 136, 0.05);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(0, 255, 136, 0.3);
|
||||
border-radius: 4px;
|
||||
transition: background 0.3s ease;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(0, 255, 136, 0.5);
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(0, 255, 136, 0.3) rgba(0, 255, 136, 0.05);
|
||||
}
|
||||
|
||||
/* Убираем скролл-бары на мобильных где не нужно */
|
||||
@media (max-width: 768px) {
|
||||
.panel::-webkit-scrollbar,
|
||||
@@ -128,3 +155,4 @@ html {
|
||||
width: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
/* Стили карточек (card, accordion, статусы) */
|
||||
/* Стили карточек, каналов, статусов и истории */
|
||||
|
||||
/* ===== CARD ===== */
|
||||
.card {
|
||||
border: 1px solid #00ff88;
|
||||
border-radius: 10px;
|
||||
padding: 14px;
|
||||
background: rgba(0,255,136,0.05);
|
||||
overflow: hidden;
|
||||
overflow: visible;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
box-shadow: 0 0 30px rgba(0, 255, 136, 0.05);
|
||||
}
|
||||
|
||||
.card h3 {
|
||||
margin: 0 0 10px;
|
||||
font-size: var(--font-size-lg);
|
||||
@@ -24,7 +23,7 @@
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
/* ===== STATUS ===== */
|
||||
/* ===== СТАТУС ===== */
|
||||
#status {
|
||||
font-family: var(--font-tech);
|
||||
font-size: var(--font-size-3xl);
|
||||
@@ -32,23 +31,19 @@
|
||||
margin-bottom: 8px;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.status-ok {
|
||||
color: #00ff88 !important;
|
||||
text-shadow: 0 0 30px rgba(0, 255, 136, 0.3);
|
||||
}
|
||||
|
||||
.status-lost {
|
||||
color: #ffffff !important;
|
||||
animation: blink 1.5s infinite;
|
||||
text-shadow: 0 0 30px rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.status-error {
|
||||
color: #ff4444 !important;
|
||||
text-shadow: 0 0 30px rgba(255, 68, 68, 0.3);
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.2; }
|
||||
@@ -62,7 +57,7 @@
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
/* ===== ACCORDION ===== */
|
||||
/* ===== АККОРДЕОН ===== */
|
||||
.accordion-header {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
@@ -73,54 +68,316 @@
|
||||
letter-spacing: 0.05em;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.accordion-header:hover {
|
||||
opacity: 0.7;
|
||||
text-shadow: 0 0 20px rgba(0, 255, 136, 0.2);
|
||||
}
|
||||
|
||||
.accordion-body {
|
||||
max-height: 520px;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.3s ease, opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.accordion-body.collapsed {
|
||||
max-height: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* ===== LAST BYTE ===== */
|
||||
#last-byte {
|
||||
font-family: var(--font-tech);
|
||||
font-size: var(--font-size-xl);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: #00ff88;
|
||||
text-shadow: 0 0 20px rgba(0, 255, 136, 0.2);
|
||||
}
|
||||
|
||||
/* ===== HISTORY ===== */
|
||||
/* ===== ИСТОРИЯ ===== */
|
||||
#history {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--font-size-sm);
|
||||
line-height: 1.35;
|
||||
max-height: 420px;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
padding-right: 4px;
|
||||
margin: 0;
|
||||
letter-spacing: 0.02em;
|
||||
overscroll-behavior: contain;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
#history::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
#history::-webkit-scrollbar-track {
|
||||
background: rgba(0, 255, 136, 0.05);
|
||||
}
|
||||
|
||||
#history::-webkit-scrollbar-thumb {
|
||||
background: #00ff88;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
/* ===== КАНАЛЫ ===== */
|
||||
.channels-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
.channels-header h3 {
|
||||
margin: 0;
|
||||
font-size: var(--font-size-lg);
|
||||
}
|
||||
.channels-controls {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
.channels-toggle-all {
|
||||
background: rgba(0, 255, 136, 0.08);
|
||||
border: 1px solid rgba(0, 255, 136, 0.2);
|
||||
color: #00ff88;
|
||||
padding: 4px 10px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-family: var(--font-mono);
|
||||
transition: all 0.2s ease;
|
||||
line-height: 1.4;
|
||||
min-height: 30px;
|
||||
}
|
||||
.channels-toggle-all:hover {
|
||||
background: rgba(0, 255, 136, 0.15);
|
||||
border-color: #00ff88;
|
||||
}
|
||||
.channels-toggle-all:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
.channels-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 6px;
|
||||
overscroll-behavior: contain;
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
overflow-x: visible;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
padding-right: 4px;
|
||||
}
|
||||
.channels-grid::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
.channels-grid::-webkit-scrollbar-track {
|
||||
background: rgba(0, 255, 136, 0.05);
|
||||
}
|
||||
.channels-grid::-webkit-scrollbar-thumb {
|
||||
background: rgba(0, 255, 136, 0.3);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
/* Карточка канала */
|
||||
.channel-item {
|
||||
background: rgba(0, 255, 136, 0.03);
|
||||
border: 1px solid rgba(0, 255, 136, 0.1);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.channel-item:hover {
|
||||
border-color: rgba(0, 255, 136, 0.25);
|
||||
background: rgba(0, 255, 136, 0.06);
|
||||
}
|
||||
.channel-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 6px 10px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
.channel-header:hover {
|
||||
background: rgba(0, 255, 136, 0.05);
|
||||
}
|
||||
.channel-label {
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: #88ffbb;
|
||||
font-family: var(--font-tech);
|
||||
}
|
||||
.channel-value {
|
||||
font-size: var(--font-size-base);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: #00ff88;
|
||||
font-family: var(--font-tech);
|
||||
min-width: 24px;
|
||||
text-align: right;
|
||||
}
|
||||
.channel-toggle {
|
||||
color: #666;
|
||||
font-size: 10px;
|
||||
transition: transform 0.3s;
|
||||
margin-left: 6px;
|
||||
}
|
||||
.channel-toggle.collapsed {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
.channel-body {
|
||||
padding: 0 10px 10px 10px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.channel-body.hidden {
|
||||
display: none;
|
||||
}
|
||||
.channel-progress {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.channel-progress .progress {
|
||||
flex: 1;
|
||||
height: 8px;
|
||||
padding: 2px;
|
||||
border-width: 2px;
|
||||
}
|
||||
.channel-progress .progress .fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #0a0, #0f0, #ff0, #f80);
|
||||
border-radius: 2px;
|
||||
transition: width 0.15s ease;
|
||||
}
|
||||
.channel-stats {
|
||||
font-size: var(--font-size-xs);
|
||||
color: #666;
|
||||
margin-top: 4px;
|
||||
font-family: var(--font-mono);
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
/* ===== ЗВУК И ВЫСОТА ===== */
|
||||
.card:has(#bar2) .fill,
|
||||
.card:has(#bar3) .fill {
|
||||
background: linear-gradient(90deg, #0a0, #0f0, #ff0, #f80);
|
||||
transition: width 0.15s ease;
|
||||
}
|
||||
.card:has(#bar2) .progress,
|
||||
.card:has(#bar3) .progress {
|
||||
height: 34px;
|
||||
padding: 6px;
|
||||
border-width: 3px;
|
||||
}
|
||||
|
||||
/* ===== АДАПТИВ ===== */
|
||||
@media (max-width: 768px) {
|
||||
.channels-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 4px;
|
||||
max-height: none;
|
||||
overflow-y: visible;
|
||||
overscroll-behavior: auto;
|
||||
}
|
||||
.channels-header {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 6px;
|
||||
}
|
||||
.channels-controls {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.channel-label {
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
.channel-value {
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
.channel-progress .progress {
|
||||
height: 6px;
|
||||
padding: 1px;
|
||||
}
|
||||
#history {
|
||||
max-height: 300px;
|
||||
}
|
||||
.card:has(#bar2) .progress,
|
||||
.card:has(#bar3) .progress {
|
||||
height: 28px;
|
||||
padding: 4px;
|
||||
border-width: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.channels-grid {
|
||||
gap: 3px;
|
||||
}
|
||||
.channel-header {
|
||||
padding: 4px 8px;
|
||||
}
|
||||
.channel-body {
|
||||
padding: 0 8px 8px 8px;
|
||||
}
|
||||
.channel-stats {
|
||||
font-size: 9px;
|
||||
}
|
||||
#history {
|
||||
max-height: 200px;
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
.card:has(#bar2) .progress,
|
||||
.card:has(#bar3) .progress {
|
||||
height: 24px;
|
||||
padding: 3px;
|
||||
border-width: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== АНИМАЦИЯ ===== */
|
||||
.channel-item.updating .channel-value {
|
||||
animation: pulse-value 0.15s ease;
|
||||
}
|
||||
@keyframes pulse-value {
|
||||
0%, 100% { transform: scale(1); }
|
||||
50% { transform: scale(1.2); color: #ff0; }
|
||||
}
|
||||
|
||||
/* Карточки на десктопе */
|
||||
@media (min-width: 769px) {
|
||||
.card {
|
||||
max-height: 100%;
|
||||
overflow: visible;
|
||||
}
|
||||
.channels-card {
|
||||
max-height: 600px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.channels-card .channels-grid {
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.history-card {
|
||||
max-height: 500px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.history-card #history {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.card {
|
||||
overflow: visible !important;
|
||||
height: auto !important;
|
||||
min-height: unset;
|
||||
max-height: none !important;
|
||||
}
|
||||
.channels-card {
|
||||
max-height: none !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
.channels-card .channels-grid {
|
||||
max-height: none !important;
|
||||
overflow-y: visible !important;
|
||||
}
|
||||
.history-card {
|
||||
max-height: none !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
.history-card #history {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
@@ -5,23 +5,28 @@
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* панели */
|
||||
.panel {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
padding-right: 4px;
|
||||
overscroll-behavior: auto;
|
||||
}
|
||||
|
||||
/* LEFT */
|
||||
.left {
|
||||
flex: 1 1 auto;
|
||||
min-width: 300px;
|
||||
padding: 10px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
height: 100%;
|
||||
overscroll-behavior: auto;
|
||||
}
|
||||
|
||||
/* RIGHT */
|
||||
.right {
|
||||
flex: 0 0 40%;
|
||||
min-width: 0;
|
||||
@@ -29,7 +34,6 @@
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.right.hidden {
|
||||
flex: 0 0 0 !important;
|
||||
width: 0 !important;
|
||||
@@ -38,7 +42,6 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* ===== RESIZER ===== */
|
||||
.resizer {
|
||||
width: 8px;
|
||||
min-width: 8px;
|
||||
@@ -48,11 +51,9 @@
|
||||
flex-shrink: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.resizer:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.resizer.hidden {
|
||||
width: 0 !important;
|
||||
min-width: 0 !important;
|
||||
@@ -60,7 +61,6 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* ===== GRID ===== */
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
@@ -68,9 +68,87 @@
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
@media (min-width: 900px) {
|
||||
/* ===== МОБИЛЬНАЯ ВЕРСИЯ ===== */
|
||||
@media (max-width: 768px) {
|
||||
.layout {
|
||||
flex-direction: column;
|
||||
height: auto !important;
|
||||
min-height: 100vh;
|
||||
gap: 8px;
|
||||
overflow: visible !important;
|
||||
}
|
||||
.panel {
|
||||
height: auto !important;
|
||||
overflow-y: visible !important;
|
||||
-webkit-overflow-scrolling: auto;
|
||||
padding-right: 0;
|
||||
}
|
||||
.left {
|
||||
flex: 1 1 auto !important;
|
||||
min-width: 0 !important;
|
||||
padding: 0;
|
||||
width: 100% !important;
|
||||
overflow-y: visible !important;
|
||||
height: auto !important;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
.right {
|
||||
flex: 0 0 auto !important;
|
||||
min-width: 100% !important;
|
||||
width: 100% !important;
|
||||
height: 40vh;
|
||||
min-height: 300px;
|
||||
opacity: 1 !important;
|
||||
pointer-events: auto !important;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.right.hidden {
|
||||
display: none !important;
|
||||
flex: 0 0 0 !important;
|
||||
height: 0 !important;
|
||||
min-height: 0 !important;
|
||||
}
|
||||
.resizer {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.layout {
|
||||
min-height: 100vh;
|
||||
gap: 6px;
|
||||
}
|
||||
.right {
|
||||
height: 35vh;
|
||||
min-height: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== АЛЬБОМНАЯ ОРИЕНТАЦИЯ ===== */
|
||||
@media (max-width: 768px) and (orientation: landscape) {
|
||||
.layout {
|
||||
flex-direction: row;
|
||||
height: calc(100vh - 80px) !important;
|
||||
min-height: auto;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
.left {
|
||||
flex: 1 1 60% !important;
|
||||
min-width: 0 !important;
|
||||
padding-right: 8px;
|
||||
overflow-y: auto !important;
|
||||
height: 100% !important;
|
||||
}
|
||||
.right {
|
||||
flex: 0 0 40% !important;
|
||||
min-width: 0 !important;
|
||||
height: 100% !important;
|
||||
min-height: 0;
|
||||
}
|
||||
.grid {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 6px;
|
||||
}
|
||||
.wide {
|
||||
grid-column: span 2;
|
||||
|
||||
@@ -1,119 +1,63 @@
|
||||
/* ===== РЕСПОНСИВНЫЙ ДИЗАЙН ===== */
|
||||
|
||||
/* ============================================
|
||||
ПЛАНШЕТЫ (768px - 1024px)
|
||||
============================================ */
|
||||
/* ===== ПЛАНШЕТЫ ===== */
|
||||
@media (max-width: 1024px) {
|
||||
/* Уменьшаем отступы */
|
||||
body {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* Хедер */
|
||||
.header-bar {
|
||||
gap: 10px;
|
||||
grid-template-columns: 1fr auto;
|
||||
}
|
||||
|
||||
.header-bar h1 {
|
||||
font-size: var(--font-size-xl);
|
||||
}
|
||||
|
||||
.header-time {
|
||||
font-size: var(--font-size-sm);
|
||||
padding: 4px 10px;
|
||||
}
|
||||
|
||||
.cam-btn {
|
||||
font-size: var(--font-size-sm);
|
||||
padding: 6px 12px;
|
||||
}
|
||||
|
||||
/* Карточки */
|
||||
.card {
|
||||
padding: 12px 14px;
|
||||
}
|
||||
|
||||
.card h3 {
|
||||
font-size: var(--font-size-base);
|
||||
}
|
||||
|
||||
#status {
|
||||
font-size: var(--font-size-2xl);
|
||||
}
|
||||
|
||||
/* Прогресс-бары */
|
||||
.progress {
|
||||
height: 28px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.progress-label {
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
/* Сигнальные иконки */
|
||||
.signal-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
body { padding: 10px; }
|
||||
.header-bar { gap: 10px; grid-template-columns: 1fr auto; }
|
||||
.header-bar h1 { font-size: var(--font-size-xl); }
|
||||
.header-time { font-size: var(--font-size-sm); padding: 4px 10px; }
|
||||
.cam-btn { font-size: var(--font-size-sm); padding: 6px 12px; }
|
||||
.card { padding: 12px 14px; }
|
||||
.card h3 { font-size: var(--font-size-base); }
|
||||
#status { font-size: var(--font-size-2xl); }
|
||||
.progress { height: 28px; padding: 4px; }
|
||||
.progress-label { font-size: var(--font-size-sm); }
|
||||
.signal-icon { width: 28px; height: 28px; }
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
МОБИЛЬНЫЕ УСТРОЙСТВА (до 768px)
|
||||
============================================ */
|
||||
/* ===== МОБИЛЬНЫЕ ===== */
|
||||
@media (max-width: 768px) {
|
||||
/* ===== ОБЩИЕ ===== */
|
||||
body {
|
||||
padding: 8px;
|
||||
font-size: var(--font-size-base);
|
||||
overflow-y: auto;
|
||||
min-height: 100vh;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/* Увеличиваем область касания для всех интерактивных элементов */
|
||||
button,
|
||||
.cam-btn,
|
||||
.tab-btn,
|
||||
.pagination-btn,
|
||||
.log-file-item {
|
||||
button, .cam-btn, .tab-btn, .pagination-btn, .log-file-item {
|
||||
min-height: 44px;
|
||||
min-width: 44px;
|
||||
}
|
||||
|
||||
/* ===== ХЕДЕР ===== */
|
||||
.header-bar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.header-bar h1 {
|
||||
font-size: var(--font-size-lg);
|
||||
text-align: center;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.header-bar > div {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.header-time {
|
||||
font-size: var(--font-size-sm);
|
||||
padding: 4px 10px;
|
||||
flex: 1;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
#audio-indicator {
|
||||
font-size: var(--font-size-base);
|
||||
padding: 4px 10px;
|
||||
min-width: 40px;
|
||||
}
|
||||
|
||||
.cam-btn {
|
||||
font-size: var(--font-size-sm);
|
||||
padding: 8px 14px;
|
||||
@@ -121,8 +65,6 @@
|
||||
flex: 1;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
/* Ссылка "Журнал" */
|
||||
.header-bar a[href="/logs.html"] {
|
||||
font-size: var(--font-size-sm);
|
||||
padding: 6px 12px;
|
||||
@@ -130,438 +72,125 @@
|
||||
flex: 1;
|
||||
min-width: 60px;
|
||||
}
|
||||
|
||||
/* ===== МАКЕТ ===== */
|
||||
.layout {
|
||||
flex-direction: column;
|
||||
height: auto;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.panel {
|
||||
height: auto;
|
||||
overflow-y: visible;
|
||||
}
|
||||
|
||||
.left {
|
||||
flex: 1 1 auto !important;
|
||||
min-width: 0 !important;
|
||||
padding: 0;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.right {
|
||||
flex: 0 0 auto !important;
|
||||
min-width: 100% !important;
|
||||
width: 100% !important;
|
||||
height: 40vh;
|
||||
min-height: 300px;
|
||||
opacity: 1 !important;
|
||||
pointer-events: auto !important;
|
||||
}
|
||||
|
||||
.right.hidden {
|
||||
display: none !important;
|
||||
flex: 0 0 0 !important;
|
||||
height: 0 !important;
|
||||
min-height: 0 !important;
|
||||
}
|
||||
|
||||
.resizer {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* ===== СЕТКА ===== */
|
||||
.grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.wide {
|
||||
grid-column: span 1;
|
||||
}
|
||||
|
||||
/* ===== КАРТОЧКИ ===== */
|
||||
.card {
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.card h3 {
|
||||
font-size: var(--font-size-base);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
#status {
|
||||
font-size: var(--font-size-2xl);
|
||||
}
|
||||
|
||||
#meta {
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
/* ===== ПРОГРЕСС-БАРЫ ===== */
|
||||
.progress-row {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.progress-label {
|
||||
font-size: var(--font-size-sm);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.progress {
|
||||
height: 30px;
|
||||
padding: 4px;
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
.progress-with-signal {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.progress-90 {
|
||||
flex: 1;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.signal-icon {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sig-bar {
|
||||
min-width: 4px;
|
||||
border-width: 1.5px;
|
||||
}
|
||||
|
||||
/* ===== АККОРДЕОН ===== */
|
||||
.accordion-header {
|
||||
padding: 8px 0;
|
||||
font-size: var(--font-size-base);
|
||||
}
|
||||
|
||||
.accordion-body {
|
||||
max-height: 400px;
|
||||
}
|
||||
|
||||
#history {
|
||||
font-size: var(--font-size-sm);
|
||||
max-height: 300px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* ===== КАМЕРА ===== */
|
||||
.cam-modal {
|
||||
height: 100%;
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
.cam-header {
|
||||
padding: 6px 10px;
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
.cam-frame {
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.crosshair-box {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-width: 1.5px;
|
||||
}
|
||||
|
||||
/* ===== ГОРЯЧИЕ КЛАВИШИ ===== */
|
||||
.hotkey-hint {
|
||||
font-size: var(--font-size-xs);
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.hotkey-hint kbd {
|
||||
padding: 2px 6px;
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
.wide { grid-column: span 1; }
|
||||
.card { padding: 12px; border-radius: 8px; overflow: visible !important; height: auto !important; }
|
||||
.card h3 { font-size: var(--font-size-base); margin-bottom: 8px; }
|
||||
#status { font-size: var(--font-size-2xl); }
|
||||
#meta { font-size: var(--font-size-sm); }
|
||||
.progress-row { margin-bottom: 12px; }
|
||||
.progress-label { font-size: var(--font-size-sm); margin-bottom: 4px; }
|
||||
.progress { height: 30px; padding: 4px; border-width: 2px; }
|
||||
.progress-with-signal { flex-wrap: nowrap; }
|
||||
.progress-90 { flex: 1; margin-right: 8px; }
|
||||
.signal-icon { width: 30px; height: 30px; flex-shrink: 0; }
|
||||
.sig-bar { min-width: 4px; border-width: 1.5px; }
|
||||
.accordion-header { padding: 8px 0; font-size: var(--font-size-base); }
|
||||
.accordion-body { max-height: 400px; overflow-y: auto; -webkit-overflow-scrolling: touch; }
|
||||
.cam-modal { height: 100%; min-height: 300px; }
|
||||
.cam-header { padding: 6px 10px; font-size: var(--font-size-sm); }
|
||||
.cam-frame { object-fit: cover; }
|
||||
.crosshair-box { width: 80px; height: 80px; border-width: 1.5px; }
|
||||
.hotkey-hint { font-size: var(--font-size-xs); padding: 4px 0; flex-shrink: 0; }
|
||||
.hotkey-hint kbd { padding: 2px 6px; font-size: var(--font-size-xs); }
|
||||
.channels-grid { overscroll-behavior: auto !important; max-height: none !important; overflow-y: visible !important; }
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
МАЛЕНЬКИЕ ТЕЛЕФОНЫ (до 480px)
|
||||
============================================ */
|
||||
/* ===== МАЛЕНЬКИЕ ТЕЛЕФОНЫ ===== */
|
||||
@media (max-width: 480px) {
|
||||
body {
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
/* Хедер */
|
||||
.header-bar h1 {
|
||||
font-size: var(--font-size-base);
|
||||
}
|
||||
|
||||
.header-bar > div {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.header-time {
|
||||
font-size: var(--font-size-xs);
|
||||
padding: 4px 8px;
|
||||
min-width: 60px;
|
||||
}
|
||||
|
||||
#audio-indicator {
|
||||
font-size: var(--font-size-sm);
|
||||
padding: 4px 8px;
|
||||
min-width: 35px;
|
||||
}
|
||||
|
||||
.cam-btn {
|
||||
font-size: var(--font-size-xs);
|
||||
padding: 6px 10px;
|
||||
min-width: 60px;
|
||||
}
|
||||
|
||||
/* Карточки */
|
||||
.card {
|
||||
padding: 10px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.card h3 {
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
#status {
|
||||
font-size: var(--font-size-xl);
|
||||
}
|
||||
|
||||
#meta {
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
|
||||
/* Прогресс-бары */
|
||||
.progress {
|
||||
height: 24px;
|
||||
padding: 3px;
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
.progress-label {
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
|
||||
.signal-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.sig-bar {
|
||||
min-width: 3px;
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
body { padding: 6px; }
|
||||
.header-bar h1 { font-size: var(--font-size-base); }
|
||||
.header-bar > div { flex-wrap: wrap; }
|
||||
.header-time { font-size: var(--font-size-xs); padding: 4px 8px; min-width: 60px; }
|
||||
#audio-indicator { font-size: var(--font-size-sm); padding: 4px 8px; min-width: 35px; }
|
||||
.cam-btn { font-size: var(--font-size-xs); padding: 6px 10px; min-width: 60px; }
|
||||
.card { padding: 10px; border-radius: 6px; }
|
||||
.card h3 { font-size: var(--font-size-sm); }
|
||||
#status { font-size: var(--font-size-xl); }
|
||||
#meta { font-size: var(--font-size-xs); }
|
||||
.progress { height: 24px; padding: 3px; border-width: 2px; }
|
||||
.progress-label { font-size: var(--font-size-xs); }
|
||||
.signal-icon { width: 24px; height: 24px; }
|
||||
.sig-bar { min-width: 3px; border-width: 1px; }
|
||||
.sig-bar-1 { height: 30%; }
|
||||
.sig-bar-2 { height: 60%; }
|
||||
.sig-bar-3 { height: 90%; }
|
||||
|
||||
/* Камера */
|
||||
.right {
|
||||
height: 35vh;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.cam-header {
|
||||
padding: 4px 8px;
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
|
||||
.crosshair-box {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
/* Аккордеон */
|
||||
#history {
|
||||
font-size: var(--font-size-xs);
|
||||
max-height: 200px;
|
||||
}
|
||||
.right { height: 35vh; min-height: 200px; }
|
||||
.cam-header { padding: 4px 8px; font-size: var(--font-size-xs); }
|
||||
.crosshair-box { width: 60px; height: 60px; }
|
||||
#history { font-size: var(--font-size-xs); max-height: 200px; }
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
ОРИЕНТАЦИЯ (ALBUM / PORTRAIT)
|
||||
============================================ */
|
||||
@media (max-width: 768px) and (orientation: landscape) {
|
||||
.layout {
|
||||
flex-direction: row;
|
||||
height: calc(100vh - 80px);
|
||||
}
|
||||
|
||||
.left {
|
||||
flex: 1 1 60% !important;
|
||||
min-width: 0 !important;
|
||||
padding-right: 8px;
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.right {
|
||||
flex: 0 0 40% !important;
|
||||
min-width: 0 !important;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.grid {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.wide {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
#status {
|
||||
font-size: var(--font-size-xl);
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
УЛУЧШЕНИЯ ДЛЯ ТАЧ-ИНТЕРФЕЙСА
|
||||
============================================ */
|
||||
/* ===== ТАЧ-ИНТЕРФЕЙС ===== */
|
||||
@media (hover: none) and (pointer: coarse) {
|
||||
/* Увеличиваем область касания */
|
||||
button,
|
||||
.cam-btn,
|
||||
.tab-btn,
|
||||
.pagination-btn,
|
||||
.log-file-item,
|
||||
.accordion-header,
|
||||
.pagination-goto-btn {
|
||||
button, .cam-btn, .tab-btn, .pagination-btn, .log-file-item,
|
||||
.accordion-header, .pagination-goto-btn {
|
||||
min-height: 44px;
|
||||
min-width: 44px;
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
/* Убираем hover-эффекты, оставляем active */
|
||||
.cam-btn:hover,
|
||||
.tab-btn:hover,
|
||||
.pagination-btn:hover,
|
||||
.log-file-item:hover {
|
||||
.cam-btn:hover, .tab-btn:hover, .pagination-btn:hover, .log-file-item:hover {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.cam-btn:active,
|
||||
.tab-btn:active,
|
||||
.pagination-btn:active,
|
||||
.log-file-item:active {
|
||||
.cam-btn:active, .tab-btn:active, .pagination-btn:active, .log-file-item:active {
|
||||
transform: scale(0.95);
|
||||
background: rgba(0, 255, 136, 0.15);
|
||||
}
|
||||
|
||||
/* Увеличиваем отступы для скролла */
|
||||
.panel,
|
||||
.logs-content,
|
||||
.logs-sidebar {
|
||||
.panel, .logs-content, .logs-sidebar, #history, #dataTableContainer, .accordion-body {
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scroll-behavior: smooth;
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
|
||||
/* Увеличиваем размер полей ввода */
|
||||
.pagination-input {
|
||||
min-height: 44px;
|
||||
font-size: var(--font-size-base);
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
/* Увеличиваем чекбоксы и переключатели */
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
.pagination-input { min-height: 44px; font-size: var(--font-size-base); padding: 8px 12px; }
|
||||
input[type="checkbox"], input[type="radio"] { width: 20px; height: 20px; }
|
||||
.header-link, .cam-btn, .cam-close-btn { min-height: 44px !important; min-width: 44px !important; padding: 8px 14px !important; }
|
||||
.header-link:active, .cam-btn:active { transform: scale(0.95); }
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
ТЁМНАЯ ТЕМА (опционально)
|
||||
============================================ */
|
||||
/* ===== ТЁМНАЯ ТЕМА ===== */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background: #0a0f0a;
|
||||
}
|
||||
body { background: #0a0f0a; }
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
ПЕЧАТЬ
|
||||
============================================ */
|
||||
/* ===== ПЕЧАТЬ ===== */
|
||||
@media print {
|
||||
.header-bar,
|
||||
.cam-btn,
|
||||
.resizer,
|
||||
.right,
|
||||
.pagination-container,
|
||||
.hotkey-hint {
|
||||
.header-bar, .cam-btn, .resizer, .right, .pagination-container, .hotkey-hint {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.layout {
|
||||
display: block !important;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.panel {
|
||||
height: auto !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.card {
|
||||
break-inside: avoid;
|
||||
border: 1px solid #00ff88;
|
||||
}
|
||||
}
|
||||
/* ===== ДОПОЛНИТЕЛЬНЫЕ УЛУЧШЕНИЯ ДЛЯ ХЕДЕРА ===== */
|
||||
@media (max-width: 768px) {
|
||||
.header-bar {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
align-items: stretch !important;
|
||||
gap: 8px !important;
|
||||
}
|
||||
|
||||
.header-controls {
|
||||
display: flex !important;
|
||||
flex-wrap: wrap !important;
|
||||
justify-content: center !important;
|
||||
gap: 6px !important;
|
||||
}
|
||||
.layout { display: block !important; height: auto !important; }
|
||||
.panel { height: auto !important; overflow: visible !important; }
|
||||
.card { break-inside: avoid; border: 1px solid #00ff88; }
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.header-controls {
|
||||
gap: 4px !important;
|
||||
}
|
||||
/* ===== СКРОЛЛБАРЫ ===== */
|
||||
::-webkit-scrollbar { width: 8px; height: 8px; }
|
||||
::-webkit-scrollbar-track { background: rgba(0, 255, 136, 0.05); border-radius: 4px; }
|
||||
::-webkit-scrollbar-thumb { background: rgba(0, 255, 136, 0.3); border-radius: 4px; transition: background 0.3s ease; }
|
||||
::-webkit-scrollbar-thumb:hover { background: rgba(0, 255, 136, 0.5); }
|
||||
* { scrollbar-width: thin; scrollbar-color: rgba(0, 255, 136, 0.3) rgba(0, 255, 136, 0.05); }
|
||||
|
||||
.panel::-webkit-scrollbar, .logs-sidebar::-webkit-scrollbar,
|
||||
.logs-content::-webkit-scrollbar, #history::-webkit-scrollbar,
|
||||
.accordion-body::-webkit-scrollbar, .channels-grid::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
/* ===== УЛУЧШЕНИЯ ДЛЯ ТАЧ-ИНТЕРФЕЙСА ===== */
|
||||
@media (hover: none) and (pointer: coarse) {
|
||||
.header-link,
|
||||
.cam-btn,
|
||||
.cam-close-btn {
|
||||
min-height: 44px !important;
|
||||
min-width: 44px !important;
|
||||
padding: 8px 14px !important;
|
||||
}
|
||||
|
||||
.header-link:active,
|
||||
.cam-btn:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
.panel::-webkit-scrollbar-track, .logs-sidebar::-webkit-scrollbar-track,
|
||||
.logs-content::-webkit-scrollbar-track, #history::-webkit-scrollbar-track,
|
||||
.accordion-body::-webkit-scrollbar-track, .channels-grid::-webkit-scrollbar-track {
|
||||
background: rgba(0, 255, 136, 0.05);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
/* ===== КНОПКА ЗАКРЫТИЯ КАМЕРЫ НА МОБИЛЬНЫХ ===== */
|
||||
@media (max-width: 768px) {
|
||||
.cam-close-btn {
|
||||
font-size: var(--font-size-lg);
|
||||
padding: 6px 14px;
|
||||
min-height: 44px;
|
||||
min-width: 44px;
|
||||
}
|
||||
.panel::-webkit-scrollbar-thumb, .logs-sidebar::-webkit-scrollbar-thumb,
|
||||
.logs-content::-webkit-scrollbar-thumb, #history::-webkit-scrollbar-thumb,
|
||||
.accordion-body::-webkit-scrollbar-thumb, .channels-grid::-webkit-scrollbar-thumb {
|
||||
background: rgba(0, 255, 136, 0.3);
|
||||
border-radius: 3px;
|
||||
}
|
||||
.panel::-webkit-scrollbar-thumb:hover, .logs-sidebar::-webkit-scrollbar-thumb:hover,
|
||||
.logs-content::-webkit-scrollbar-thumb:hover, #history::-webkit-scrollbar-thumb:hover,
|
||||
.accordion-body::-webkit-scrollbar-thumb:hover, .channels-grid::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(0, 255, 136, 0.5);
|
||||
}
|
||||
@@ -9,11 +9,7 @@
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
<title>G-Port Панель</title>
|
||||
<link rel="icon" type="image/png" href="/favicon.png">
|
||||
|
||||
<!-- Локальные шрифты -->
|
||||
<link rel="stylesheet" href="/css/fonts.css">
|
||||
|
||||
<!-- ===== СТИЛИ ===== -->
|
||||
<link rel="stylesheet" href="/css/base.css">
|
||||
<link rel="stylesheet" href="/css/header.css">
|
||||
<link rel="stylesheet" href="/css/layout.css">
|
||||
@@ -23,7 +19,6 @@
|
||||
<link rel="stylesheet" href="/css/audio.css">
|
||||
<link rel="stylesheet" href="/css/responsive.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="header-bar">
|
||||
@@ -37,57 +32,66 @@
|
||||
</div>
|
||||
|
||||
<div class="layout">
|
||||
<!-- LEFT: графики / дашборд -->
|
||||
<!-- LEFT -->
|
||||
<div class="panel left" id="leftPanel">
|
||||
<div class="grid">
|
||||
|
||||
<div class="card wide">
|
||||
<!-- Карточка состояния -->
|
||||
<div class="card wide" style="flex-shrink: 0;">
|
||||
<h3>СОСТОЯНИЕ</h3>
|
||||
<div id="status">...</div>
|
||||
<div id="meta"></div>
|
||||
</div>
|
||||
|
||||
<div class="card wide">
|
||||
<h3>КАНАЛ</h3>
|
||||
|
||||
<div class="progress-row">
|
||||
<div class="progress-label">
|
||||
Канал 1 — <span id="last-byte">0</span>
|
||||
</div>
|
||||
<div class="progress-with-signal">
|
||||
<div class="progress progress-90">
|
||||
<div class="fill" id="bar1"></div>
|
||||
</div>
|
||||
<div class="signal-icon" id="signal-icon" aria-label="Уровень сигнала" role="img">
|
||||
<div class="sig-bar sig-bar-1"></div>
|
||||
<div class="sig-bar sig-bar-2"></div>
|
||||
<div class="sig-bar sig-bar-3"></div>
|
||||
</div>
|
||||
<!-- Карточка каналов (8 штук) -->
|
||||
<div class="card wide channels-card">
|
||||
<div class="channels-header">
|
||||
<h3>КАНАЛЫ СИГНАЛОВ (8)</h3>
|
||||
<div class="channels-controls">
|
||||
<button class="channels-toggle-all" id="expandAllBtn">▼</button>
|
||||
<button class="channels-toggle-all" id="collapseAllBtn">▶</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="progress-row">
|
||||
<div class="progress-label">🔊 Звук</div>
|
||||
<div class="progress">
|
||||
<div class="fill" id="bar2"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="progress-row">
|
||||
<div class="progress-label">📐 Высота</div>
|
||||
<div class="progress">
|
||||
<div class="fill" id="bar3"></div>
|
||||
</div>
|
||||
<div class="channels-grid" id="channelsContainer">
|
||||
<!-- Каналы генерируются JS -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===== ВОЗВРАЩАЕМ ЗВУК И ВЫСОТУ ===== -->
|
||||
<!-- Карточка со звуком -->
|
||||
<div class="card wide">
|
||||
<h3>🔊 ЗВУК</h3>
|
||||
<div class="progress-row">
|
||||
<div class="progress-label">Уровень звука</div>
|
||||
<div class="progress">
|
||||
<div class="fill" id="bar2" style="width:0%"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="font-family:var(--font-tech);font-size:var(--font-size-sm);color:#666;margin-top:4px;">
|
||||
<span id="sound-level">0</span> / 100
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Карточка с высотой -->
|
||||
<div class="card wide">
|
||||
<h3>📐 ВЫСОТА</h3>
|
||||
<div class="progress-row">
|
||||
<div class="progress-label">Уровень высоты</div>
|
||||
<div class="progress">
|
||||
<div class="fill" id="bar3" style="width:0%"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="font-family:var(--font-tech);font-size:var(--font-size-sm);color:#666;margin-top:4px;">
|
||||
<span id="height-level">0</span> / 100
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Карточка истории -->
|
||||
<div class="card wide history-card">
|
||||
<h3 class="accordion-header" id="history-header">▶ ПРИНЯТЫЕ ДАННЫЕ</h3>
|
||||
<div class="accordion-body" id="history-body">
|
||||
<div id="history"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -103,8 +107,6 @@
|
||||
</div>
|
||||
<div class="cam-frame-wrapper">
|
||||
<img id="camImage" src="" alt="Camera" class="cam-frame">
|
||||
|
||||
<!-- ПЕРЕКРЕСТИЕ -->
|
||||
<div class="cam-crosshair" id="crosshair">
|
||||
<div class="crosshair-v"></div>
|
||||
<div class="crosshair-h"></div>
|
||||
@@ -113,15 +115,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Подсказка по горячим клавишам -->
|
||||
<div class="hotkey-hint">
|
||||
(ENG lang key) <kbd>C</kbd> — камера | <kbd>M</kbd> — звук | <kbd>Esc</kbd> — сброс
|
||||
</div>
|
||||
|
||||
<script type="module" src="/dist/app.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -25,6 +25,7 @@ import { initAccordion } from "./accordion.js";
|
||||
import { drawChart } from "./chart.js";
|
||||
import { initResize } from "./layout.js";
|
||||
import { initCamera } from "./camera.js";
|
||||
import { updateAllChannels } from './ui.js';
|
||||
import { audioEngine } from "./audio.js";
|
||||
|
||||
let lastChart: number = 0;
|
||||
@@ -166,6 +167,7 @@ async function update(): Promise<void> {
|
||||
if (arr.length > 0 && isOnline) {
|
||||
const bufferSize = health.stats?.filled ?? 0;
|
||||
addSignals(arr, bufferSize);
|
||||
updateAllChannels(arr);
|
||||
}
|
||||
|
||||
const value = raw & 0x3F;
|
||||
|
||||
@@ -9,6 +9,10 @@ export interface DOMElements {
|
||||
canvas: HTMLCanvasElement | null;
|
||||
bars: NodeListOf<HTMLElement>;
|
||||
barFill: HTMLElement | null;
|
||||
bar2: HTMLElement | null;
|
||||
bar3: HTMLElement | null;
|
||||
soundLevel: HTMLElement | null;
|
||||
heightLevel: HTMLElement | null;
|
||||
historyHeader: HTMLElement | null;
|
||||
historyBody: HTMLElement | null;
|
||||
audioIndicator: HTMLElement | null;
|
||||
@@ -24,6 +28,10 @@ export const DOM: DOMElements = {
|
||||
canvas: null,
|
||||
bars: document.querySelectorAll(".sig-bar"),
|
||||
barFill: null,
|
||||
bar2: null,
|
||||
bar3: null,
|
||||
soundLevel: null,
|
||||
heightLevel: null,
|
||||
historyHeader: null,
|
||||
historyBody: null,
|
||||
audioIndicator: null,
|
||||
@@ -38,6 +46,10 @@ export function initDOM(): void {
|
||||
DOM.history = document.getElementById("history");
|
||||
DOM.canvas = document.getElementById("signalChart") as HTMLCanvasElement;
|
||||
DOM.barFill = document.getElementById("bar1");
|
||||
DOM.bar2 = document.getElementById("bar2");
|
||||
DOM.bar3 = document.getElementById("bar3");
|
||||
DOM.soundLevel = document.getElementById("sound-level");
|
||||
DOM.heightLevel = document.getElementById("height-level");
|
||||
DOM.historyHeader = document.getElementById("history-header");
|
||||
DOM.historyBody = document.getElementById("history-body");
|
||||
DOM.bars = document.querySelectorAll(".sig-bar");
|
||||
@@ -51,9 +63,9 @@ export function setServerTime(v: string): void {
|
||||
export function setStatus(v: string, isError: boolean = false): void {
|
||||
if (!DOM.status) return;
|
||||
DOM.status.textContent = v;
|
||||
|
||||
|
||||
DOM.status.classList.remove("status-ok", "status-error", "status-lost");
|
||||
|
||||
|
||||
if (isError) {
|
||||
if (v === "Нет связи") {
|
||||
DOM.status.classList.add("status-lost");
|
||||
@@ -72,7 +84,7 @@ export function setMeta(v: string): void {
|
||||
export function setBars(p: number, noConnection: boolean = false): void {
|
||||
const el = DOM.barFill;
|
||||
if (!el) return;
|
||||
|
||||
|
||||
if (noConnection) {
|
||||
el.style.width = "0%";
|
||||
el.classList.add("no-connection");
|
||||
@@ -85,7 +97,7 @@ export function setBars(p: number, noConnection: boolean = false): void {
|
||||
export function setSignal(level: number, noConnection: boolean = false): void {
|
||||
const bars = DOM.bars;
|
||||
if (!bars || bars.length < 3) return;
|
||||
|
||||
|
||||
bars.forEach(bar => {
|
||||
bar.classList.remove("active-1", "active-2", "active-3", "dim", "no-connection");
|
||||
if (noConnection) {
|
||||
@@ -94,9 +106,9 @@ export function setSignal(level: number, noConnection: boolean = false): void {
|
||||
bar.classList.add("dim");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
if (noConnection) return;
|
||||
|
||||
|
||||
if (level >= 1) {
|
||||
bars[0].classList.add("active-1");
|
||||
bars[0].classList.remove("dim");
|
||||
@@ -111,7 +123,6 @@ export function setSignal(level: number, noConnection: boolean = false): void {
|
||||
}
|
||||
}
|
||||
|
||||
// ФУНКЦИЮ ДЛЯ ИНДИКАТОРА ЗВУКА
|
||||
export function setAudioIndicator(active: boolean, level: number = 0): void {
|
||||
const el = DOM.audioIndicator;
|
||||
if (!el) return;
|
||||
@@ -120,7 +131,6 @@ export function setAudioIndicator(active: boolean, level: number = 0): void {
|
||||
el.textContent = `🔊 ${level}`;
|
||||
el.style.color = '#00ff88';
|
||||
el.style.opacity = '1';
|
||||
// Анимация пульсации
|
||||
el.style.animation = 'pulse 1s ease-in-out infinite';
|
||||
} else {
|
||||
el.textContent = '🔇';
|
||||
@@ -128,4 +138,175 @@ export function setAudioIndicator(active: boolean, level: number = 0): void {
|
||||
el.style.opacity = '0.5';
|
||||
el.style.animation = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function setSoundLevel(level: number): void {
|
||||
if (DOM.bar2) {
|
||||
const percent = Math.max(0, Math.min(100, level));
|
||||
DOM.bar2.style.width = `${percent}%`;
|
||||
}
|
||||
if (DOM.soundLevel) {
|
||||
DOM.soundLevel.textContent = Math.round(level).toString();
|
||||
}
|
||||
}
|
||||
|
||||
export function setHeightLevel(level: number): void {
|
||||
if (DOM.bar3) {
|
||||
const percent = Math.max(0, Math.min(100, level));
|
||||
DOM.bar3.style.width = `${percent}%`;
|
||||
}
|
||||
if (DOM.heightLevel) {
|
||||
DOM.heightLevel.textContent = Math.round(level).toString();
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 8 КАНАЛОВ =====
|
||||
export interface ChannelState {
|
||||
value: number;
|
||||
strength: number;
|
||||
expanded: boolean;
|
||||
}
|
||||
|
||||
export const channels: ChannelState[] = Array(8).fill(null).map(() => ({
|
||||
value: 0,
|
||||
strength: 0,
|
||||
expanded: true
|
||||
}));
|
||||
|
||||
let channelElements: any[] = [];
|
||||
|
||||
export function initChannels() {
|
||||
const container = document.getElementById('channelsContainer');
|
||||
if (!container) return;
|
||||
|
||||
container.innerHTML = '';
|
||||
channelElements = [];
|
||||
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'channel-item';
|
||||
item.dataset.channel = String(i);
|
||||
|
||||
item.innerHTML = `
|
||||
<div class="channel-header" data-channel="${i}">
|
||||
<span class="channel-label">Канал ${i + 1}</span>
|
||||
<span style="display:flex;align-items:center;gap:6px;">
|
||||
<span class="channel-value" id="ch-val-${i}">--</span>
|
||||
<span class="channel-toggle" id="ch-toggle-${i}">▼</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="channel-body" id="ch-body-${i}">
|
||||
<div class="channel-progress">
|
||||
<div class="progress">
|
||||
<div class="fill" id="ch-bar-${i}" style="width:0%"></div>
|
||||
</div>
|
||||
<div class="signal-icon" id="ch-signal-${i}">
|
||||
<div class="sig-bar sig-bar-1"></div>
|
||||
<div class="sig-bar sig-bar-2"></div>
|
||||
<div class="sig-bar sig-bar-3"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="channel-stats" id="ch-stats-${i}">--</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
container.appendChild(item);
|
||||
|
||||
channelElements[i] = {
|
||||
item: item,
|
||||
value: document.getElementById(`ch-val-${i}`),
|
||||
bar: document.getElementById(`ch-bar-${i}`),
|
||||
signal: document.getElementById(`ch-signal-${i}`),
|
||||
stats: document.getElementById(`ch-stats-${i}`),
|
||||
toggle: document.getElementById(`ch-toggle-${i}`),
|
||||
body: document.getElementById(`ch-body-${i}`)
|
||||
};
|
||||
|
||||
const header = item.querySelector('.channel-header');
|
||||
header?.addEventListener('click', () => {
|
||||
toggleChannel(i);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function toggleChannel(index: number) {
|
||||
const ch = channels[index];
|
||||
ch.expanded = !ch.expanded;
|
||||
|
||||
const el = channelElements[index];
|
||||
if (!el) return;
|
||||
|
||||
if (ch.expanded) {
|
||||
el.body.classList.remove('hidden');
|
||||
el.toggle.textContent = '▼';
|
||||
} else {
|
||||
el.body.classList.add('hidden');
|
||||
el.toggle.textContent = '▶';
|
||||
}
|
||||
}
|
||||
|
||||
export function setChannelValue(index: number, value: number, strength: number) {
|
||||
if (index < 0 || index > 7) return;
|
||||
|
||||
const ch = channels[index];
|
||||
const el = channelElements[index];
|
||||
if (!el) return;
|
||||
|
||||
ch.value = value;
|
||||
ch.strength = strength;
|
||||
|
||||
const percent = (value / 31) * 100;
|
||||
el.bar.style.width = `${percent}%`;
|
||||
el.value.textContent = value.toString();
|
||||
|
||||
const bars = el.signal?.querySelectorAll('.sig-bar');
|
||||
if (bars) {
|
||||
bars.forEach((bar: Element, i: number) => {
|
||||
const isActive = i < strength;
|
||||
(bar as HTMLElement).style.opacity = isActive ? '1' : '0.2';
|
||||
});
|
||||
}
|
||||
|
||||
const strengthNames = ['Отсутствует', 'Низкая', 'Средняя', 'Высокая'];
|
||||
el.stats.textContent = `${value}/31 | ${strengthNames[strength] || '---'}`;
|
||||
el.item.classList.remove('no-data');
|
||||
}
|
||||
|
||||
export function updateAllChannels(rawData: number[]) {
|
||||
const latest = new Map<number, number>();
|
||||
for (const byte of rawData) {
|
||||
const channel = (byte >> 5) & 0x07;
|
||||
const value = byte & 0x1F;
|
||||
latest.set(channel, value);
|
||||
}
|
||||
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const value = latest.get(i) ?? 0;
|
||||
const strength = value >= 25 ? 3 : value >= 15 ? 2 : value >= 5 ? 1 : 0;
|
||||
setChannelValue(i, value, strength);
|
||||
}
|
||||
}
|
||||
|
||||
// Инициализация при загрузке
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initChannels();
|
||||
|
||||
const expandAll = document.getElementById('expandAllBtn');
|
||||
const collapseAll = document.getElementById('collapseAllBtn');
|
||||
|
||||
if (expandAll) {
|
||||
expandAll.addEventListener('click', () => {
|
||||
channels.forEach((ch, i) => {
|
||||
if (!ch.expanded) toggleChannel(i);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (collapseAll) {
|
||||
collapseAll.addEventListener('click', () => {
|
||||
channels.forEach((ch, i) => {
|
||||
if (ch.expanded) toggleChannel(i);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user