feat: 화재 알람 데모 SCADA 스타일 경고 인디케이터 + ZONE 17 콘텐츠 정렬
Build & Deploy to K8s / build-and-deploy (push) Successful in 5m29s
Build & Deploy to K8s / build-and-deploy (push) Successful in 5m29s
- 각 zone 중앙에 SCADA 스타일 경고 비콘 자동 삽입 (펄스 링 + 빨간 배지 + 노란 경고 삼각형) - WARN/ALARM 별 색상 분리 (CSS 변수 --b-* 로 SVG <use> shadow DOM 통과) - WARN: 노란 톤 + 정적 표시 - ALARM: 빨간 톤 + drop-shadow + brightness 깜빡임 - zone-area 점선 테두리: warn(노란/얇음), alarm(빨간/굵음+pulse) - ZONE 17 콘텐츠를 ZONE 18 비례에 맞춰 재배치 (label y 453→396) — manual-call 과 라벨 겹침 해소 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -789,6 +789,33 @@
|
||||
<path d="M6 7H36M6 13H36M6 19H36M6 25H36" class="stair-line" />
|
||||
</symbol>
|
||||
|
||||
<symbol id="beacon" viewBox="0 0 100 100" overflow="visible">
|
||||
<!-- 펄스 링 1 (외부 확산) -->
|
||||
<circle cx="50" cy="50" r="32" fill="none" stroke="var(--b-pulse)" stroke-width="3" opacity="0">
|
||||
<animate attributeName="r" values="24;52" dur="1.4s" repeatCount="indefinite" />
|
||||
<animate attributeName="opacity" values=".9;0" dur="1.4s" repeatCount="indefinite" />
|
||||
<animate attributeName="stroke-width" values="3;0.6" dur="1.4s" repeatCount="indefinite" />
|
||||
</circle>
|
||||
<!-- 펄스 링 2 (시간 지연) -->
|
||||
<circle cx="50" cy="50" r="32" fill="none" stroke="var(--b-pulse)" stroke-width="3" opacity="0">
|
||||
<animate attributeName="r" values="24;52" dur="1.4s" begin="0.7s" repeatCount="indefinite" />
|
||||
<animate attributeName="opacity" values=".9;0" dur="1.4s" begin="0.7s" repeatCount="indefinite" />
|
||||
<animate attributeName="stroke-width" values="3;0.6" dur="1.4s" begin="0.7s" repeatCount="indefinite" />
|
||||
</circle>
|
||||
|
||||
<!-- 메인 원형 배지 (외곽) -->
|
||||
<circle cx="50" cy="50" r="32" fill="var(--b-ring-fill)" stroke="var(--b-ring-stroke)" stroke-width="2.4" />
|
||||
<!-- 메인 원형 배지 (내부) -->
|
||||
<circle cx="50" cy="50" r="26" fill="var(--b-core-fill)" stroke="var(--b-core-stroke)" stroke-width="1.6" filter="url(#alarm-glow)" />
|
||||
|
||||
<!-- 경고 삼각형 -->
|
||||
<path d="M50 30 L72 64 L28 64 Z" fill="var(--b-tri-fill)" stroke="var(--b-tri-stroke)" stroke-width="3.4" stroke-linejoin="round" stroke-linecap="round" />
|
||||
|
||||
<!-- 느낌표 -->
|
||||
<line x1="50" y1="42" x2="50" y2="55" stroke="var(--b-mark)" stroke-width="3.4" stroke-linecap="round" />
|
||||
<circle cx="50" cy="60" r="2.2" fill="var(--b-mark)" />
|
||||
</symbol>
|
||||
|
||||
<style>
|
||||
.screen { fill: url(#screen-bg); }
|
||||
.board { fill: url(#board-bg); stroke: #edf9ff; stroke-width: 2.2; }
|
||||
@@ -819,8 +846,8 @@
|
||||
.zone-area.za-factory1 { fill: rgba(150,90,210,.18); }
|
||||
.zone-area.za-office { fill: rgba(140,160,190,.14); }
|
||||
.zone:hover .zone-area { fill: rgba(255,255,255,.075); }
|
||||
.zone.warn .zone-area { fill: rgba(255,207,78,.32); stroke: #ffe595; }
|
||||
.zone.alarm .zone-area { fill: rgba(255,56,82,.42); stroke: #ffffff; animation: alarmPulse .82s infinite; }
|
||||
.zone.warn .zone-area { fill: rgba(255,207,78,.32); stroke: #ffcf4e; stroke-width: 2; stroke-dasharray: 6 4; }
|
||||
.zone.alarm .zone-area { fill: rgba(255,56,82,.42); stroke: #ff3852; stroke-width: 2.6; stroke-dasharray: 8 4; animation: alarmPulse .82s infinite; }
|
||||
|
||||
.building-name { font-family: Arial, 'Noto Sans KR', sans-serif; font-size: 18px; font-weight: 900; fill: #ffffff; text-anchor: middle; paint-order: stroke; stroke: rgba(0,0,0,.58); stroke-width: 3px; }
|
||||
.building-sub { font-family: Arial, 'Noto Sans KR', sans-serif; font-size: 8px; font-weight: 800; fill: #e8fff1; text-anchor: middle; letter-spacing: .55px; }
|
||||
@@ -851,6 +878,41 @@
|
||||
.legend-text { font-family: Arial, 'Noto Sans KR', sans-serif; font-size: 8px; font-weight: 800; fill: #ffffff; }
|
||||
|
||||
@keyframes alarmPulse { 0%,100% { opacity: 1; } 50% { opacity: .38; } }
|
||||
|
||||
/* === 경고 인디케이터 (펄스 + 배지 + 경고 삼각형) === */
|
||||
.zone-beacon {
|
||||
visibility: hidden; opacity: 0; pointer-events: none;
|
||||
transition: opacity .2s ease-out; overflow: visible;
|
||||
}
|
||||
/* WARN — 노란/주황 톤 (주의) */
|
||||
.zone.warn .zone-beacon {
|
||||
visibility: visible; opacity: .95;
|
||||
--b-pulse: #ffcf4e;
|
||||
--b-ring-fill: rgba(255,207,78,0.18);
|
||||
--b-ring-stroke: #ffcf4e;
|
||||
--b-core-fill: rgba(255,180,40,0.55);
|
||||
--b-core-stroke: #ffe089;
|
||||
--b-tri-fill: rgba(120,80,0,0.4);
|
||||
--b-tri-stroke: #1f1300;
|
||||
--b-mark: #1f1300;
|
||||
}
|
||||
/* ALARM — 빨간 톤 + 깜빡임 (위험) */
|
||||
.zone.alarm .zone-beacon {
|
||||
visibility: visible; opacity: 1;
|
||||
--b-pulse: #ff3852;
|
||||
--b-ring-fill: rgba(255,56,82,0.22);
|
||||
--b-ring-stroke: #ff3852;
|
||||
--b-core-fill: rgba(255,56,82,0.65);
|
||||
--b-core-stroke: #ff5878;
|
||||
--b-tri-fill: rgba(180,20,40,0.55);
|
||||
--b-tri-stroke: #ffea00;
|
||||
--b-mark: #ffea00;
|
||||
animation: beaconAlarmFlash .55s ease-in-out infinite alternate;
|
||||
}
|
||||
@keyframes beaconAlarmFlash {
|
||||
from { filter: drop-shadow(0 0 4px #ff3852) brightness(1); }
|
||||
to { filter: drop-shadow(0 0 18px #ff3852) brightness(1.25); }
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
|
||||
@@ -1107,9 +1169,9 @@
|
||||
|
||||
<g id="zone-17" class="zone" data-zone="ZONE 17">
|
||||
<rect x="696" y="306" width="96" height="160" class="zone-area za-office" />
|
||||
<text x="744" y="453" class="zone-label">ZONE 17</text>
|
||||
<use href="#sensor" x="727" y="407" width="34" height="34" />
|
||||
<use href="#manual-call" x="701" y="439" width="24" height="24" />
|
||||
<text x="744" y="396" class="zone-label">ZONE 17</text>
|
||||
<use href="#sensor" x="727" y="353" width="34" height="34" />
|
||||
<use href="#manual-call" x="701" y="435" width="24" height="24" />
|
||||
</g>
|
||||
|
||||
<g id="zone-18" class="zone" data-zone="ZONE 18">
|
||||
@@ -1857,6 +1919,30 @@
|
||||
});
|
||||
}
|
||||
|
||||
// 각 zone 중앙에 경광등 자동 삽입 (alarm/warn 시 자동 표시)
|
||||
function attachBeacons() {
|
||||
const NS = "http://www.w3.org/2000/svg";
|
||||
const W = 76, H = 76; // 정사각 viewBox 100x100
|
||||
document.querySelectorAll(".zone").forEach(group => {
|
||||
const rect = group.querySelector(".zone-area");
|
||||
if (!rect) return;
|
||||
const x = parseFloat(rect.getAttribute("x"));
|
||||
const y = parseFloat(rect.getAttribute("y"));
|
||||
const w = parseFloat(rect.getAttribute("width"));
|
||||
const h = parseFloat(rect.getAttribute("height"));
|
||||
const cx = x + w / 2;
|
||||
const cy = y + h / 2;
|
||||
const use = document.createElementNS(NS, "use");
|
||||
use.setAttribute("href", "#beacon");
|
||||
use.setAttribute("x", cx - W / 2);
|
||||
use.setAttribute("y", cy - H / 2 - 6); // zone 중앙 약간 위
|
||||
use.setAttribute("width", W);
|
||||
use.setAttribute("height", H);
|
||||
use.setAttribute("class", "zone-beacon");
|
||||
group.appendChild(use);
|
||||
});
|
||||
}
|
||||
|
||||
setClock();
|
||||
setInterval(() => {
|
||||
setClock();
|
||||
@@ -1867,6 +1953,7 @@
|
||||
renderAll();
|
||||
updatePressure();
|
||||
bindSvgClick();
|
||||
attachBeacons();
|
||||
addLog(`<span style="color:#11722a;font-weight:900">SYSTEM READY</span> fire monitoring system initialized.`);
|
||||
addLog(`<span style="color:#11722a;font-weight:900">NORMAL</span> all devices communication OK.`);
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user