* { box-sizing: border-box; } body { margin: 0; font-family: "Trebuchet MS", "Verdana", "Georgia", serif; background: radial-gradient(circle at 10% 10%, #ffefd5 0%, #fce4ec 35%, #e1f5fe 100%); color: #1d2a38; min-height: 100vh; } .shell { max-width: 1200px; margin: 0 auto; padding: 24px; } .header h1 { margin: 0; letter-spacing: 0.01em; } .header p { margin-top: 6px; color: #344357; } .panel { border-radius: 14px; background: rgba(255, 255, 255, 0.68); border: 1px solid rgba(29, 42, 56, 0.1); padding: 16px; margin-top: 14px; box-shadow: 0 20px 40px -30px rgba(0, 0, 0, 0.35); } .api-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; } #apiBase { min-width: 260px; flex: 1; padding: 10px 12px; border-radius: 8px; border: 1px solid #b6c7de; } button { border: 0; cursor: pointer; border-radius: 8px; padding: 10px 14px; background: #2f5d95; color: #fff; font-weight: 600; } button:hover { background: #254a75; } .status { padding: 8px 12px; border-radius: 999px; background: #ffecb3; font-weight: 600; } .hint { color: #445566; margin-bottom: 0; margin-top: 8px; } .serial-block { margin-top: 12px; border-top: 1px dashed #d8e2ef; padding-top: 12px; } .serial-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; } #comPortSelect { min-width: 180px; padding: 10px 12px; border-radius: 8px; border: 1px solid #b6c7de; background: #fff; } .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; } .card h2 { margin-top: 0; } .list { display: flex; flex-direction: column; gap: 12px; } .item { border: 1px solid #d8e2ef; border-radius: 10px; padding: 12px; background: #fff; box-shadow: 0 10px 20px -22px rgba(0, 0, 0, 0.4); } .item-head { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; } .value { font-size: 26px; font-weight: 700; margin: 4px 0; } .sub { font-size: 13px; color: #526379; } .controls { margin-top: 10px; display: grid; gap: 8px; } label { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; font-size: 13px; color: #304257; } .controls input[type="number"], .controls input[type="range"] { width: 120px; padding: 6px 8px; } .toggle { display: inline-flex; background: #edf2f7; border-radius: 10px; padding: 2px; width: fit-content; } .toggle button { background: #dde6f2; color: #1f2d3c; } .toggle button.active { background: #2f5d95; color: #fff; } .modeRow { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; } .valveAuto, .valveManual { border-top: 1px dashed #d6e0eb; margin-top: 8px; padding-top: 8px; display: grid; gap: 6px; } .hidden { display: none; } .status-ok { color: #0f7f2f; font-weight: 600; } .status-warn { color: #9a5700; font-weight: 600; } .status-error { color: #a61f1f; font-weight: 600; } .footer { margin-top: 12px; color: #3b4a60; min-height: 28px; } @media (max-width: 880px) { .grid { grid-template-columns: 1fr; } .api-row { align-items: stretch; } } /* Compact 16 sensors / 32 valves layout */ .shell { max-width: 1540px; padding: 18px; } .hero { padding: 22px; margin-bottom: 14px; } .hero h1 { margin: 0 0 8px; font-size: clamp(28px, 4vw, 54px); line-height: 0.95; } .hero p { max-width: 780px; margin: 0; font-size: 15px; } .grid { grid-template-columns: minmax(300px, 0.72fr) minmax(620px, 1.7fr); gap: 14px; } .panel { padding: 14px; border-radius: 18px; } .panel h2 { margin: 0 0 10px; font-size: 18px; } .controls, .connection-bar, .api-bar, .settings-grid { gap: 8px; } button, select, input { min-height: 34px; padding: 7px 10px; border-radius: 10px; font-size: 13px; } .list { display: grid; gap: 8px; } #sensors { grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); } #valves { grid-template-columns: repeat(auto-fill, minmax(215px, 1fr)); } .item.compact-item { gap: 8px; padding: 10px; border-radius: 14px; min-width: 0; } .compact-head, .compact-main, .compact-controls, .compact-actions, .inline-control, .range-control { display: flex; align-items: center; gap: 8px; } .compact-head, .compact-main, .compact-actions { justify-content: space-between; } .compact-head h3, .compact-head h4 { margin: 0; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 14px; } .compact-main { flex-wrap: wrap; } .compact-controls { align-items: stretch; flex-direction: column; gap: 7px; } .compact-actions { gap: 6px; } .compact-actions button, .mini-btn { min-height: 30px; padding: 5px 9px; border-radius: 9px; font-size: 12px; } .inline-control, .range-control { min-width: 0; flex: 1 1 auto; padding: 0; border: 0; background: transparent; font-size: 12px; color: var(--muted); } .inline-control input { width: 74px; } .range-control input[type="range"] { flex: 1 1 90px; min-width: 90px; } .small-value { font-weight: 800; font-size: 15px; color: var(--ink); } .sensor-item .small-value { font-size: 20px; } .sensor-zone, .valve-zone, .status-pill { flex: 0 0 auto; padding: 4px 8px; border-radius: 999px; background: rgba(17, 38, 60, 0.08); color: var(--muted); font-size: 11px; font-weight: 800; } .status-pill.open { background: rgba(34, 166, 112, 0.18); color: #0d6b45; } .status-pill.closed { background: rgba(221, 91, 81, 0.15); color: #9c2f26; } .toggle { flex: 0 0 auto; gap: 3px; padding: 3px; border-radius: 11px; } .toggle button { min-height: 28px; padding: 4px 8px; border-radius: 8px; font-size: 11px; } .meta-row, .log, .hint { font-size: 12px; } @media (max-width: 1100px) { .grid { grid-template-columns: 1fr; } } @media (max-width: 560px) { .shell { padding: 10px; } .hero, .panel { padding: 12px; } #sensors, #valves { grid-template-columns: 1fr; } } /* Channel cards like MCU panel */ .valve-item.channel-card { padding: 9px; background-image: linear-gradient(135deg, rgba(255, 255, 255, 0.9), rgba(232, 236, 222, 0.72)); } #valves { grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); } .channel-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; font-size: 12px; color: var(--muted); } .channel-head strong { font-size: 15px; color: var(--ink); } .channel-title { display: grid; gap: 2px; min-width: 0; } .channel-title small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 10px; font-weight: 800; color: var(--muted); } .channel-head span, .channel-connect, .valve-state-row span { display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; } .channel-lamp { display: inline-block; width: 17px; height: 17px; border-radius: 50%; border: 1px solid rgba(19, 32, 47, 0.28); box-shadow: inset 0 1px 2px rgba(255, 255, 255, 0.8), 0 1px 3px rgba(19, 32, 47, 0.22); background: #d8dde2; } .channel-lamp.on { background: radial-gradient(circle at 35% 30%, #b5ff8d, #159400 62%, #0e6000); } .channel-lamp.off { background: linear-gradient(145deg, #f5f6f8, #cfd5da); } .channel-lamp.alarm { background: radial-gradient(circle at 35% 30%, #ffb9b9, #ff1010 62%, #930000); } .channel-id-grid { display: grid; grid-template-columns: 0.72fr 0.82fr 0.9fr 0.9fr; align-items: end; gap: 6px; } .channel-id-grid label { display: grid; gap: 3px; min-width: 0; font-size: 11px; color: var(--muted); } .channel-id-grid input, .channel-id-grid select { width: 100%; min-height: 28px; padding: 4px 7px; border-radius: 7px; font-size: 12px; } .channel-connect { min-height: 28px; justify-content: center; padding-bottom: 3px; font-size: 11px; color: var(--muted); } .channel-body { display: grid; grid-template-columns: 44px minmax(0, 1fr); gap: 8px; align-items: stretch; } .temperature-widget { display: grid; grid-template-columns: 19px 15px; grid-template-rows: 1fr auto; gap: 3px; min-height: 152px; } .temp-scale { display: flex; flex-direction: column; justify-content: space-between; align-items: flex-end; font-size: 10px; line-height: 1; color: #0d3550; } .temp-bar { position: relative; overflow: hidden; align-self: stretch; border: 1px solid rgba(13, 53, 80, 0.4); background: repeating-linear-gradient(to top, rgba(13, 53, 80, 0.1) 0 1px, transparent 1px 18px), #f6fbff; } .temp-bar b { position: absolute; left: 1px; right: 1px; bottom: 1px; min-height: 3px; background: linear-gradient(180deg, #ff6a3d, #ff1d1d); } .temp-now { grid-column: 1 / -1; text-align: center; font-size: 11px; font-weight: 800; color: var(--ink); } .channel-workarea { display: grid; gap: 7px; min-width: 0; } .angle-panel, .position-panel { display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 4px 8px; padding: 7px 9px; border: 1px solid rgba(19, 32, 47, 0.12); border-radius: 10px; background: rgba(255, 255, 255, 0.64); } .angle-panel span, .position-panel span { font-size: 12px; color: var(--muted); } .angle-panel strong, .position-panel strong { font-size: 20px; color: var(--ink); } .angle-panel small, .position-panel small { grid-column: 1 / -1; font-size: 11px; color: var(--muted); } .position-panel { background: rgba(245, 251, 255, 0.72); } .channel-data-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 5px; } .channel-data-grid div { display: grid; gap: 2px; min-width: 0; padding: 6px 7px; border: 1px solid rgba(19, 32, 47, 0.1); border-radius: 8px; background: rgba(255, 255, 255, 0.58); } .channel-data-grid span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 10px; color: var(--muted); } .channel-data-grid strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; color: var(--ink); } .delta.ok { color: #0d6b45; } .delta.hot { color: #b73520; } .delta.cold { color: #1759a6; } .channel-position { gap: 6px; font-size: 11px; } .valve-state-row, .channel-actions { display: flex; align-items: center; justify-content: center; gap: 8px; flex-wrap: wrap; } .valve-state-row { font-size: 11px; color: var(--ink); } .channel-actions button { min-height: 28px; padding: 4px 10px; border-radius: 8px; font-size: 11px; } .channel-mode-line { justify-content: space-between; gap: 5px; } .channel-mode-line .toggle { margin-right: auto; } @media (max-width: 560px) { .channel-id-grid { grid-template-columns: 1fr; align-items: stretch; } .channel-connect { justify-content: flex-start; } } /* Full 16 channel operator blocks */ #sensors, #valves { grid-template-columns: repeat(3, minmax(0, 1fr)); } .full-channel-card { gap: 9px; } .full-channel-id-grid { grid-template-columns: 0.7fr 0.8fr 1.5fr; } .full-channel-body { grid-template-columns: 48px minmax(0, 1fr); } .top-metrics-row { display: grid; grid-template-columns: 1fr; gap: 7px; } .main-position-panel { background: rgba(228, 247, 255, 0.82); } .main-position-panel strong { font-size: 28px; line-height: 1; } .full-channel-data-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } .full-state-row { justify-content: space-around; padding: 6px 8px; border-radius: 10px; background: rgba(255, 255, 255, 0.56); border: 1px solid rgba(19, 32, 47, 0.1); } @media (max-width: 1260px) { #sensors, #valves { grid-template-columns: repeat(2, minmax(0, 1fr)); } } @media (max-width: 900px) { #sensors, #valves { grid-template-columns: 1fr; } .full-channel-id-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } .full-channel-data-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } } @media (max-width: 560px) { .top-metrics-row { grid-template-columns: 1fr; } .full-channel-id-grid, .full-channel-data-grid { grid-template-columns: 1fr; } } /* Presentation upgrade for operator dashboard */ :root { --panel-blue: #123a5f; --panel-blue-2: #1f5f93; --panel-steel: #eef4f8; --panel-line: rgba(31, 95, 147, 0.24); --ok-green: #159447; --warn-red: #d63b2a; --amber: #f5b43f; } body { background: radial-gradient(circle at 12% 12%, rgba(45, 111, 167, 0.18), transparent 30%), radial-gradient(circle at 88% 4%, rgba(245, 180, 63, 0.22), transparent 24%), linear-gradient(135deg, #f6eadb 0%, #f7f1f5 42%, #e8f1fb 100%); } .shell { max-width: 1680px; } .hero { position: relative; overflow: hidden; border: 1px solid rgba(18, 58, 95, 0.14); box-shadow: 0 18px 60px rgba(18, 58, 95, 0.14); background: linear-gradient(120deg, rgba(255, 255, 255, 0.92), rgba(234, 243, 250, 0.88)), repeating-linear-gradient(90deg, rgba(18, 58, 95, 0.04) 0 1px, transparent 1px 18px); } .hero::after { content: ""; position: absolute; right: -90px; top: -110px; width: 320px; height: 320px; border-radius: 50%; background: conic-gradient(from 220deg, rgba(31, 95, 147, 0.22), rgba(245, 180, 63, 0.2), rgba(31, 95, 147, 0.08)); filter: blur(2px); } .hero h1 { position: relative; z-index: 1; letter-spacing: -0.035em; color: #0d2e4d; text-shadow: 0 1px 0 rgba(255, 255, 255, 0.9); } .hero p { position: relative; z-index: 1; } .panel { border: 1px solid rgba(18, 58, 95, 0.14); box-shadow: 0 16px 44px rgba(18, 58, 95, 0.12); background: linear-gradient(180deg, rgba(255, 255, 255, 0.94), rgba(246, 250, 253, 0.92)), repeating-linear-gradient(0deg, rgba(18, 58, 95, 0.025) 0 1px, transparent 1px 20px); } .panel h2 { display: flex; align-items: center; gap: 10px; color: #0d2e4d; } .panel h2::before { content: ""; width: 11px; height: 28px; border-radius: 999px; background: linear-gradient(180deg, var(--panel-blue-2), var(--amber)); box-shadow: 0 0 0 4px rgba(31, 95, 147, 0.08); } button { box-shadow: 0 8px 18px rgba(18, 58, 95, 0.14); transition: transform 0.12s ease, box-shadow 0.12s ease, filter 0.12s ease; } button:hover { transform: translateY(-1px); filter: saturate(1.08); box-shadow: 0 11px 24px rgba(18, 58, 95, 0.18); } input, select { border: 1px solid rgba(18, 58, 95, 0.22); box-shadow: inset 0 1px 2px rgba(18, 58, 95, 0.06); background: linear-gradient(180deg, #ffffff, #f7fbff); } .full-channel-card { position: relative; overflow: hidden; border: 1px solid rgba(18, 58, 95, 0.22); box-shadow: 0 14px 32px rgba(18, 58, 95, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.86); background: linear-gradient(145deg, rgba(255, 255, 255, 0.96), rgba(232, 241, 248, 0.95)), repeating-linear-gradient(90deg, rgba(18, 58, 95, 0.035) 0 1px, transparent 1px 16px); } .full-channel-card::before { content: ""; position: absolute; inset: 0 auto 0 0; width: 6px; background: linear-gradient(180deg, var(--panel-blue-2), var(--ok-green), var(--amber)); } .full-channel-card::after { content: ""; position: absolute; inset: 0 0 auto 0; height: 44px; background: linear-gradient(90deg, rgba(18, 58, 95, 0.1), transparent 60%); pointer-events: none; } .channel-head, .channel-id-grid, .channel-body { position: relative; z-index: 1; } .channel-head { min-height: 42px; padding: 8px 10px 8px 14px; margin: -2px -2px 2px 0; border-radius: 12px; background: linear-gradient(90deg, rgba(18, 58, 95, 0.1), rgba(255, 255, 255, 0.64)); } .channel-title strong { font-size: 18px; letter-spacing: -0.02em; } .channel-title small { font-size: 11px; color: #42647f; } .channel-lamp { width: 20px; height: 20px; border: 2px solid rgba(255, 255, 255, 0.88); box-shadow: inset 0 1px 3px rgba(255, 255, 255, 0.8), 0 0 0 1px rgba(18, 58, 95, 0.16), 0 3px 9px rgba(18, 58, 95, 0.22); } .channel-lamp.on { box-shadow: inset 0 1px 3px rgba(255, 255, 255, 0.82), 0 0 0 1px rgba(21, 148, 71, 0.28), 0 0 14px rgba(21, 148, 71, 0.55); } .channel-lamp.alarm { box-shadow: inset 0 1px 3px rgba(255, 255, 255, 0.82), 0 0 0 1px rgba(214, 59, 42, 0.28), 0 0 14px rgba(214, 59, 42, 0.48); } .channel-id-grid { padding: 9px; border-radius: 12px; border: 1px solid rgba(18, 58, 95, 0.12); background: rgba(255, 255, 255, 0.62); } .channel-id-grid label { font-weight: 800; text-transform: uppercase; letter-spacing: 0.035em; } .channel-id-grid input, .channel-id-grid select { font-weight: 800; color: #0d2e4d; } .temperature-widget { padding: 8px 4px; border-radius: 14px; border: 1px solid rgba(18, 58, 95, 0.16); background: linear-gradient(180deg, #fafdff, #eaf3f8); box-shadow: inset 0 1px 4px rgba(18, 58, 95, 0.08); } .temp-bar { border-radius: 8px; border-color: rgba(18, 58, 95, 0.34); box-shadow: inset 0 2px 8px rgba(18, 58, 95, 0.12); } .temp-bar b { border-radius: 7px 7px 2px 2px; background: linear-gradient(180deg, #ffb347, #ff4f2d 52%, #d91414); box-shadow: 0 0 12px rgba(255, 79, 45, 0.42); } .temp-now { color: #0d2e4d; } .top-metrics-row { align-items: stretch; } .position-panel, .angle-panel { border-color: rgba(31, 95, 147, 0.22); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.8), 0 8px 18px rgba(18, 58, 95, 0.08); } .position-panel span, .angle-panel span { font-weight: 900; text-transform: uppercase; letter-spacing: 0.04em; } .position-panel strong, .angle-panel strong { color: #0b365b; text-shadow: 0 1px 0 #fff; } .main-position-panel { background: linear-gradient(135deg, rgba(226, 246, 255, 0.96), rgba(255, 255, 255, 0.9)), radial-gradient(circle at 90% 15%, rgba(31, 95, 147, 0.15), transparent 35%); } .angle-panel { background: linear-gradient(135deg, rgba(255, 244, 218, 0.96), rgba(255, 255, 255, 0.9)), radial-gradient(circle at 90% 15%, rgba(245, 180, 63, 0.22), transparent 35%); } .channel-data-grid div { border-color: rgba(18, 58, 95, 0.12); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.78); background: linear-gradient(180deg, rgba(255, 255, 255, 0.82), rgba(239, 246, 251, 0.76)); } .channel-data-grid span { font-weight: 900; text-transform: uppercase; letter-spacing: 0.035em; } .channel-data-grid strong { font-size: 13px; } .full-state-row { background: linear-gradient(90deg, rgba(21, 148, 71, 0.08), rgba(255, 255, 255, 0.72), rgba(214, 59, 42, 0.08)); } .channel-actions { padding-top: 2px; } .quickPosition[data-position="100"] { background: linear-gradient(180deg, #2eaf68, #128246); color: #fff; } .quickPosition[data-position="0"] { background: linear-gradient(180deg, #f06a57, #c93024); color: #fff; } .channel-mode .active { background: linear-gradient(180deg, #234d73, #123a5f); color: #fff; } .hidden { display: none !important; } #modbusTransport { min-width: 160px; } #tcpHost { min-width: 150px; } #tcpPort { width: 92px; } #modbusSlaveId { width: 78px; } .serial-row label { font-weight: 800; color: #24445e; } /* Single full-width channel workspace */ .grid { grid-template-columns: 1fr; } .panel:has(#valves) { display: block; } .grid > *:has(#valves), #valves { display: none; }