:root {
  --bg-primary: #0a0e1a;
  --bg-secondary: #111827;
  --bg-card: #1a2236;
  --bg-card-hover: #1f2a42;
  --border: #2a3a5c;
  --border-glow: #0ea5e9;
  --text-primary: #e2e8f0;
  --text-secondary: #94a3b8;
  --text-muted: #64748b;
  --accent-cyan: #06b6d4;
  --accent-blue: #3b82f6;
  --accent-teal: #14b8a6;
  --accent-red: #ef4444;
  --accent-amber: #f59e0b;
  --accent-green: #22c55e;
  --accent-gold: #ffcf5c; /* sp48의 소제목 색 — 어두운 배경에서 가장 먼저 눈에 들어온다 */
  --accent-sky: #7fd1ff;  /* sp48가 시계(#pcClock)에 쓰는 색 — '시각'을 가리키는 색으로 통일 */
  --radius: 12px;
  --radius-sm: 8px;
  --font-sans: "Noto Sans KR", "Malgun Gothic", "Apple SD Gothic Neo", -apple-system, sans-serif;
  --font-mono: "JetBrains Mono", Consolas, "Courier New", monospace;
}

* { box-sizing: border-box; }

/* Bootstrap 의 .text-muted 는 --bs-secondary-color(라이트 테마의 어두운 회색)로 떨어진다.
   이 앱은 data-bs-theme 를 지정하지 않아 다크 배경 위에 어두운 회색이 찍혀 거의 안 보인다.
   ("조회 중...", "조회된 데이터가 없습니다", 부이 관리 안내문 등이 모두 이 클래스를 쓴다.) */
.text-muted { color: var(--text-muted) !important; }

body {
  background: var(--bg-primary);
  color: var(--text-primary);
  font-family: var(--font-sans);
  min-height: 100vh;
}
body::before {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  background:
    radial-gradient(at 20% 0%, rgba(6, 182, 212, 0.06) 0%, transparent 60%),
    radial-gradient(at 80% 15%, rgba(59, 130, 246, 0.05) 0%, transparent 50%);
}
.container-fluid { position: relative; z-index: 1; }

/* 대시보드 화면 전체를 FHD(1920x1080) 한 화면에 맞춘다 (스크롤 없이).
   좌(1):우(2) — 좌측은 지도/그래프를 위아래로 반씩, 우측은 BC-1/BC-2를 좌우로 나란히(각각 세로로 길게). */
.dashboard-grid {
  display: flex; gap: 14px;
  height: calc(100vh - 60px - 2rem); /* 60px = 네비바, 2rem = .container-fluid.p-3 상하 패딩 */
  min-height: 560px;
}
.dash-col-mapchart { flex: 2.3 1 0; min-width: 0; display: flex; flex-direction: column; gap: 14px; }
.dash-col-cards { flex: 2.7 1 0; min-width: 0; display: flex; flex-direction: column; gap: 14px; }
/* 좁은 화면(태블릿 세로/모바일)용 배치는 파일 맨 끝의 '모바일' 블록에 모아 두었다. */

.buoy-cards-stack { display: flex; flex-direction: row; gap: 14px; flex: 1 1 auto; min-height: 0; }
.buoy-cards-stack .buoy-card { flex: 1 1 0; min-width: 0; min-height: 0; }

/* 지도:그래프 세로 비율. 그래프 칸은 탭 줄(고정 높이)을 포함하므로, 여기서 덜어낸 만큼이
   그대로 그래프 캔버스 높이에서 빠진다. */
.dash-map-pane { flex: 0.95 1 0; min-height: 0; position: relative; }
.dash-chart-pane { flex: 1.05 1 0; min-height: 0; display: flex; flex-direction: column; gap: 10px; }

.chart-section-grow { flex: 1 1 auto; min-height: 120px; display: flex; flex-direction: column; }
.chart-canvas-wrap { flex: 1 1 auto; min-width: 0; min-height: 0; position: relative; }
.chart-canvas-wrap canvas { position: absolute; inset: 0; width: 100% !important; height: 100% !important; }

.mono { font-family: var(--font-mono); }

::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: var(--bg-secondary); }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }

