관리자 사이트 7페이지의 파편화된 인라인 스타일(색상 리터럴 160개, 페이지별 <style>)을 단일 디자인시스템으로 통합. 신규 php/assets/app.css — "Slate NOC Console": - CSS 변수 단일 토큰(라이트/다크 prefers-color-scheme/인쇄 강제라이트). - 한글 시스템폰트 우선(폐쇄망 웹폰트 미로드 → 오프라인 일관 렌더), 외부 리소스 0. - 상태 3중 인코딩(색+도형[점/좌측레일/글리프]+텍스트) — 색맹·흑백 인쇄 대비. - WCAG AA 대비(토큰 쌍 실측), 본문/캡션 하한 .8125rem. 7페이지 리팩터(dashboard/login/monthly_report/security_evidence/setup_wizard/ setup_mfa/blocked): 인라인 <style> 제거→app.css 링크, PHP 색 삼항→클래스, 생 hex 0, PHP 로직 무변경. make_deploy_bundles.py: TEXT_EXTS += .css. 적대적 다차원 리뷰(미학·CSS아키텍처·통합일관성·a11y·slop) 확정 18건 반영: - [HIGH] .ok 배너 셀렉터가 .badge.ok/.quick-tile.ok 와 충돌 → 정상 배지가 사각형으로 깨지던 것 .banner-ok 네임스페이스로 수정. - 자동새로고침 일시정지 토글(WCAG 2.2.1)+스크롤/details 복원, 차트 임계선+ 최신값 마커, 다크 헤더 슬레이트화, 그림자 절제, 정상 레일 중립화(예외만 컬러), 지표칩 중립화+저습 .dry 분리, 진단 severity 텍스트 라벨, forced-colors 포커스, <main> 랜드마크, role=alert/status, ink-3 대비 상향, 인쇄 칩색 제거 등. 검증: 7페이지 php -l clean, 생 hex 0, 외부참조 0. 라이트/다크 헤드리스 렌더 확인.
420 lines
24 KiB
CSS
420 lines
24 KiB
CSS
/* =============================================================================
|
|
* app.css — 통합 모니터링 관리자 디자인 시스템 "Slate NOC Console"
|
|
*
|
|
* 단일 진실원(single source of truth). 7개 페이지가 이 파일 하나만 링크한다.
|
|
* <link rel="stylesheet" href="assets/app.css">
|
|
*
|
|
* 설계 원칙:
|
|
* - 모든 색/여백/반경/타이포는 :root CSS 변수. 페이지에 hex 리터럴 금지.
|
|
* - 한글 시스템 폰트 우선(폐쇄망 — 웹폰트 로드 불가). 외부 리소스 0.
|
|
* - 상태는 3중 인코딩: 색 + 도형(점/좌측레일/글리프) + 텍스트 라벨.
|
|
* (색맹·흑백 인쇄에서도 정상/누수/경보/오프라인 구분됨 — WCAG 1.4.1)
|
|
* - 명도 대비 WCAG AA(본문 4.5:1). 본문/캡션 하한 .8125rem, 라벨/배지/타임스탬프는 .75rem 허용.
|
|
* - 라이트 우선 + prefers-color-scheme 다크 자동 + 인쇄는 강제 라이트(리포트/증적).
|
|
* ============================================================================= */
|
|
|
|
/* ── 디자인 토큰 (라이트) ─────────────────────────────────────────────────── */
|
|
:root {
|
|
/* 표면·잉크 */
|
|
--bg: #eef1f5;
|
|
--surface: #ffffff;
|
|
--surface-2: #f5f7fa;
|
|
--raise: #fbfcfe;
|
|
--ink: #1b2430; /* 본문 (on surface: ~13:1) */
|
|
--ink-2: #54606f; /* 보조 텍스트 (on white: ~5.2:1 AA) */
|
|
--ink-3: #6b7687; /* 캡션/축라벨 (on white: ~4.5:1 AA, 최소선) */
|
|
--line: #dce1e8;
|
|
--line-2: #eaeef3;
|
|
|
|
/* 브랜드 (절제된 스틸블루 — 저채도라 저온 시맨틱/링크와 분리) */
|
|
--primary: #2b5bab;
|
|
--primary-600: #234f8f;
|
|
--on-primary: #ffffff;
|
|
--primary-tint: #e9eff8;
|
|
|
|
/* 헤더(앱바) — 라이트: 브랜드 primary 배경 + 흰 잉크 */
|
|
--header-bg: var(--primary);
|
|
--header-ink: var(--on-primary);
|
|
|
|
/* 상태 시맨틱 — rail/dot 용(진한 색) + ink(텍스트) + bg(옅은 배경) */
|
|
--normal: #1f8a4c; --normal-ink: #136c39; --normal-bg: #e6f4ec;
|
|
--leak: #d12b2b; --leak-ink: #b11f1f; --leak-bg: #fbe8e8;
|
|
--warn: #b26a00; --warn-ink: #935600; --warn-bg: #f8edda;
|
|
--offline:#6b7683; --offline-ink:#4b5560; --offline-bg:#ebeef2;
|
|
|
|
/* 지표 칩 — 서로/브랜드와 뚜렷이 구분 */
|
|
--hot-ink: var(--leak-ink); --hot-bg: var(--leak-bg);
|
|
--cold-ink: #0b5a6e; --cold-bg: #e1f1f4; /* 티일(저온) — 스틸블루와 다름 */
|
|
--humid-ink: var(--warn-ink); --humid-bg: var(--warn-bg);
|
|
--dry-ink: #4a4fa0; --dry-bg: #ecedf8; /* 절제된 인디고(저습, 희소) */
|
|
|
|
/* 차트 라인 */
|
|
--chart-temp: #c2410c; /* 온도=따뜻 */
|
|
--chart-hum: #0e7490; /* 습도=차가움(티일) */
|
|
--chart-grid: var(--line);
|
|
|
|
/* 타이포 — 한글 시스템 폰트 우선(오프라인 일관 렌더). 웹폰트 미로드. */
|
|
--font: "Malgun Gothic","Apple SD Gothic Neo",system-ui,-apple-system,"Segoe UI",Roboto,"Noto Sans KR",sans-serif;
|
|
--mono: ui-monospace,SFMono-Regular,Consolas,"D2Coding",monospace;
|
|
|
|
/* 4/8 간격 리듬 */
|
|
--sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px; --sp-5: 20px; --sp-6: 24px; --sp-8: 32px;
|
|
/* 반경 스케일 */
|
|
--r-sm: 4px; --r: 8px; --r-lg: 12px; --r-pill: 999px;
|
|
/* 그림자 (절제) */
|
|
--shadow: 0 1px 3px rgba(16,24,40,.08), 0 1px 2px rgba(16,24,40,.04);
|
|
--shadow-lg: 0 6px 20px rgba(16,24,40,.12);
|
|
--ring: 0 0 0 3px rgba(43,91,171,.28);
|
|
}
|
|
|
|
/* ── 다크 (관제 야간/개인 선호) — 같은 토큰을 명도만 조정 ──────────────────── */
|
|
@media (prefers-color-scheme: dark) {
|
|
:root {
|
|
--bg: #10151c;
|
|
--surface: #1a2129;
|
|
--surface-2: #212a34;
|
|
--raise: #232d38;
|
|
--ink: #e6ecf3;
|
|
--ink-2: #a3adba;
|
|
--ink-3: #8a95a2;
|
|
--line: #2b3540;
|
|
--line-2: #232c36;
|
|
|
|
--primary: #4c8dff;
|
|
--primary-600: #6ba1ff;
|
|
--on-primary: #0d1219;
|
|
--primary-tint: #182740;
|
|
|
|
/* 헤더 — 다크: surface-2 배경 + 본문 잉크(하단 헤어라인은 header 규칙) */
|
|
--header-bg: var(--surface-2);
|
|
--header-ink: var(--ink);
|
|
|
|
/* 다크 필드에서 4.5:1+ 유지 위해 명도 상향 */
|
|
--normal: #35c775; --normal-ink: #7fe0a8; --normal-bg: #12281d;
|
|
--leak: #ff5a52; --leak-ink: #ff958e; --leak-bg: #301617;
|
|
--warn: #ffb020; --warn-ink: #ffcb6b; --warn-bg: #2d2410;
|
|
--offline:#8a94a1; --offline-ink:#aab3bf; --offline-bg:#20272f;
|
|
|
|
--cold-ink: #5fd0e6; --cold-bg: #10262c;
|
|
--dry-ink: #a7abf0; --dry-bg: #1c1e33;
|
|
--hot-ink: var(--leak-ink); --hot-bg: var(--leak-bg);
|
|
--humid-ink: var(--warn-ink); --humid-bg: var(--warn-bg);
|
|
|
|
--chart-temp: #ff8a5c;
|
|
--chart-hum: #43c4dc;
|
|
|
|
--shadow: 0 1px 3px rgba(0,0,0,.5);
|
|
--shadow-lg: 0 8px 24px rgba(0,0,0,.55);
|
|
--ring: 0 0 0 3px rgba(76,141,255,.4);
|
|
}
|
|
/* 다크 헤더: primary 배너가 아닌 surface-2이므로 본문과 분리할 1px 하단 헤어라인 */
|
|
header { border-bottom: 1px solid var(--line); }
|
|
}
|
|
|
|
/* ── 리셋·베이스 ──────────────────────────────────────────────────────────── */
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
html { -webkit-text-size-adjust: 100%; }
|
|
body {
|
|
font-family: var(--font);
|
|
background: var(--bg);
|
|
color: var(--ink);
|
|
line-height: 1.55;
|
|
font-size: 1rem;
|
|
-webkit-font-smoothing: antialiased;
|
|
}
|
|
/* 수치는 자릿수 고정폭(표·지표 정렬) */
|
|
.val, .metric-chip, .report-stat strong, td.num, .num, .status-clock { font-variant-numeric: tabular-nums; }
|
|
a { color: var(--primary); }
|
|
:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; border-radius: var(--r-sm); }
|
|
img { max-width: 100%; }
|
|
|
|
/* 타입 스케일 (4단) */
|
|
h1 { font-size: 1.375rem; font-weight: 700; line-height: 1.3; }
|
|
h2 { font-size: 1.125rem; font-weight: 700; color: var(--ink); margin: var(--sp-6) 0 var(--sp-3); }
|
|
.panel h2, .status-hero h2 { margin-top: 0; }
|
|
label, .label { font-size: .875rem; font-weight: 600; }
|
|
.caption, small { font-size: .8125rem; color: var(--ink-3); }
|
|
|
|
/* ── 헤더(앱바) ───────────────────────────────────────────────────────────── */
|
|
header {
|
|
background: var(--header-bg);
|
|
color: var(--header-ink);
|
|
padding: var(--sp-4) var(--sp-6);
|
|
display: flex; align-items: center; justify-content: space-between; gap: var(--sp-4);
|
|
}
|
|
header h1 { font-size: 1.3rem; color: var(--header-ink); }
|
|
.top-actions { display: flex; gap: var(--sp-2); align-items: center; flex-wrap: wrap; }
|
|
.top-actions a {
|
|
color: var(--header-ink); font-size: .8125rem; font-weight: 600; text-decoration: none;
|
|
background: rgba(255,255,255,.14); border: 1px solid rgba(255,255,255,.22);
|
|
padding: 6px 14px; border-radius: var(--r-sm);
|
|
}
|
|
.top-actions a:hover { background: rgba(255,255,255,.24); }
|
|
|
|
/* ── 레이아웃 ─────────────────────────────────────────────────────────────── */
|
|
.container { max-width: 1320px; margin: var(--sp-6) auto; padding: 0 var(--sp-4); }
|
|
.ops-grid { display: grid; grid-template-columns: minmax(0,1.5fr) minmax(280px,.9fr);
|
|
gap: var(--sp-4); align-items: start; margin-bottom: var(--sp-6); }
|
|
.cards { display: flex; gap: var(--sp-4); flex-wrap: wrap; margin-bottom: var(--sp-6); }
|
|
.sensor-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px,1fr));
|
|
gap: var(--sp-4); margin-bottom: var(--sp-6); }
|
|
|
|
/* ── 패널·카드 ───────────────────────────────────────────────────────────── */
|
|
.panel, .card, .chart-panel, .leak-status-card, .empty-note {
|
|
background: var(--surface); border: 1px solid var(--line); border-radius: var(--r);
|
|
}
|
|
.panel { padding: var(--sp-5); }
|
|
.chart-panel { padding: var(--sp-5); margin-bottom: var(--sp-6); }
|
|
.card { padding: var(--sp-5) var(--sp-6); flex: 1; min-width: 180px; }
|
|
.card span { color: var(--ink-2); font-size: .8125rem; }
|
|
.card .val { font-size: 2rem; font-weight: 700; margin-top: var(--sp-1); color: var(--ink); }
|
|
|
|
/* ── 시스템 상태 배너(hero) ──────────────────────────────────────────────── */
|
|
.status-hero {
|
|
border-radius: var(--r); padding: var(--sp-5); margin-bottom: var(--sp-5);
|
|
display: grid; grid-template-columns: minmax(0,1fr) auto; gap: var(--sp-4); align-items: center;
|
|
color: var(--ink); background: var(--surface);
|
|
border: 1px solid var(--line); border-left: 6px solid var(--offline);
|
|
}
|
|
.status-hero h2 { font-size: 1.25rem; display: flex; align-items: center; gap: var(--sp-2); }
|
|
.status-hero h2::before {
|
|
content: ""; width: .7em; height: .7em; border-radius: 50%; background: var(--offline); flex: none;
|
|
}
|
|
.status-hero p { color: var(--ink-2); font-size: .9rem; margin-top: 2px; }
|
|
.status-hero.normal { border-left-color: var(--normal); }
|
|
.status-hero.warning { border-left-color: var(--warn); }
|
|
.status-hero.danger { border-left-color: var(--leak); background: var(--leak-bg); }
|
|
.status-hero.normal h2::before { background: var(--normal); }
|
|
.status-hero.warning h2::before { background: var(--warn); }
|
|
.status-hero.danger h2::before { background: var(--leak); }
|
|
.status-clock { text-align: right; font-size: .8125rem; color: var(--ink-3); white-space: nowrap; }
|
|
|
|
/* ── 센서 상태 카드 (좌측레일 + 점 + 배지 = 3중) ─────────────────────────── */
|
|
.sensor-card {
|
|
background: var(--surface); border: 1px solid var(--line); border-radius: var(--r);
|
|
border-left: 1px solid var(--line); padding: var(--sp-5);
|
|
}
|
|
.sensor-card.alert { border-left: 6px solid var(--leak); background: var(--leak-bg); }
|
|
.sensor-card.offline { border-left: 6px solid var(--offline); background: var(--offline-bg); }
|
|
|
|
/* 배지/알약 공통 — 앞에 점 글리프(도형 중복), 텍스트 라벨 필수 */
|
|
.badge, .pill {
|
|
display: inline-flex; align-items: center; gap: .4em;
|
|
border-radius: var(--r-pill); padding: 3px 10px;
|
|
font-size: .75rem; font-weight: 700; line-height: 1.4;
|
|
background: var(--normal-bg); color: var(--normal-ink);
|
|
}
|
|
.badge::before, .pill::before {
|
|
content: ""; width: .5em; height: .5em; border-radius: 50%; background: currentColor; flex: none;
|
|
}
|
|
.badge.ok, .pill.ok { background: var(--normal-bg); color: var(--normal-ink); }
|
|
.badge.alert,.pill.alert { background: var(--leak-bg); color: var(--leak-ink); }
|
|
.badge.offline,.pill.offline{ background: var(--offline-bg); color: var(--offline-ink); }
|
|
.pill.high_temp { background: var(--hot-bg); color: var(--hot-ink); }
|
|
.pill.low_temp { background: var(--cold-bg); color: var(--cold-ink); }
|
|
.pill.high_humidity{ background: var(--humid-bg); color: var(--humid-ink); }
|
|
.pill.low_humidity { background: var(--dry-bg); color: var(--dry-ink); }
|
|
.pill.leak, .pill.leak_alert { background: var(--leak-bg); color: var(--leak-ink); }
|
|
.pill.leak_recovery { background: var(--normal-bg); color: var(--normal-ink); }
|
|
|
|
/* 지표 칩 */
|
|
.metric-line { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-top: var(--sp-3); }
|
|
.metric-chip {
|
|
display: inline-flex; align-items: center; border-radius: var(--r-pill);
|
|
background: var(--surface-2); color: var(--ink-2);
|
|
padding: 4px 10px; font-size: .78rem; font-weight: 700;
|
|
}
|
|
.metric-chip.hot { background: var(--hot-bg); color: var(--hot-ink); }
|
|
.metric-chip.cold { background: var(--cold-bg); color: var(--cold-ink); }
|
|
.metric-chip.humid { background: var(--humid-bg); color: var(--humid-ink); }
|
|
.metric-chip.dry { background: var(--dry-bg); color: var(--dry-ink); }
|
|
.metric-time { color: var(--ink-3); font-size: .75rem; margin-top: var(--sp-2); }
|
|
|
|
/* ── 운영 자가진단 ───────────────────────────────────────────────────────── */
|
|
.diag-list { display: grid; gap: var(--sp-2); }
|
|
.diag-item {
|
|
border: 1px solid var(--line); border-radius: var(--r); padding: 9px 10px;
|
|
display: grid; grid-template-columns: auto minmax(0,1fr); gap: var(--sp-2); align-items: start;
|
|
}
|
|
.diag-dot { width: 10px; height: 10px; border-radius: 50%; margin-top: 5px; background: var(--normal); flex: none; }
|
|
.diag-item.warn .diag-dot { background: var(--warn); }
|
|
.diag-item.critical .diag-dot { background: var(--leak); }
|
|
.diag-item strong { display: block; font-size: .8125rem; }
|
|
.diag-item span { color: var(--ink-2); font-size: .78rem; line-height: 1.4; }
|
|
|
|
/* ── 30일 운영 요약 ─────────────────────────────────────────────────────── */
|
|
.report-grid { display: grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: var(--sp-3); }
|
|
.report-stat { border: 1px solid var(--line); border-radius: var(--r); padding: var(--sp-3); background: var(--surface); }
|
|
.report-stat span { display: block; color: var(--ink-2); font-size: .75rem; margin-bottom: 4px; }
|
|
.report-stat strong { font-size: 1.1rem; }
|
|
|
|
/* ── 표 ───────────────────────────────────────────────────────────────────── */
|
|
.table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; margin-bottom: var(--sp-6); border-radius: var(--r); }
|
|
table {
|
|
width: 100%; border-collapse: collapse; background: var(--surface);
|
|
border: 1px solid var(--line); border-radius: var(--r); overflow: hidden;
|
|
}
|
|
.table-wrap table { margin-bottom: 0; }
|
|
th {
|
|
background: var(--surface-2); font-size: .8125rem; font-weight: 700; color: var(--ink-2);
|
|
padding: 10px 14px; text-align: left; border-bottom: 1px solid var(--line); white-space: nowrap;
|
|
}
|
|
td { padding: 9px 14px; border-bottom: 1px solid var(--line-2); font-size: .875rem; color: var(--ink); }
|
|
tbody tr:hover td { background: var(--surface-2); }
|
|
.alert-row td { background: var(--leak-bg); }
|
|
.alert-row:hover td { background: var(--leak-bg); }
|
|
|
|
/* 상태 텍스트(인라인) — 색 + 앞 글리프 */
|
|
.ok-color, .err-color, .warn-color { font-weight: 700; }
|
|
.ok-color { color: var(--normal-ink); }
|
|
.err-color { color: var(--leak-ink); }
|
|
.warn-color{ color: var(--warn-ink); }
|
|
.ok-color::before { content: "\2713\00a0"; } /* ✓ */
|
|
.err-color::before { content: "\2715\00a0"; } /* ✕ */
|
|
.warn-color::before{ content: "\26a0\00a0"; } /* ⚠ */
|
|
|
|
/* 점검 통과/실패(설치 마법사·보안 증적) — 색만 아님, 글리프+텍스트 */
|
|
.check.ok, .check-ok { color: var(--normal-ink); font-weight: 700; }
|
|
.check.fail, .check-fail { color: var(--leak-ink); font-weight: 700; }
|
|
.check.ok::before, .check-ok::before { content: "\2713\00a0"; }
|
|
.check.fail::before, .check-fail::before { content: "\2715\00a0"; }
|
|
|
|
/* ── 차트 ─────────────────────────────────────────────────────────────────── */
|
|
.chart-legend { display: flex; gap: var(--sp-4); flex-wrap: wrap; margin: var(--sp-2) 0 var(--sp-3);
|
|
font-size: .8125rem; color: var(--ink-2); }
|
|
.chart-legend span { display: inline-flex; align-items: center; gap: 6px; }
|
|
.legend-swatch { width: 14px; height: 4px; border-radius: 2px; display: inline-block; }
|
|
.legend-swatch.temp { background: var(--chart-temp); }
|
|
.legend-swatch.hum { background: var(--chart-hum); }
|
|
.chart-svg-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
|
|
.chart-empty, .empty-note { color: var(--ink-3); padding: var(--sp-6); text-align: center; }
|
|
.empty-note { padding: var(--sp-5); }
|
|
|
|
/* ── 누수 패널 ───────────────────────────────────────────────────────────── */
|
|
.leak-status-card { border-left: 1px solid var(--line); padding: var(--sp-5); }
|
|
.leak-status-card.leak { border-left: 6px solid var(--leak); background: var(--leak-bg); }
|
|
.leak-status-card.offline { border-left: 6px solid var(--offline); background: var(--offline-bg); }
|
|
.leak-thumbs { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
|
|
.leak-thumbs a { display: inline-block; line-height: 0; }
|
|
.leak-thumbs img { width: 64px; height: 64px; object-fit: cover; border-radius: var(--r-sm);
|
|
border: 1px solid var(--line); background: var(--surface-2); }
|
|
|
|
/* ── 모바일 요약 타일/링크 ──────────────────────────────────────────────── */
|
|
.mobile-quick { display: none; }
|
|
.quick-grid { display: grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: var(--sp-2); margin-bottom: var(--sp-3); }
|
|
.quick-tile { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r);
|
|
padding: 10px; text-decoration: none; color: var(--ink); min-height: 70px; }
|
|
.quick-tile span { display: block; color: var(--ink-2); font-size: .75rem; margin-bottom: 5px; }
|
|
.quick-tile strong { font-size: 1.25rem; line-height: 1; }
|
|
.quick-tile.danger { border-color: var(--leak); background: var(--leak-bg); }
|
|
.quick-tile.warn { border-color: var(--warn); background: var(--warn-bg); }
|
|
.quick-tile.ok { border-color: var(--normal); background: var(--normal-bg); }
|
|
.quick-links { display: flex; gap: var(--sp-2); overflow-x: auto; -webkit-overflow-scrolling: touch;
|
|
padding-bottom: 6px; margin-bottom: var(--sp-4); }
|
|
.quick-links a { flex: 0 0 auto; color: var(--primary-600); background: var(--primary-tint);
|
|
border: 1px solid var(--line); text-decoration: none; border-radius: var(--r-pill);
|
|
padding: 8px 12px; font-size: .8125rem; font-weight: 700; }
|
|
|
|
/* ── 모바일 접힘(disclosure) ────────────────────────────────────────────── */
|
|
.mobile-detail { margin-bottom: var(--sp-6); }
|
|
.mobile-detail summary { display: none; }
|
|
|
|
.refresh { float: right; font-size: .8125rem; color: var(--ink-2); }
|
|
|
|
/* ── 폼 / 로그인 / 카드형 페이지 ────────────────────────────────────────── */
|
|
.login-box {
|
|
background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-lg);
|
|
padding: var(--sp-8) var(--sp-6); box-shadow: var(--shadow-lg); width: 100%; max-width: 400px;
|
|
}
|
|
.login-box h1 { color: var(--primary); margin-bottom: var(--sp-2); text-align: center; }
|
|
.login-box p.sub { font-size: .875rem; color: var(--ink-2); text-align: center; margin-bottom: var(--sp-6); }
|
|
.form-group { margin-bottom: var(--sp-4); }
|
|
.form-group label { display: block; color: var(--ink-2); margin-bottom: 6px; }
|
|
.form-group input, input[type=text], input[type=password], textarea {
|
|
width: 100%; padding: 10px 14px; border: 1px solid var(--line); border-radius: var(--r);
|
|
font-size: .95rem; font-family: inherit; color: var(--ink); background: var(--surface); outline: none;
|
|
}
|
|
.form-group input:focus, input:focus, textarea:focus { border-color: var(--primary); box-shadow: var(--ring); }
|
|
/* 강제 색상(고대비/Windows High Contrast)에서는 border-color·box-shadow가 무시되어
|
|
입력 포커스가 사라진다 → 시스템 아웃라인으로 포커스 링 복원 */
|
|
@media (forced-colors: active) {
|
|
input:focus, textarea:focus { outline: 2px solid; outline-offset: 1px; }
|
|
}
|
|
.btn {
|
|
display: inline-block; width: 100%; padding: 12px; background: var(--primary); color: var(--on-primary);
|
|
border: none; border-radius: var(--r); font-size: 1rem; font-weight: 700; cursor: pointer; text-align: center;
|
|
}
|
|
.btn:hover { background: var(--primary-600); }
|
|
.btn:disabled { background: var(--offline); cursor: not-allowed; }
|
|
.btn.secondary { background: var(--surface); color: var(--primary); border: 1px solid var(--line); }
|
|
|
|
/* 배너 — 성공 배너는 .banner-ok. (생 .ok 는 .badge.ok/.pill.ok/.quick-tile.ok 와
|
|
특이도 동률로 충돌해 정상 배지를 사각형으로 깨뜨리므로 네임스페이스한다.) */
|
|
.error, .expired, .banner-ok {
|
|
padding: 10px 14px; border-radius: var(--r); font-size: .875rem; margin-bottom: var(--sp-4);
|
|
border: 1px solid var(--line); border-left-width: 4px;
|
|
}
|
|
.error { background: var(--leak-bg); color: var(--leak-ink); border-left-color: var(--leak); }
|
|
.expired { background: var(--warn-bg); color: var(--warn-ink); border-left-color: var(--warn); }
|
|
.banner-ok { background: var(--normal-bg); color: var(--normal-ink); border-left-color: var(--normal); }
|
|
|
|
/* MFA 시크릿/코드 블록 등 */
|
|
.secret, code, .mono {
|
|
font-family: var(--mono); background: var(--surface-2); border: 1px solid var(--line);
|
|
border-radius: var(--r-sm); padding: 2px 6px; word-break: break-all; color: var(--ink);
|
|
}
|
|
.secret { display: block; padding: 12px; font-size: 1.05rem; letter-spacing: .06em; }
|
|
|
|
/* ── 반응형 ──────────────────────────────────────────────────────────────── */
|
|
@media (max-width: 768px) {
|
|
header { flex-direction: column; align-items: flex-start; }
|
|
.top-actions { margin-top: var(--sp-3); overflow-x: auto; flex-wrap: nowrap; -webkit-overflow-scrolling: touch; }
|
|
.top-actions a { flex: 0 0 auto; }
|
|
.status-hero { grid-template-columns: 1fr; }
|
|
.status-clock { text-align: left; }
|
|
.mobile-quick { display: block; }
|
|
.ops-grid { grid-template-columns: 1fr; }
|
|
.report-grid { grid-template-columns: repeat(2, minmax(0,1fr)); }
|
|
.cards { flex-direction: column; }
|
|
.card, .sensor-card { min-width: 0; }
|
|
header h1 { font-size: 1.1rem; }
|
|
.card .val { font-size: 1.6rem; }
|
|
th, td { padding: 8px 10px; }
|
|
td { font-size: .8125rem; }
|
|
.mobile-detail { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r); overflow: hidden; }
|
|
.mobile-detail summary { display: flex; justify-content: space-between; align-items: center; gap: 10px;
|
|
padding: 13px 14px; color: var(--ink); font-weight: 700; cursor: pointer; list-style: none; }
|
|
.mobile-detail summary::-webkit-details-marker { display: none; }
|
|
.mobile-detail summary span { color: var(--ink-2); font-size: .8125rem; font-weight: 600; }
|
|
.mobile-detail .table-wrap { margin-bottom: 0; border-top: 1px solid var(--line); border-radius: 0; }
|
|
}
|
|
@media (max-width: 480px) {
|
|
.container { padding: 0 var(--sp-3); }
|
|
header { padding: var(--sp-3) var(--sp-4); }
|
|
.quick-grid { grid-template-columns: repeat(2, minmax(0,1fr)); }
|
|
.sensor-grid { grid-template-columns: 1fr; }
|
|
.card { padding: var(--sp-4); }
|
|
.col-hide-xs { display: none; }
|
|
.report-grid { grid-template-columns: 1fr; }
|
|
.login-box { padding: var(--sp-6) var(--sp-5); margin: 0 var(--sp-3); }
|
|
/* 캡션/본문 하한 유지 — 이 밑으로 축소 금지 */
|
|
th, td { font-size: .8125rem; }
|
|
}
|
|
|
|
/* ── 인쇄 — 강제 라이트(월간 리포트·보안 증적은 종이 가독) ────────────────── */
|
|
@media print {
|
|
:root {
|
|
--bg: #fff; --surface: #fff; --surface-2: #fff; --raise: #fff;
|
|
--ink: #000; --ink-2: #333; --ink-3: #444; --line: #999; --line-2: #ccc;
|
|
--primary: #14315e; --on-primary: #fff; --primary-tint: #fff;
|
|
--normal-bg: #fff; --leak-bg: #fff; --warn-bg: #fff; --offline-bg: #fff;
|
|
--cold-bg: #fff; --dry-bg: #fff;
|
|
--shadow: none; --shadow-lg: none;
|
|
}
|
|
body { background: #fff; color: #000; }
|
|
header, .top-actions, .refresh, .mobile-quick, .btn { display: none !important; }
|
|
.panel, .card, table, .chart-panel, .status-hero { box-shadow: none; border: 1px solid #999;
|
|
page-break-inside: avoid; break-inside: avoid; }
|
|
.badge, .pill { border: 1px solid currentColor; } /* 흑백에서도 테두리로 구분 */
|
|
a { color: #000; text-decoration: underline; }
|
|
}
|