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