/* ---------- Navbar ---------- */
.app-navbar {
  background: var(--bg-secondary) !important;
  border-bottom: 1px solid var(--border);
  min-height: 60px;
}
.app-navbar .navbar-brand { color: #fff; font-weight: 700; }
.app-navbar .nav-link {
  color: var(--text-secondary) !important;
  border-radius: var(--radius-sm);
  padding: 8px 14px !important;
  font-weight: 500;
  transition: all .2s;
}
.app-navbar .nav-link:hover { color: var(--text-primary) !important; background: rgba(255, 255, 255, 0.06); }
.app-navbar .nav-link.active { color: var(--accent-cyan) !important; background: rgba(6, 182, 212, 0.12); }

.logout-btn {
  border: 1px solid var(--border); color: var(--text-secondary);
  border-radius: var(--radius-sm); background: transparent; padding: 6px 14px; font-size: 13.5px;
  transition: all .2s; text-decoration: none; display: inline-block;
}
.logout-btn:hover { border-color: var(--accent-red); color: var(--accent-red); background: rgba(239, 68, 68, .1); }

/* 로그인 ID 표시(Oracle 판은 비밀번호 변경 기능 없음) */
.user-name { color: var(--text-secondary); font-size: 13.5px; text-decoration: none; cursor: pointer; transition: color .2s; }
.user-name:hover { color: var(--accent-cyan); text-decoration: none; }

/* ---------- Section divider ---------- */
.section-divider {
  display: flex; align-items: center; gap: 12px; margin: 0;
}
.section-divider::before { content: ""; flex: 0 0 12px; height: 1px; background: var(--border); }
.section-divider::after { content: ""; flex: 1; height: 1px; background: var(--border); }
.section-divider .section-title {
  color: var(--text-secondary); font-size: 15px; font-weight: 700; letter-spacing: .05em; white-space: nowrap;
}

/* ---------- Panels / cards ---------- */
.panel {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.panel.hoverable { transition: border-color .25s; }
.panel.hoverable:hover { border-color: var(--border-glow); }

/* 관측소(BC-1/BC-2)마다 고유 색을 주고, 카드 왼쪽 띠 + 이름 옆 점으로 표시한다.
   같은 색이 그래프에서도 그대로 쓰이므로 카드 ↔ 그래프 선이 한눈에 대응된다. */
.buoy-card {
  padding: 25px 20px; display: flex; flex-direction: column;
  border-left: 4px solid var(--station-color, var(--border));
  /* 카드 안에서 cqh(= 카드 높이의 1%)를 쓰기 위한 컨테이너 지정. 카드는 높이가 확정된
     flex 아이템이라 size 컨테인먼트를 걸어도 크기가 흔들리지 않는다. */
  container-type: size;
}
.buoy-card-header { flex: 0 0 auto; display: flex; justify-content: space-between; align-items: center; gap: 8px; margin-bottom: 50px; }
.buoy-name { display: flex; align-items: center; gap: 8px; font-weight: 700; font-size: 24px; color: var(--text-primary); }
.station-dot {
  width: 9px; height: 9px; border-radius: 50%; background: var(--station-color, var(--text-secondary));
  box-shadow: 0 0 8px var(--station-color, transparent); flex-shrink: 0;
}

.status-badge {
  display: inline-flex; align-items: center; gap: 5px;
  border-radius: 20px; padding: 4px 12px; font-size: 13px; font-weight: 600; white-space: nowrap;
}
.status-badge .dot { width: 7px; height: 7px; border-radius: 50%; }
.status-badge.online { color: var(--accent-green); background: rgba(34, 197, 94, .15); }
.status-badge.online .dot { background: var(--accent-green); }
.status-badge.offline { color: var(--accent-red); background: rgba(239, 68, 68, .15); }
.status-badge.offline .dot { background: var(--accent-red); }

/* ---------- BC-1/BC-2 카드 내부: sp48.jsp(sts_sp48/css/style.css)의 항목/값 규칙을 그대로 옮김 ----------
   sp48에서 이 화면이 잘 읽히는 이유는 네 가지였다.
     1) 소제목을 배경과 대비가 뚜렷한 금색(#ffcf5c) + 밑줄로 세워 그룹 경계가 먼저 보인다.
     2) 항목명도 굵게/밝게 줘서 "흐린 라벨 + 밝은 값"이 아니라 한 쌍으로 읽힌다.
     3) 값은 크게 흰색으로, 단위는 값 옆에 작게 — 크기 차이로 위계를 만든다.
     4) 값/단위 열을 고정폭으로 잡아 행이 바뀌어도 소수점 자리가 같은 세로선에 선다.
   특히 4)가 기존 배치가 어수선해 보인 원인이었다(1fr auto auto 는 행마다 값 폭이 달라진다). */

/* 이 화면은 "스크롤 없이 한 화면"이 전제라 카드 높이가 화면에 묶여 있는데, 항목 9개 +
   소제목 3개를 고정 크기로 두면 낮은 화면에서 마지막 행이 카드 밖으로 밀려난다. 그래서
   소제목/항목명/값/여백을 clamp 로 화면에 물리는데, 기준을 vh(뷰포트)가 아니라 cqh(카드
   높이)로 잡는다.
   vh 로 잡으면 내용 높이는 vh 계수의 합만큼 늘어나는 반면 실제로 쓸 수 있는 높이는
   (100vh - 네비바/패딩) 이라 1vh 당 1px 씩만 늘어난다. 계수 합이 1을 넘는 순간 화면이
   커질수록 여유가 오히려 줄어들어, 어느 크기부터는 반드시 넘치게 된다(실제로 그랬다).
   cqh 는 카드 높이에 직접 비례하므로 내용/카드 비율이 화면 크기와 무관하게 일정해진다. */

/* 그룹(관측정보/CT#1/CT#2) 사이는 소제목의 margin-top으로 넉넉히 띄우고, 그룹 안의 행끼리는 고정
   padding만 줘서 붙어 보이게 한다 (flex:1 로 늘려 붙잡으면 행 간격이 그룹 간격보다 커져서
   리듬이 뒤집힌다). 카드에 남는 높이는 ct-section 전체를 세로 가운데로 두어 위아래로 나눈다. */
/* 남는 높이는 세로 가운데 정렬로 위아래에 나눠 주되, 아래쪽에 padding 을 먼저 깔아
   그만큼 내용을 위로 끌어올린다 (관측정보 제목이 카드 헤더에서 너무 떨어져 보이지 않게).
   cqh 로 줘서 화면이 커져도 위/아래 비율이 그대로 유지된다. */
/* 카드 안 모든 값(위도/경도/전압/CT#1/CT#2)이 같은 OBS_TIME 레코드에서 나오므로, 관측시간은
   어느 한 그룹이 아니라 카드 전체에 걸리는 정보다. 그래서 그룹 제목 옆이 아니라 헤더 바로
   아래 독립된 줄로 두고, 항목명은 시각을 가리키는 색(--accent-sky)으로 소제목 금색과 구분한다. */
/* 좌우 padding 은 .grp-title / .item-row 과 같은 6px 이어야 한다. 이게 없으면 항목명은
   6px 왼쪽으로, 값은 6px 오른쪽으로 삐져나가 아래 소제목들과 시작/끝 선이 어긋난다.
   (값은 타임스탬프라 폭이 커서 grid 3열에 넣을 수 없다. 대신 flex 로 양끝에 붙이면
   왼쪽은 소제목 이름과, 오른쪽은 단위 칸의 오른쪽 끝과 같은 선에 선다.) */
.obs-time-row {
  flex: 0 0 auto; display: flex; justify-content: space-between; align-items: baseline; gap: 10px;
  padding: 0 6px; margin-bottom: 30px;
}
/* 항목명은 크기도 색도 .grp-title 과 같게 — 관측정보/CT#1/CT#2 와 같은 층위의 줄이므로
   같은 소제목으로 읽히는 게 맞다. 대신 값 쪽에 시각을 가리키는 색(--accent-sky)을 줘서,
   이 줄만 값이 흰 숫자가 아닌 것으로 구분되게 한다. */
.obs-time-nm { font-size: clamp(14px, 2.9cqh, 28px); font-weight: 700; color: var(--accent-gold); }
.obs-time-val {
  font-family: var(--font-mono); font-size: clamp(14px, 2.9cqh, 28px); font-weight: 700; color: var(--accent-sky);
  white-space: nowrap;
}

.ct-section {
  flex: 1 1 auto; display: flex; flex-direction: column; justify-content: flex-start;
  min-height: 0;
}
.ct-group { flex: 0 0 auto; }

.grp-title {
  /* 아래 .item-row 와 같은 그리드/여백을 쓴다. 그래야 소제목 옆 수심 표기가 ℃·psu 같은
     단위들과 같은 칸의 같은 왼쪽 선에 선다 (열 정의를 바꿀 때는 .item-row 와 함께 고칠 것).
     좌우 padding 은 칸을 맞추기 위한 것이고, 밑줄(border-bottom)은 padding 바깥이라
     카드 폭 전체로 그대로 그어진다. */
  display: grid; grid-template-columns: minmax(0, 1fr) 184px 62px; column-gap: 12px;
  align-items: baseline;
  margin: clamp(10px, 3.6cqh, 40px) 0 8px; padding: 0 6px 8px;
  font-size: clamp(14px, 2.9cqh, 28px); font-weight: 700; color: var(--accent-gold);
  border-bottom: 1px solid var(--border);
}
.ct-group:first-child .grp-title { margin-top: 0; }
/* 센서 설치 수심(표층 / 5m 수심)은 소제목 이름의 일부가 아니라 그 그룹에 붙는 부가 정보라,
   괄호로 이름에 묶지 않고 단위 칸(3열)에 넣어 아래 ℃·mS/cm·psu 와 같은 줄에 세운다.
   크기는 .item-unit 에 맞추고, 색은 소제목 금색이 아니라 관측시간 값과 같은 --accent-sky 로
   둔다. 카드에서 금색은 '소제목', 푸른색은 '값에 딸린 부가 정보'로 역할이 갈린다. */
.grp-title-nm { grid-column: 1 / 3; min-width: 0; }
.grp-title-sub {
  grid-column: 3; text-align: left; white-space: nowrap;
  font-size: clamp(11px, 1.9cqh, 18px); font-weight: 500; color: var(--accent-sky);
}
/* 항목명 칸에 1fr 만 쓰면 grid 트랙의 최소폭이 글자 길이(min-content)만큼 잡혀 값/단위를
   오른쪽으로 밀어내므로, minmax(0, 1fr) 로 최소폭을 0 으로 고정해 넘칠 때 항목명 쪽에서
   말줄임되게 한다. 값/단위는 고정폭이라 행이 바뀌어도 자리가 움직이지 않는다.
   값 열 폭(184px)은 가장 긴 값인 경도("126.81594" = 172.8px)에 여유를 둔 크기로 브라우저에서
   재서 정했다. 항목명 열은 가장 긴 "전기전도도"(96.6px)에도 130px 넘게 남으므로 영향이 없다.
   세로 padding 은 sp48(.item-row 7px)과 같은 값 — 9px 로는 항목 9개 + 소제목 3개가 카드
   높이를 넘겨서 마지막 행이 잘렸다. */
.item-row {
  display: grid; grid-template-columns: minmax(0, 1fr) 184px 62px; column-gap: 12px;
  align-items: baseline; line-height: 1.4; padding: clamp(2px, .35cqh, 8px) 6px; border-radius: 4px;
}
.item-nm {
  font-size: clamp(13px, 2.2cqh, 22px); font-weight: 700; color: #c8d3e0; text-align: left;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.item-val {
  font-family: var(--font-mono); font-size: clamp(15px, 3.35cqh, 34px); font-weight: 700; color: #fff; text-align: right;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.item-val.warn { color: var(--accent-amber); }
.item-val.danger { color: var(--accent-red); }
.item-unit { font-size: 15px; color: var(--text-muted); text-align: left; }

/* 제한 계정(guest): 전압/CT#2 가 빠져 카드 아래가 비므로, 남는 높이를 관측시간/관측정보/CT#1
   세 덩어리가 균등하게 나눠 갖고 글자도 키운다. */
.buoy-card.limited .buoy-card-header { margin-bottom: clamp(28px, 9cqh, 96px); }
.buoy-card.limited .obs-time-row { margin-bottom: 0; }
.buoy-card.limited .obs-time-nm,
.buoy-card.limited .obs-time-val { font-size: clamp(14px, 2.9cqh, 28px); }
.buoy-card.limited .ct-section { justify-content: space-evenly; }
.buoy-card.limited .ct-group { flex: 0 0 auto; }
.buoy-card.limited .grp-title { margin: 0 0 clamp(6px, 1.4cqh, 16px); }
.buoy-card.limited .item-row { padding: clamp(4px, 1.2cqh, 16px) 6px; }
/* 글자 크기는 기본 카드와 같게 둔다 — 값 열(184px)이 고정폭이라 키우면 경도 값이 잘린다. */

/* 지도 위 관측소 ID 라벨 */
.buoy-label {
  background: var(--bg-card) !important; color: var(--text-primary) !important;
  border: 1px solid var(--border) !important; font-family: var(--font-mono); font-size: 11px; font-weight: 700;
  padding: 2px 7px !important; box-shadow: none !important;
}
.buoy-label::before { border-top-color: var(--border) !important; }

/* ---------- Chart card: 헤더 + 그래프 + 커스텀 범례(색·이름·최신값) ---------- */
.chart-card {
  background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius);
  transition: border-color .3s; padding: 16px 20px;
  display: flex; flex-direction: column;
}
.chart-card:hover { border-color: var(--border-glow); }


.chart-body { flex: 1 1 auto; min-height: 0; display: flex; }

/* 그래프 툴팁 - 캔버스 기본 툴팁 대신 HTML 로 그린다(dashboard.js 의 renderTooltip).
   항목 표시가 범례와 같은 실선/점선 스와치라 CT#1·CT#2 구분이 그대로 이어진다.
   body 바로 아래 붙으므로 카드 밖으로 잘리지 않고, 마우스 이벤트는 통과시킨다. */
.chart-tooltip {
  position: absolute; z-index: 1000; pointer-events: none; white-space: nowrap;
  transform: translate(-50%, calc(-100% - 14px));
  background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 9px 12px; box-shadow: 0 6px 18px rgba(0, 0, 0, .45);
  opacity: 0; transition: opacity .12s;
}
.chart-tooltip .tt-title {
  color: var(--text-secondary); font-family: var(--font-mono); font-size: 12px;
  margin-bottom: 7px; padding-bottom: 6px; border-bottom: 1px solid var(--border);
}
.chart-tooltip .tt-row { display: flex; align-items: center; gap: 8px; padding: 2px 0; }
.chart-tooltip .tt-label { color: var(--text-secondary); font-family: var(--font-mono); font-size: 12.5px; }
.chart-tooltip .tt-val {
  color: #fff; font-family: var(--font-mono); font-weight: 700; font-size: 13.5px; margin-left: auto;
}

/* 카드에 이미 있는 값을 그래프 옆에 또 큰 패널로 반복하지 않고, 범례 한 줄에 색·이름·최신값을 같이 담는다. */
.custom-legend { display: flex; justify-content: center; flex-wrap: wrap; gap: 6px; flex: 0 0 auto; padding-top: 14px; }
.legend-item {
  display: flex; align-items: center; gap: 8px; padding: 6px 12px; border-radius: 6px;
  border: 1px solid transparent; cursor: pointer; user-select: none; transition: all .18s;
}
.legend-item:hover { background: rgba(255, 255, 255, 0.05); border-color: rgba(255, 255, 255, 0.08); }
.legend-swatch { display: block; flex-shrink: 0; }
.legend-label { color: var(--text-secondary); font-family: var(--font-mono); font-size: 12.5px; }
.legend-val { color: var(--text-primary); font-family: var(--font-mono); font-weight: 700; font-size: 13.5px; }
.legend-item.is-hidden { opacity: .45; }
.legend-item.is-hidden .legend-label, .legend-item.is-hidden .legend-val { text-decoration: line-through; color: var(--text-muted); }

.chart-tabs { display: flex; gap: 8px; flex: 0 0 auto; }
.chart-tab {
  background: var(--bg-secondary); border: 1px solid var(--border); color: var(--text-secondary);
  border-radius: var(--radius-sm); padding: 8px 18px; font-size: 14px; font-weight: 600;
  cursor: pointer; transition: all .2s; font-family: var(--font-sans);
}
.chart-tab:hover:not(.active) { border-color: var(--border-glow); color: var(--text-primary); }
.chart-tab.active {
  border-color: transparent; color: #fff;
  background: linear-gradient(135deg, var(--accent-cyan), var(--accent-blue));
}

/* ---------- Map ---------- */
#map { border-radius: var(--radius); background: var(--bg-secondary); }
/* 밝은 OSM 타일만 다크 UI 톤에 맞춰 톤다운한다(CARTO 다크 타일은 API 키가 필요해져 제외).
   전용 pane(dashboard.js의 osmPane)에만 걸어서 위성/KHOA 레이어는 원래 색 그대로 보이게 한다. */
.leaflet-osm-pane { filter: grayscale(0.25) brightness(0.75) contrast(1.05); }
.leaflet-popup-content-wrapper, .leaflet-popup-tip { background: var(--bg-card); color: var(--text-primary); }
.leaflet-container a.leaflet-popup-close-button { color: var(--text-secondary); }

/* 지도 레이어 선택(기본/위성/수심(KHOA)/전자해도(KHOA)) — sts_sp48의 #layerToggle과 같은 자리
   (지도 우상단 오버레이)지만 색은 이 앱의 다크 카드 톤에 맞춘다. */
.map-layer-toggle {
  position: absolute; z-index: 500; top: 10px; right: 10px;
  background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 9px 11px; box-shadow: 0 4px 14px rgba(0, 0, 0, .35);
}
.map-layer-toggle .layer-radio {
  display: flex; align-items: center; gap: 6px; margin-bottom: 6px; cursor: pointer;
  font-size: 12.5px; font-weight: 500; color: var(--text-secondary); white-space: nowrap;
}
.map-layer-toggle .layer-radio:last-child { margin-bottom: 0; }
.map-layer-toggle .layer-radio:hover { color: var(--text-primary); }
.map-layer-toggle .layer-radio input { width: 13px; height: 13px; accent-color: var(--accent-cyan); cursor: pointer; }
.map-layer-toggle .layer-divider { height: 1px; background: var(--border); margin: 7px 0; }

/* 관측소 마커 팝업 — 카드에서 쓰는 어휘(관측소색 강조 바, mono 값, muted 라벨)를 그대로 가져와
   지도 위에서도 같은 디자인으로 읽히게 한다. Leaflet 기본 여백(13px 24px)은 우리 레이아웃과
   안 맞아 0으로 비우고 .buoy-popup 안에서 다시 잡는다. */
.buoy-popup-wrap .leaflet-popup-content-wrapper {
  background: var(--bg-card); color: var(--text-primary); border: 1px solid var(--border);
  border-radius: var(--radius-sm); box-shadow: 0 10px 28px rgba(0, 0, 0, .5);
}
.buoy-popup-wrap .leaflet-popup-content { margin: 0; }
.buoy-popup-wrap .leaflet-popup-tip { background: var(--bg-card); border: 1px solid var(--border); box-shadow: none; }
.buoy-popup-wrap .leaflet-popup-close-button {
  color: var(--text-secondary) !important; top: 6px !important; right: 8px !important;
}
.buoy-popup-wrap .leaflet-popup-close-button:hover { color: var(--text-primary) !important; }

.buoy-popup { min-width: 220px; padding: 12px 14px; font-family: var(--font-sans); }
/* 관측소명만으로 이미 구분되므로 관측소 ID는 따로 안 보여준다 — 팝업 오른쪽 위의 닫기(X)
   버튼과 겹쳐 보이기도 했다. 대신 오른쪽에 padding을 둬 이름이 길어도 X 버튼과 안 겹치게 한다. */
.buoy-popup-header {
  padding: 0 20px 8px 8px; margin-bottom: 8px; border-bottom: 1px solid var(--border);
  border-left: 3px solid var(--station-color, var(--border));
}
.buoy-popup-name { font-size: 14.5px; font-weight: 700; color: var(--text-primary); }
.buoy-popup-row { display: flex; justify-content: space-between; align-items: baseline; gap: 16px; padding: 3px 0; }
.buoy-popup-label { font-size: 12px; color: var(--text-secondary); white-space: nowrap; }
.buoy-popup-val {
  font-family: var(--font-mono); font-size: 12.5px; font-weight: 600; color: var(--text-primary);
  white-space: nowrap; text-align: right;
}

/* ---------- Trace page (이동정보 조회) ---------- */
/* 필터 패널은 고정 높이, 지도:표 1:1 영역이 나머지를 채운다(그래프 조회와 같은 FHD 맞춤 방식) */
.trace-page-wrap {
  display: flex; flex-direction: column; gap: 12px;
  height: calc(100vh - 60px - 2rem);
  min-height: 560px;
}
.trace-page-wrap .filter-panel { flex: 0 0 auto; }

.trace-body { flex: 1 1 auto; min-height: 0; display: flex; gap: 12px; }
.trace-map-col { flex: 1 1 0; min-width: 0; position: relative; }
.trace-table-col { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; overflow: hidden; }

.trace-install-info {
  flex: 0 0 auto; display: flex; align-items: baseline; gap: 16px; flex-wrap: wrap;
  padding: 14px 16px; border-bottom: 1px solid var(--border);
}
.trace-install-label { font-size: 15px; font-weight: 700; color: var(--accent-gold); white-space: nowrap; }
.trace-install-station { font-size: 16px; font-weight: 600; color: var(--text-primary); white-space: nowrap; }
.trace-install-val {
  font-family: var(--font-mono); font-size: 15.5px; color: var(--accent-sky);
  margin-left: auto; white-space: nowrap;
}

.trace-table-wrap { flex: 1 1 auto; min-height: 0; overflow-y: auto; }

/* ---------- Login ---------- */
.login-page { min-height: 100vh; display: flex; align-items: center; justify-content: center; }
.login-container {
  background: var(--bg-card); border: 1px solid var(--border); border-radius: 16px;
  width: 100%; max-width: 420px; padding: 48px 40px; box-shadow: 0 0 24px rgba(6, 182, 212, .15);
}
.login-header { text-align: center; margin-bottom: 32px; }
.login-header h1 { font-size: 19px; font-weight: 700; margin: 0; color: var(--text-primary); }
.login-form .form-label { color: var(--text-secondary); font-size: 13px; font-weight: 500; }
.login-form .form-control {
  background: var(--bg-primary); border: 1px solid var(--border); color: var(--text-primary);
  border-radius: var(--radius-sm); padding: 12px 16px;
}
.login-form .form-control:focus {
  border-color: var(--accent-cyan); box-shadow: 0 0 0 3px rgba(6, 182, 212, .15);
  background: var(--bg-primary); color: var(--text-primary);
}
.login-form .form-control::placeholder { color: var(--text-muted); }
/* 비밀번호 입력칸의 '표시' 눈동자 아이콘(Edge/크롬 기반 브라우저 내장) - 기본이 검정이라
   어두운 배경에서 안 보인다. 날짜 입력의 달력 아이콘(.filter-panel)과 같은 방식으로 반전시킨다. */
.login-form .form-control::-ms-reveal { filter: invert(1) brightness(1.8); cursor: pointer; }
.login-btn {
  background: linear-gradient(135deg, var(--accent-cyan), var(--accent-blue));
  border: none; border-radius: var(--radius-sm); color: #fff; font-weight: 600; padding: 12px;
  transition: all .25s; width: 100%;
}
.login-btn:hover { transform: translateY(-1px); box-shadow: 0 6px 20px rgba(6, 182, 212, .35); color: #fff; }

/* ---------- Account (비밀번호 변경) ---------- */
/* 로그인 페이지와 같은 카드(.login-container/.login-form/.login-btn)를 재사용한다 - 이미
   navbar가 있는 화면이라 .login-page(100vh)는 그대로 못 쓰고, 남은 영역 안에서만 가운데로 둔다. */
.account-page {
  display: flex; align-items: center; justify-content: center;
  min-height: calc(100vh - 60px - 2rem);
}
.account-user { font-size: 14px; font-weight: 500; color: var(--accent-cyan); }

/* ---------- History page ---------- */
.filter-panel { padding: 16px; }
/* 라벨을 없앤 대신, 시작 묶음(일자+시각)과 종료 묶음 사이의 ~ 가 "기간"이라는 걸 알린다.
   좌우 여백을 Bootstrap 거터(g-2 = 8px)보다 넉넉히 줘서 두 묶음이 눈으로 갈라지게 한다. */
.filter-panel .range-sep {
  color: var(--text-secondary); font-size: 17px; font-weight: 700; line-height: 1;
  padding-left: 14px; padding-right: 14px;
}
/* 조회는 입력 묶음과 성격이 다른 실행 버튼이라 한 칸 띄운다 */
.filter-panel .search-gap { margin-left: 16px; }
.filter-panel .form-control, .filter-panel .form-select {
  background-color: var(--bg-secondary); border: 1px solid var(--border); color: var(--text-primary);
}
.filter-panel .form-select {
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23e2e8f0' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
}
/* 날짜 입력의 달력 아이콘은 브라우저 기본이 검정이라 다크 배경에서 안 보인다 - 반전시켜 흰색으로 */
.filter-panel input[type="date"]::-webkit-calendar-picker-indicator {
  filter: invert(1) brightness(1.7);
  opacity: .8; cursor: pointer;
}
.filter-panel input[type="date"]::-webkit-calendar-picker-indicator:hover { opacity: 1; }
.filter-panel .form-select option { background: var(--bg-secondary); color: var(--text-primary); }
.filter-panel .form-control:focus, .filter-panel .form-select:focus {
  background-color: var(--bg-secondary); color: var(--text-primary);
  border-color: var(--accent-cyan); box-shadow: 0 0 0 3px rgba(6, 182, 212, .15);
}
.btn-cyan {
  background: linear-gradient(135deg, var(--accent-cyan), var(--accent-blue));
  border: none; color: #fff; font-weight: 600; border-radius: var(--radius-sm);
}
.btn-cyan:hover { color: #fff; box-shadow: 0 4px 14px rgba(6, 182, 212, .3); }
.btn-outline-panel {
  border: 1px solid var(--border); color: var(--text-secondary); background: transparent; border-radius: var(--radius-sm);
}
.btn-outline-panel:hover { border-color: var(--accent-cyan); color: var(--accent-cyan); background: rgba(6, 182, 212, .08); }

/* 초록 버튼 - 파란 계열(btn-cyan)과 나란히 설 때 색만으로 구분되게 한다.
   CSV 다운로드(데이터 조회)와 수정(부이 관리)이 같이 쓴다. */
.btn-green {
  background: linear-gradient(135deg, var(--accent-teal), var(--accent-green));
  border: none; color: #fff; font-weight: 600; border-radius: var(--radius-sm);
}
.btn-green:hover { color: #fff; box-shadow: 0 4px 14px rgba(34, 197, 94, .35); }
.btn-green:disabled { opacity: .45; box-shadow: none; }

/* ---------- Chart page (그래프 조회) ---------- */
/* 필터 패널 + 항목 패널은 고정 높이, 그래프 패널만 나머지를 채운다(대시보드의
   .dashboard-grid와 같은 방식) — 그래야 FHD 한 화면 안에서 범례가 잘리지 않는다. */
.chart-page-wrap {
  display: flex; flex-direction: column; gap: 12px;
  height: calc(100vh - 60px - 2rem);
  min-height: 560px;
}
.chart-page-wrap .filter-panel, .chart-page-wrap .metric-panel { flex: 0 0 auto; }

.metric-panel { padding: 14px 16px; }
.metric-panel-row { display: flex; align-items: center; flex-wrap: wrap; gap: 16px; }

/* 깊이(전체/표층/5m수심), 항목(수온(CT#1)/수온(CT#2)/... ) — 둘 다 같은 네모 칩 스타일
   (.chip-radio)을 쓴다. 항목 쪽은 '전체' 범위에서 checkbox로, 그 외엔 radio로 input의 type이
   바뀌지만(chart-page.js) 모양은 그대로다. 깊이 칩은 cyan/blue, 항목 칩은 CSV 다운로드 버튼
   (btn-green)과 같은 teal/green으로 눌렀을 때 색을 갈라 두 그룹을 구분한다. */
.depth-filter, .metric-item-filter { display: flex; gap: 8px; flex: 0 0 auto; flex-wrap: wrap; }
.chip-radio { margin: 0; cursor: pointer; }
.chip-radio input { position: absolute; opacity: 0; pointer-events: none; }
.chip-radio span {
  display: inline-block; background: var(--bg-secondary); border: 1px solid var(--border);
  color: var(--text-secondary); border-radius: var(--radius-sm); padding: 7px 16px;
  font-size: 13.5px; font-weight: 600; transition: all .2s;
}
.chip-radio span:hover { border-color: var(--border-glow); color: var(--text-primary); }
.chip-radio input:focus-visible + span { outline: 2px solid var(--accent-cyan); outline-offset: 2px; }

.depth-filter .chip-radio input:checked + span {
  border-color: transparent; color: #fff;
  background: linear-gradient(135deg, var(--accent-cyan), var(--accent-blue));
}
.metric-item-filter .chip-radio input:checked + span {
  border-color: transparent; color: #fff;
  background: linear-gradient(135deg, var(--accent-teal), var(--accent-green));
}

.metric-divider { width: 1px; align-self: stretch; background: var(--border); flex: 0 0 auto; }

.chart-page-panel {
  background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 16px 20px; display: flex; flex-direction: column;
  flex: 1 1 auto; min-height: 0;
}
.chart-page-canvas { position: relative; flex: 1 1 auto; min-height: 0; }
.chart-page-canvas canvas { position: absolute; inset: 0; width: 100% !important; height: 100% !important; }
.chart-msg {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  color: var(--text-muted); font-size: 14px; z-index: 2;
}
.chart-page-panel .custom-legend { flex: 0 0 auto; }

.chart-zoom-toolbar { display: flex; justify-content: flex-end; gap: 6px; flex: 0 0 auto; margin-bottom: 8px; }
.chart-zoom-toolbar .btn { padding: 4px 10px; font-size: 12px; }

/* Bootstrap 5.3 의 표 색은 --bs-table-color-state(hover/active) > --bs-table-color-type(줄무늬)
   > --bs-table-color 순으로 셀에 적용된다. 그런데 줄무늬/hover 의 '글자색' 기본값이
   --bs-emphasis-color 이고, 이 페이지는 data-bs-theme 를 지정하지 않아 라이트 테마의 검정이
   들어온다. 배경색만 어둡게 덮고 글자색을 지정하지 않으면 홀수 행과 hover 행의 글자가 검정으로
   찍혀 어두운 배경에 묻힌다. 그래서 bg 뿐 아니라 color 도 전부 짝을 맞춰 지정한다. */
.table-dark-custom {
  --bs-table-bg: var(--bg-card);
  --bs-table-color: var(--text-primary);
  --bs-table-border-color: var(--border);
  --bs-table-striped-color: var(--text-primary);
  --bs-table-striped-bg: rgba(255, 255, 255, 0.035);
  --bs-table-active-color: #fff;
  --bs-table-active-bg: rgba(6, 182, 212, 0.16);
  /* hover 는 줄무늬 위에 얹히므로, 줄무늬와 확실히 구분되도록 배경을 진하게 주고 글자는 흰색으로 */
  --bs-table-hover-color: #fff;
  --bs-table-hover-bg: rgba(6, 182, 212, 0.16);
  font-family: var(--font-mono);
  font-size: 13px;
}
.table-dark-custom > tbody > tr { transition: background-color .12s; }
/* 헤더 글자색은 thead 에 color 를 주는 것으로는 안 된다 - Bootstrap 이 셀(th/td)에 직접
   color 를 걸기 때문에 덮인다. 셀 기준 변수로 지정해야 먹는다. */
.table-dark-custom > thead > tr > th {
  --bs-table-bg: var(--bg-secondary);
  --bs-table-color: var(--text-secondary);
  font-family: var(--font-sans); font-weight: 600; white-space: nowrap;
}

/* ---------- 부이 관리 ---------- */
.page-title { font-size: 20px; font-weight: 700; margin: 0 0 14px; color: var(--text-primary); }

/* 폰트를 키우면 자동 열 폭(table-layout:auto)에서는 내용이 긴 관측소명/비고가 IMEI·위경도
   열에 폭을 빼앗긴다. 비율을 고정해 두고 긴 항목에 폭을 몰아준다.
   비고 열이 없으므로 합계가 100%가 되게 맞췄다. */
.station-table { font-size: 15px; table-layout: fixed; }
.station-table col.c-id { width: 9%; }
.station-table col.c-nm { width: 28%; }
.station-table col.c-lat, .station-table col.c-lon { width: 12%; }
.station-table col.c-imei { width: 20%; }
/* 수정 + 저장 두 개와 결과 메시지가 한 줄에 들어가야 한다 */
.station-table col.c-save { width: 19%; }

.station-table > thead > tr > th { font-size: 14px; }
/* 저장 버튼이 열 오른쪽에 붙으므로 머리글도 같은 쪽으로 맞춘다 */
.station-table > thead > tr > th.th-save { text-align: right; padding-right: 6px; }
.station-table > tbody > tr > td { padding: 9px 6px; vertical-align: middle; }
.station-table .stn-id {
  font-family: var(--font-mono); font-size: 15px; font-weight: 700;
  color: var(--accent-cyan); white-space: nowrap;
}

/* table-layout:fixed 에서는 칸 폭을 열이 정하므로 입력칸은 100% 로 채우기만 한다
   (min-width 가 남아 있으면 고정 폭보다 커져서 열을 다시 밀어낸다). */
.station-table .cell-input {
  width: 100%; min-width: 0;
  background-color: var(--bg-secondary); border: 1px solid var(--border); color: var(--text-primary);
  border-radius: var(--radius-sm); padding: 8px 12px; font-size: 15px;
}
.station-table .cell-input.num { font-family: var(--font-mono); }
.station-table .cell-input:focus {
  background-color: var(--bg-secondary); color: var(--text-primary);
  border-color: var(--accent-cyan); box-shadow: 0 0 0 3px rgba(6, 182, 212, .15);
}

/* 저장 메시지를 버튼 아래에 두면 그 셀만 높아져서 버튼이 나머지 입력칸과 세로 중앙이
   어긋난다. 같은 줄에 나란히 놓고 가운데 정렬로 맞춘다. */
.station-table .save-cell { display: flex; align-items: center; justify-content: flex-end; gap: 10px; }
.station-table .btn-save, .station-table .btn-edit {
  padding: 8px 18px; font-size: 14px; white-space: nowrap; flex: 0 0 auto;
}
.station-table .btn-save:disabled { opacity: .45; box-shadow: none; }
/* 잠긴 입력칸은 '지금은 못 고친다'가 보여야 하지만, 값 자체는 읽어야 하므로 글자는 흐리게
   하지 않는다. Bootstrap 기본 :disabled 배경(밝은 회색)만 다크 톤으로 덮는다. */
.station-table .cell-input:disabled {
  background-color: rgba(255, 255, 255, .03); border-color: rgba(255, 255, 255, .07);
  color: var(--text-secondary); opacity: 1; cursor: default;
}
/* 편집 중인 행은 한눈에 보이게 */
.station-table tr.is-editing > td { background-image: linear-gradient(rgba(6, 182, 212, .07), rgba(6, 182, 212, .07)); }
.station-table .save-msg { font-size: 13px; text-align: right; min-width: 0; }
.station-table .save-msg.ok { color: var(--accent-green); }
.station-table .save-msg.err { color: var(--accent-red); }

/* ---------- 모바일 / 좁은 화면 ---------- */
/* 위의 배치는 전부 FHD 한 화면(스크롤 없음)이 전제라, 화면 높이(100vh)에 묶인 컨테이너 안에서
   각 칸이 flex 비율(flex-basis 0)로 높이를 나눠 갖는다. 좁은 화면에서 컨테이너 높이만 auto 로
   풀면 비율로 나눌 높이 자체가 없어져 칸들이 0 으로 접히고 내용이 서로 겹친다.
   그래서 여기서는 컨테이너를 화면 높이에서 떼어 내 페이지 전체를 상하 스크롤로 바꾸고,
   지도/그래프/카드처럼 내용으로 높이가 정해지지 않는 칸에만 고정 높이를 준다.
   FHD 화면에는 이 블록이 적용되지 않는다. */
@media (max-width: 1199.98px) {
  /* 네비바: 1줄 = 로고 + 사용자, 2줄 = 메뉴(넘치면 가로로 밀어서 본다) */
  .app-navbar { flex-wrap: wrap; row-gap: 6px; padding-top: 8px; padding-bottom: 8px; }
  .app-navbar .navbar-brand { font-size: 16px; margin-right: 8px; }
  .app-nav-user { gap: 10px !important; }
  .app-nav-links {
    order: 3; width: 100%; flex-wrap: nowrap; overflow-x: auto; -webkit-overflow-scrolling: touch;
  }
  .app-navbar .nav-link { padding: 6px 12px !important; white-space: nowrap; font-size: 14px; }
  .container-fluid.p-3 { padding: 10px !important; }

  /* 대시보드: 지도 → 그래프 → BC-1 → BC-2 순으로 위아래로 쌓는다 */
  .dashboard-grid { flex-direction: column; height: auto; min-height: 0; gap: 10px; }
  .dash-col-mapchart, .dash-col-cards { flex: 0 0 auto; gap: 10px; }
  .dash-map-pane { flex: 0 0 auto; height: 340px; }
  .dash-chart-pane { flex: 0 0 auto; }
  .chart-section-grow { flex: 0 0 auto; height: 320px; }
  .chart-tabs { flex-wrap: wrap; gap: 6px; }
  .chart-tab { padding: 6px 12px; font-size: 13px; }
  .buoy-cards-stack { flex-direction: column; gap: 10px; }
  /* 카드는 cqh(카드 높이 기준) 로 글자 크기를 잡으므로 높이를 반드시 확정해 줘야 한다 */
  .buoy-cards-stack .buoy-card { flex: 0 0 auto; height: 600px; padding: 20px 16px; }
  .buoy-cards-stack .buoy-card.limited { height: 480px; }
  .buoy-card-header { margin-bottom: 24px; }
  .obs-time-row { margin-bottom: 16px; }
  /* 값 열 184px 은 FHD 글자 크기(최대 34px) 기준 — 모바일 글자 크기(약 20px)에선 130px 이면 경도가 들어간다 */
  .grp-title, .item-row { grid-template-columns: minmax(0, 1fr) 130px 52px; column-gap: 8px; }
  .item-unit { font-size: 13px; }

  /* 조회 필터: 기간 구분(~)/조회 버튼 앞 여백을 줄여 한 줄에 더 들어가게 */
  .filter-panel { padding: 12px; }
  .filter-panel .range-sep { padding-left: 2px; padding-right: 2px; }
  .filter-panel .search-gap { margin-left: 0; }

  /* 그래프 조회 */
  .chart-page-wrap { height: auto; min-height: 0; gap: 10px; }
  .metric-panel-row { gap: 10px; }
  .metric-divider { width: 100%; height: 1px; align-self: auto; }
  /* flex: 0 0 auto 그대로면 칩 6개가 한 줄 폭(약 720px)을 고집해 화면 밖으로 나간다 */
  .metric-item-filter { flex: 1 1 100%; min-width: 0; }
  .chart-page-panel { flex: 0 0 auto; padding: 12px; }
  .chart-page-canvas { flex: 0 0 auto; height: 380px; }

  /* 이동정보 조회: 지도 위, 표 아래 */
  .trace-page-wrap { height: auto; min-height: 0; gap: 10px; }
  .trace-body { flex-direction: column; gap: 10px; }
  .trace-map-col { flex: 0 0 auto; height: 360px; }
  .trace-table-col { flex: 0 0 auto; }
  .trace-table-wrap { flex: 0 0 auto; max-height: 420px; }
  .trace-install-val { margin-left: 0; }

  /* 부이 관리: 비율 고정 표라 좁으면 입력칸이 뭉개진다 — 최소 폭을 두고 가로 스크롤로 본다 */
  .station-table { min-width: 860px; }

  /* 로그인 */
  .login-page { padding: 16px; }
  .login-container { padding: 32px 22px; }
}
/* 폰 세로 폭에서는 로고 + 사용자명 + 로그아웃이 한 줄에 안 들어가 네비바가 3줄이 된다 — 사용자명만 뺀다 */
@media (max-width: 479.98px) {
  .app-nav-user .user-name { display: none; }
}
