/* 팔레트: dataviz 스킬 reference palette (검증된 기본 순서 슬롯 1~3) */
.viz-root {
  color-scheme: light;
  --surface-1: #fcfcfb;
  --page: #f9f9f7;
  --text-primary: #0b0b0b;
  --text-secondary: #52514e;
  --muted: #898781;
  --grid: #e1e0d9;
  --baseline: #c3c2b7;
  /* 스파크라인 기준선(전일 종가 가로 점선)·세션 경계(세로 점선) 전용 잉크.
     --baseline 과 갈라 둔 이유: 저쪽은 **테두리**용(탭·칩 활성, 카드 hover)이고,
     이쪽은 캔버스 위에 놓이는 **데이터 참조선**이라 요구 대비가 다르다.
     --baseline(#c3c2b7)을 그대로 쓰면 다크 유리 카드 바닥(rgb 46,51,60)에서
     1.06:1 까지 떨어져 사실상 안 보인다 — 유리 틴트 rgb(58,58,62)가 다크
     --baseline(#383835)과 거의 같은 색이기 때문. 값은 눈대중이 아니라
     tools/probe_baseline_vis.py 가 4가지 바닥(라이트/다크 × 유리 켬/끔)에서 실측한다.
     값을 고른 방법 — 눈대중이 아니라 **--muted(#898781)에서 잉크 쪽으로 한 칸씩
     밀어, 최악 얼룩 바닥(얼룩 3개가 겹친 지점)에서 가로 3:1(WCAG 1.4.11 그래픽
     요소)·세로 2:1 을 넘는 첫 지점**. 라이트는 −17, 다크는 +10 이다. 같은 중성
     회색 계열에서 최소한만 움직이므로 테마와 따로 놀지 않는다.
     verify_glass [11] 이 이 값과 대비·픽셀 스냅을 음성 대조군과 함께 단정한다. */
  --spark-base: #787670;
  --border: rgba(11, 11, 11, 0.10);
  --s-prsn: #2a78d6;   /* 개인: blue (slot 1) */
  --s-frgn: #008300;   /* 외국인: green (slot 2) */
  --s-orgn: #e87ba4;   /* 기관: magenta (slot 3) */
  --s-etcc: #eda100;   /* 기타법인: yellow (slot 4) */
  --s-index: #0b0b0b;  /* 지수: 단일계열 문맥 라인 → 카테고리 슬롯 대신 잉크 */
  /* 프로그램매매 2계열. 위 차트가 slot 1~4 를 다 쓰고 있어 6·7 을 쓴다 —
     slot 2(=--pos)·5(초록 계열)·8(=--neg)은 등락색으로 오독돼 제외. */
  --s-arbt: #4a3aa7;   /* 차익거래: violet (slot 7) */
  --s-nabt: #eb6834;   /* 비차익거래: orange (slot 6) */
  --vol-fill: #b9b8ac; /* 거래대금 막대: 문맥용 중립 */
  --day-band: rgba(11, 11, 11, 0.035); /* 다일 뷰 거래일 교차 음영: 데이터가 아니라 배경 구획 */
  --warn-bg: #fab21922;
  --fresh-bg: #00830026;   /* 갱신 순간 배지 플래시 (slot 2 저채도 틴트) */
  /* 신용·미수 화면 — 검증된 같은 슬롯을 의미 이름으로 재사용 (차트마다 독립 시스템) */
  --c-margin: #2a78d6;    /* 신용융자 전체 (slot 1) */
  --c-margin-k: #008300;  /* 유가증권 (slot 2) */
  --c-margin-q: #e87ba4;  /* 코스닥 (slot 3) */
  /* 증시 대기자금 3선. 1번 차트가 신용융자 3선(slot 1~3)과 한 legend를 쓰므로
     같은 차트 안에서 slot 4~6을 이어 쓴다 — 슬롯 순서 자체가 CVD 안전장치라
     건너뛰면 깨진다(yellow↔orange를 붙여봤더니 normal ΔE 13.7로 FAIL).
     검증: validate_palette.js 1~6 슬롯 light/dark 전항목 PASS. */
  --c-dep: #eda100;       /* 투자자예탁금 (slot 4) */
  --c-ddep: #1baf7a;      /* 장내파생 거래예수금 (slot 5) */
  --c-rp: #eb6834;        /* 대고객 RP 매도잔고 (slot 6) */
  --c-rcv: #2a78d6;       /* 미수금 (2번 차트의 slot 1) */
  --c-liq: #eda100;       /* 반대매매 금액 막대 (slot 4) */
  /* 반대매매 비중 (slot 3). 금액과 색을 공유했더니 — 같은 현상의 두 표현이라 묶으려던 의도였는데
     — 어느 축이 어느 계열 것인지 실제로 반대로 읽혔다. 축이 좌/우로 번갈아 붙는 4밴드에서
     색은 계열을 가리키는 유일한 단서라, 다른 계열이면 다른 색이어야 한다. */
  --c-liqpct: #e87ba4;
  /* 상승/하락. 글로벌 관례(상승 초록 · 하락 빨강)를 따른다. 빨강은 새로 고르지 않고
     검증을 통과한 8슬롯 팔레트의 slot 8(#e34948 / dark #e66767)을 그대로 쓴다.
     ⚠️ 초록↔빨강은 적록색약에서 가장 구분이 어려운 쌍이라 **색만으로 방향을 말하지
        않는다** — 등락 표시에 ▲/▼ 를 함께 붙여 두 번째 단서를 남긴다. */
  --pos: #008300;
  --neg: #e34948;
  --pos-bg: #0083001f;
  --neg-bg: #e349481f;
  /* 커스텀 차트(/chart) — 사용자가 아무 지표나 골라 담는 화면이라 의미 이름을 붙일 수 없다.
     dataviz reference palette의 8슬롯 **고정 순서**를 그대로 쓴다 (순환·생성 금지).
     검증: validate_palette.js 8슬롯 — light/dark 전항목 PASS
     (worst adjacent CVD ΔE 9.1 light / 8.4 dark, normal-vision 19.6 / 19.3).
     light 모드의 magenta·yellow·aqua는 3:1 미달(WARN) → relief 규칙 적용:
     계열 타일·범례·밴드 라벨·표 뷰로 이름을 항상 보이게 한다.
     슬롯은 계열이 선택될 때 배정되고 해제될 때까지 **유지**된다 — 계열 수가 바뀌어도
     남은 계열의 색이 다시 칠해지지 않게(색은 순위가 아니라 개체를 따른다). */
  --series-1: #2a78d6;
  --series-2: #008300;
  --series-3: #e87ba4;
  --series-4: #eda100;
  --series-5: #1baf7a;
  --series-6: #eb6834;
  --series-7: #4a3aa7;
  --series-8: #e34948;
  /* 국채 3선은 8슬롯 중 slot 1(파랑)·4(노랑)·3(마젠타)를 그대로 쓴다 — 별도 램프 변수를
     두지 않는다(macro_export RATE_COLS 주석에 선정 근거). 여기서 주의할 것 하나:
     slot 2(초록)는 --pos, slot 8(빨강)은 --neg 와 **같은 색**이라 이 화면에서 등락
     방향을 뜻한다. 계열색으로 쓰면 의미가 충돌하므로 두 슬롯은 카드 팔레트에서 제외한다
     (verify_macro 가 단정). 슬롯 값을 바꿀 때 이 규칙을 같이 확인할 것. */
}
/* 다크 테마 — theme.js가 <html data-theme>를 항상 확정 지정 (자동 모드면 OS 설정을 해석해서 넣음) */
html[data-theme="dark"] .viz-root {
    color-scheme: dark;
    --surface-1: #1a1a19;
    --page: #0d0d0d;
    --text-primary: #ffffff;
    --text-secondary: #c3c2b7;
    --muted: #898781;
    --grid: #2c2c2a;
    --baseline: #383835;
    --spark-base: #93918b;   /* 다크는 바닥에서 멀어지는 방향(밝게) — 위 주석 참조 */
    --border: rgba(255, 255, 255, 0.10);
    --s-prsn: #3987e5;
    --s-frgn: #008300;
    --s-orgn: #d55181;
    --s-etcc: #c98500;
    --s-index: #ffffff;
    --s-arbt: #9085e9;
    --s-nabt: #d95926;
    --vol-fill: #4e4e4a;
    --day-band: rgba(255, 255, 255, 0.045);
    --fresh-bg: #00830040;
    --c-margin: #3987e5;
    --c-margin-k: #008300;
    --c-margin-q: #d55181;
    --c-dep: #c98500;
    --c-ddep: #199e70;
    --c-rp: #d95926;
    --c-rcv: #3987e5;
    --c-liq: #c98500;
    --c-liqpct: #d55181;
    --pos: #008300;
    --neg: #e66767;
    --pos-bg: #00830040;
    --neg-bg: #e6676730;
    /* /chart 8슬롯 다크 스텝 — 같은 8색을 다크 표면(#1a1a19)에 맞춰 다시 고른 것.
       라이트값의 자동 반전이 아니다. 전항목 PASS (대비는 8색 모두 ≥3:1). */
    --series-1: #3987e5;
    --series-2: #008300;
    --series-3: #d55181;
    --series-4: #c98500;
    --series-5: #199e70;
    --series-6: #d95926;
    --series-7: #9085e9;
    --series-8: #e66767;
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body { background: #f9f9f7; }
html[data-theme="dark"] body { background: #0d0d0d; }
.viz-root {
  max-width: 860px;
  margin: 0 auto;
  padding: 20px 16px 40px;
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
  color: var(--text-primary);
  background: var(--page);
}
/* 좌측 레일이 붙는 화면(/chart)은 860px를 쪼개면 차트가 좁아진다 → 작업대만 넓힌다 */
.viz-root.wide { max-width: 1180px; }

header { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; }
/* min-width:0 이 없으면 긴 부제가 min-content 폭을 강제해 제목 블록이 한 줄을 다 먹고
   상태 블록(테마 버튼 포함)이 다음 줄 왼쪽으로 밀려난다 → 최우측 상단 유지 불가.
   ⚠️ `:first-child` 였는데 레일 여닫기 버튼이 헤더 맨 앞에 꽂히면서 안 맞게 됐다
   (제목 블록이 아니라 아무것도 안 잡혀 헤더가 2줄로 접혔다). 제목 블록은 여전히
   **첫 div** 이므로 :first-of-type 이 정확하다 — .status 는 두 번째 div 라 안 걸린다. */
header > div:first-of-type { flex: 1 1 auto; min-width: 0; }
h1 { font-size: 20px; margin: 0 0 4px; }
.sub { margin: 0; color: var(--text-secondary); font-size: 13px; }
.unit { color: var(--muted); }
.status { flex: 0 0 auto; margin-left: auto; text-align: right; font-size: 13px; color: var(--text-secondary); }
.status .date { display: block; font-weight: 600; }
.updated.stale { background: var(--warn-bg); border-radius: 4px; padding: 1px 6px; }

/* ── 좌측 화면 이동 레일 ────────────────────────────────────────────────
   가로 탭 줄을 대신한다(shell.js가 nav.tabs를 이 레일로 통째로 교체).
   fixed 라 레이아웃에서 빠지므로, 본문을 미는 건 ≥1280px에서만 한다.
   그보다 좁으면 겹쳐 열리고 backdrop이 깔린다 → 본문 폭 손실 0.
   .navrail 은 .viz-root 안에 꽂히므로 팔레트 변수를 그대로 상속받는다
   (body 직속으로 옮기면 --surface-1 이 정의되지 않아 투명해진다). */
.navrail {
  position: fixed; top: 0; left: 0; bottom: 0; width: 180px; z-index: 40;
  background: var(--surface-1); border-right: 1px solid var(--border);
  padding: 12px 10px; overflow-y: auto; overscroll-behavior: contain;
  display: flex; flex-direction: column; gap: 3px;
  transform: translateX(-100%); visibility: hidden;
  transition: transform .16s ease, visibility 0s linear .16s;
}
html[data-navrail="open"] .navrail {
  transform: none; visibility: visible;
  transition: transform .16s ease, visibility 0s linear 0s;
}
@media (prefers-reduced-motion: reduce) { .navrail { transition: none; } }
.navrail-h { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; }
.navrail-title { font-size: 11.5px; font-weight: 700; color: var(--muted); letter-spacing: .04em; }
.navrail-x {
  margin-left: auto; font: inherit; font-size: 12px; line-height: 20px; cursor: pointer;
  border: 1px solid var(--border); background: var(--page); color: var(--text-secondary);
  border-radius: 6px; padding: 0 7px;
}
.navrail-x:hover { color: var(--text-primary); }
.navrail-item {
  display: block; border-radius: 8px; padding: 7px 10px; font-size: 14px;
  color: var(--text-secondary); text-decoration: none;
  border: 1px solid transparent; word-break: keep-all;
}
.navrail-item:hover { background: var(--day-band); color: var(--text-primary); }
.navrail-item.active {
  color: var(--text-primary); font-weight: 700;
  background: var(--day-band); border-color: var(--baseline);
}
.navrail-backdrop { position: fixed; inset: 0; z-index: 39; background: rgba(11, 11, 11, .34); }

/* ── 왼쪽 가장자리 손잡이 ─────────────────────────────────────────────
   레일이 화면 왼쪽에서 나오므로 손잡이도 **화면 가장자리**에 있어야 잡는 곳과
   열리는 곳이 같아진다. 헤더 안(제목 왼쪽)에 뒀을 때는 방향은 맞았지만 헤더
   흐름에 들어가 제목을 오른쪽으로 밀었다 — fixed 라 이제 안 민다.

   폭을 14px 로 묶은 게 핵심이다. .viz-root 는 어느 폭에서도 `padding: 20px 16px`
   이라 내용은 x=16 부터 시작한다 → 손잡이[0,14]는 본문과 **한 픽셀도 겹치지 않는다**.
   (이게 예전에 가장자리 안을 기각했던 사유였다. 폭 제한으로 해소된다.)
   높이는 화면 전체 — 마우스를 왼쪽으로 던지면 무조건 닿는다(가장자리는 무한 표적).

   ⚠️ 보이는 막대도 **화면 높이 전체**다. 처음엔 세로 64px 토막을 가운데 세웠는데,
   판정 영역은 이미 전체 높이였던 탓에 "왼쪽 위/아래에서 마우스를 대면 아무것도
   안 보이는데 눌리기는 하는" 상태가 됐다(= 가운데만 버튼처럼 보임). 보이는 것과
   눌리는 것이 같아야 하므로 막대를 top:0/bottom:0 으로 늘였다.
   평소엔 3px 실선, 손이 닿으면 폭만 부푼다 — 높이는 이미 최대라 더 자랄 데가 없다.
   완전히 감추지 않은 이유: 보이지 않는 버튼은 다음에 못 찾는다. */
.navrail-edge {
  position: fixed; left: 0; top: 0; bottom: 0; width: 14px; z-index: 41;
  border: 0; margin: 0; padding: 0; background: transparent;
  -webkit-appearance: none; appearance: none; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  color: var(--text-secondary);
  transition: left .16s ease;
}
/* 열리면 레일 오른쪽 끝으로 따라가 **접는 손잡이**가 된다 — 한 물건이 여닫는다. */
html[data-navrail="open"] .navrail-edge { left: 180px; }
/* 보이는 막대 — 판정 영역과 같은 **전체 높이** */
.navrail-edge::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0;
  width: 3px; border-radius: 0 3px 3px 0;
  background: var(--border);
  transition: width .14s ease, background .14s ease;
}
.navrail-edge-i {
  position: relative; z-index: 1; font-size: 13px; line-height: 1;
  opacity: 0; pointer-events: none; transition: opacity .14s ease;
}
.navrail-edge:hover::before, .navrail-edge:focus-visible::before {
  width: 14px; background: var(--day-band);
  box-shadow: inset -1px 0 0 var(--border);
}
.navrail-edge:hover .navrail-edge-i, .navrail-edge:focus-visible .navrail-edge-i {
  opacity: 1;
}
.navrail-edge:hover { color: var(--text-primary); }
@media (prefers-reduced-motion: reduce) {
  .navrail-edge, .navrail-edge::before, .navrail-edge-i { transition: none; }
}
/* 손가락에는 hover 가 없다 → 항상 보이는 알약으로. 여기만 **전체 높이 예외**다:
   화면 왼쪽 끝을 위아래로 다 덮으면 iOS 사파리 뒤로가기 스와이프를 삼켜서, 뒤로
   가려는 손짓이 매번 메뉴를 여는 꼴이 된다. 마우스에는 그 제스처가 없으니 전체 높이,
   손가락에는 가운데 토막 — 입력 장치가 다르면 안전한 표적도 다르다. */
@media (hover: none) {
  .navrail-edge { top: 50%; bottom: auto; height: 148px; translate: 0 -50%; width: 15px; }
  .navrail-edge::before {
    width: 15px; background: var(--day-band); border-radius: 0 5px 5px 0;
    box-shadow: inset -1px 0 0 var(--border);
  }
  .navrail-edge-i { opacity: 1; }
}
@media (min-width: 1280px) {
  html[data-navrail="open"] body { padding-left: 180px; }
  .navrail-backdrop { display: none; }
}
/* ── 우측 이벤트 레일 (메인 화면) ──────────────────────────────────────
   좌측 화면 레일의 **거울상**이다. 같은 구조를 쓰는 이유는 취향이 아니라
   일관성이다 — "가장자리를 밀면 뭔가 나온다"가 좌우 한 규칙이어야 다음에
   찾는다.

   폭 236px 은 눈대중이 아니라 **1512px(맥북 전체폭)에서 본문 오른쪽에 실제로
   남는 여백**이다(좌측 레일 열림 기준 실측). 그보다 넓게 잡으면 본문을 밀고,
   좁게 잡으면 지표명이 여러 줄로 접힌다.

   ## 임계가 둘이다 — 헷갈리기 쉬우니 여기 적어둔다
     · **640px 이상 = 밀어내기**(body padding-right). 이 CSS 블록.
     · **1440px 이상 = 기본 열림**. 이건 CSS 가 아니라 calrail.js 의 판정이다.
   즉 760px(반폭)에서는 "기본은 접혀 있지만, 열면 본문을 민다"가 된다.

   ## 왜 겹쳐 열기를 버렸나
   좌측 레일은 **지나가는 길**이라 겹쳐도 된다 — 항목을 누르면 화면을 떠나고
   레일도 같이 닫힌다. 이 레일은 반대로 **카드를 보면서 곁눈질하는 참조표**다.
   겹치면 보려던 카드를 자기가 가린다. 그래서 좌우가 어법은 같아도 여기서만
   갈린다(사용자 지적: "카드들 위에 위치하고 있어").

   ## 바닥이 640px 인 근거 (tools/probe_evrail_push.py 실측, 236px 밀어낸 상태)
     1180+ 본문 828px 그대로 4칸 ← 밀어도 손실 0 (viz-root 가 860 고정이라)
      900   632px  3칸
      760   492px  2칸 (카드 241px — 지금 3칸 236px 보다 오히려 넓다)
      640   372px  2칸 (카드 181px)  ← 여기까지
      560   292px  1칸 (한 줄짜리 세로 페이지가 된다)
      390   122px  1칸 + 잘린 요소 5개 🔴
   640 은 "2칸이 살아남는 마지막 폭"이다. 그 아래는 미는 게 가리는 것보다
   나쁘므로 겹쳐 열기(+backdrop)로 남긴다. */
.evrail {
  position: fixed; top: 0; right: 0; bottom: 0; width: 236px; z-index: 40;
  background: var(--surface-1); border-left: 1px solid var(--border);
  padding: 12px 10px; display: flex; flex-direction: column; gap: 6px;
  transform: translateX(100%); visibility: hidden;
  transition: transform .16s ease, visibility 0s linear .16s;
}
html[data-evrail="open"] .evrail {
  transform: none; visibility: visible;
  transition: transform .16s ease, visibility 0s linear 0s;
}
@media (prefers-reduced-motion: reduce) { .evrail { transition: none; } }
.evrail-h { display: flex; align-items: center; gap: 6px; }
.evrail-title { font-size: 11.5px; font-weight: 700; color: var(--muted); letter-spacing: .04em; }
.evrail-x {
  margin-left: auto; font: inherit; font-size: 12px; line-height: 20px; cursor: pointer;
  border: 1px solid var(--border); background: var(--page); color: var(--text-secondary);
  border-radius: 6px; padding: 0 7px;
}
.evrail-x:hover { color: var(--text-primary); }
.evrail-sub { font-size: 11px; color: var(--muted); }
/* 목록만 스스로 스크롤한다 — 레일 전체가 스크롤하면 머리말과 "전체 보기" 링크가
   같이 밀려 올라간다(/calendar 목록에서 겪은 그 증상). */
.evrail-list {
  flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
  border: 1px solid var(--border); border-radius: 10px; overflow-x: hidden;
  background: var(--page);
}
.evrail-more {
  display: block; text-align: center; font-size: 12px; padding: 6px 0;
  color: var(--text-secondary); text-decoration: none;
  border: 1px solid var(--border); border-radius: 8px; background: var(--page);
}
.evrail-more:hover { color: var(--text-primary); }
.evrail-backdrop { position: fixed; inset: 0; z-index: 39; background: rgba(11, 11, 11, .34); }

.ev-dh {
  position: sticky; top: 0; z-index: 2;
  display: flex; align-items: baseline; gap: 6px;
  padding: 5px 8px; font-size: 11.5px; font-weight: 700;
  background: var(--surface-1);
  border-bottom: 1px solid var(--border); border-top: 1px solid var(--border);
}
.ev-dh:first-child { border-top: 0; }
.ev-dh .rel { margin-left: auto; font-weight: 400; color: var(--muted); font-size: 11px; }
.ev-dh.is-today { color: var(--series-1); }

/* 236px 에는 /calendar 의 5칸 그리드가 안 들어간다 → 세 줄로 쌓는다.
   시각·국기·★가 한 줄, 지표명이 한 줄, 수치가 한 줄. */
.ev-row {
  padding: 5px 8px; border-bottom: 1px solid var(--grid); font-size: 12.5px;
}
.ev-row:last-child { border-bottom: 0; }
.ev-row.past { color: var(--text-secondary); }
.ev-top { display: flex; align-items: baseline; gap: 6px; }
.ev-t { font-variant-numeric: tabular-nums; color: var(--text-secondary); font-size: 11.5px; }
.ev-c { font-size: 11.5px; white-space: nowrap; }
/* 과거 날짜 머리말 — 오늘보다 한 단계 물러난다. 색만으로 구분하지 않고 왼쪽
   테두리를 함께 두어, 스크롤 중에 "지금 과거를 보고 있다"가 흐릿하게라도 남는다. */
.ev-dh.is-past { color: var(--text-secondary); }
.ev-dh.is-past .rel { color: var(--text-secondary); }

/* 국기 — 이모지가 아니라 인라인 SVG(calutil.js 국기 절 참조).
   `.ctry-code` 를 같이 두는 이유: 국기는 색으로 빨리 갈리고 코드는 흑백·색약·
   확대에서도 읽힌다. 하나가 실패해도 다른 하나가 남는다. */
.ev-c, .cal-c { display: inline-flex; align-items: center; gap: 3px; }
svg.flag {
  flex: none; display: block; width: 15px; height: 10px;
  /* 상자 반경은 공용 토큰이 아니라 1px 고정 — 15px 짜리에 20px 반경을 걸면
     국기가 알약이 된다(칩을 --g-r-chip 으로 따로 뗀 것과 같은 이유). */
  border-radius: 1px;
}
.ctry-code { font-variant-numeric: tabular-nums; letter-spacing: .02em; }
/* 국가 필터 칩 — 국기 + 나라 이름. 칩은 이미 padding 이 있어 gap 만 준다. */
.chip.has-flag { display: inline-flex; align-items: center; gap: 5px; }
.ev-imp {
  margin-left: auto; font-size: 10.5px; letter-spacing: -1px;
  color: var(--series-4); white-space: nowrap;
}
.ev-imp.i3 { color: var(--series-8); }
.ev-imp.i1 { color: var(--muted); }
/* 자체 분류(★를 우리가 매긴 것)는 점선 밑줄 — 소스 등급과 같아 보이면 안 된다. */
.ev-imp.est { text-decoration: underline dotted; text-underline-offset: 2px; }
/* 236px 에서 줄바꿈이 두 번 어긋났다.
   ① `anywhere` 는 영문 단어 중간을 끊는다("Job Opening / s") → break-word.
   ② 그런데 break-word 만으로는 **한글이 어절 중간에서 끊긴다**("전년 / 비?") —
      한국어는 어절 안에 공백이 없어 브라우저가 아무 데서나 자른다. keep-all 로
      어절을 지키고, 칸보다 긴 한 단어일 때만 break-word 가 받는다(좌측 레일 항목과
      같은 처리). */
.ev-nm {
  margin-top: 1px; line-height: 1.35;
  word-break: keep-all; overflow-wrap: break-word;
}
.ev-nm b { font-weight: 600; }
.ev-nm .per { color: var(--muted); font-size: 11px; }
/* 전월비/전년비 라벨은 **우리 판정**이라(소스가 준 구분이 아니다) 점선 밑줄로
   ★ 자체 분류와 같은 어법을 쓴다 — 같은 종류의 «우리가 매긴 것» 이다. */
.ev-nm .per.basis, .cal-nm .per.basis {
  text-decoration: underline dotted; text-underline-offset: 2px;
}

/* ── 값이 방금 바뀐 줄 ─────────────────────────────────────────────────────
   폴링으로 실제값이 채워지면 그 줄만 잠깐 강조한다. 두 경우를 **색으로** 나눈다 —
   새로 나온 값(fl-new)과 개정된 값(fl-rev). 같은 표시로 뭉개면 개정을 발표로
   오독한다.

   ⚠️ 초록·빨강은 쓰지 않는다. 이 대시보드에서 그 두 색은 **가격 방향** 전용이고,
      "방금 발표됨" 은 방향이 아니다(▲▼ 를 중성색으로 둔 것과 같은 이유). */
@keyframes ev-flash {
  from { background: var(--fl-tint); box-shadow: inset 2px 0 0 var(--fl-edge); }
  to   { background: transparent; box-shadow: inset 2px 0 0 transparent; }
}
.ev-row.is-flash {
  --fl-tint: color-mix(in srgb, var(--series-1) 16%, transparent);
  --fl-edge: var(--series-1);
  animation: ev-flash 6s ease-out 1 forwards;
}
.ev-row.fl-rev { --fl-tint: color-mix(in srgb, var(--series-4) 14%, transparent); }
.ev-row.fl-rev { --fl-edge: var(--series-4); }
/* 모션을 줄이라는 설정이면 **깜빡이지 않고** 테두리만 남긴다. 강조를 통째로 없애면
   "방금 바뀌었다"는 정보 자체가 사라진다 — 애니메이션만 끄는 게 맞다. */
@media (prefers-reduced-motion: reduce) {
  .ev-row.is-flash { animation: none; box-shadow: inset 2px 0 0 var(--fl-edge); }
}
.ev-v {
  margin-top: 2px; display: flex; flex-wrap: wrap; gap: 6px;
  font-variant-numeric: tabular-nums; font-size: 11px;
}
.ev-v span { color: var(--muted); }
.ev-v b { font-weight: 700; color: var(--text-primary); }
/* ▲▼ 는 "예상보다 높음/낮음"이지 좋다/나쁘다가 아니다 → 초록·빨강을 쓰지 않는다.
   이 대시보드에서 초록·빨강은 **가격 방향** 전용이다. */
.ev-v .sp { color: var(--text-primary); font-weight: 700; }
.ev-empty { padding: 22px 8px; text-align: center; color: var(--muted); font-size: 12px; }
/* 목록 맨 위 「지난 일정 더 보기」 — 버튼일 때도 있고 안내문일 때도 있어서 같은
   클래스로 두고 상태만 갈린다(button/div 를 섞어도 모양이 같아야 한다). */
.ev-more {
  display: block; width: 100%; box-sizing: border-box;
  font: inherit; font-size: 11px; text-align: center; padding: 7px 8px;
  color: var(--text-secondary); background: var(--surface-1);
  border: 0; border-bottom: 1px solid var(--border); cursor: pointer;
}
.ev-more:hover { color: var(--text-primary); }
/* 안내문 상태는 눌러도 아무 일이 없다 → 손가락 커서를 주지 않는다. */
.ev-more.is-end { color: var(--muted); cursor: default; }
.ev-more.is-err { color: var(--neg); cursor: pointer; }

/* 오른쪽 가장자리 손잡이 — 좌측(.navrail-edge)의 거울상. 주석은 그쪽 참조.
   폭 14px 이 핵심: .viz-root 는 어느 폭에서도 `padding: 20px 16px` 이라
   본문 내용은 오른쪽 끝에서 16px 안쪽에서 끝난다 → 한 픽셀도 안 겹친다. */
.evrail-edge {
  position: fixed; right: 0; top: 0; bottom: 0; width: 14px; z-index: 41;
  border: 0; margin: 0; padding: 0; background: transparent;
  -webkit-appearance: none; appearance: none; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  color: var(--text-secondary);
  transition: right .16s ease;
}
html[data-evrail="open"] .evrail-edge { right: 236px; }
.evrail-edge::before {
  content: ""; position: absolute; right: 0; top: 0; bottom: 0;
  width: 3px; border-radius: 3px 0 0 3px;
  background: var(--border);
  transition: width .14s ease, background .14s ease;
}
.evrail-edge-i {
  position: relative; z-index: 1; font-size: 13px; line-height: 1;
  opacity: 0; pointer-events: none; transition: opacity .14s ease;
}
.evrail-edge:hover::before, .evrail-edge:focus-visible::before {
  width: 14px; background: var(--day-band);
  box-shadow: inset 1px 0 0 var(--border);
}
.evrail-edge:hover .evrail-edge-i, .evrail-edge:focus-visible .evrail-edge-i { opacity: 1; }
.evrail-edge:hover { color: var(--text-primary); }
@media (prefers-reduced-motion: reduce) {
  .evrail-edge, .evrail-edge::before, .evrail-edge-i { transition: none; }
}
/* 손가락에는 hover 가 없다 → 항상 보이는 알약. 좌측과 같은 이유로 전체 높이가
   아니라 가운데 토막이다(iOS 사파리 가장자리 스와이프와 안 싸우게). */
@media (hover: none) {
  .evrail-edge { top: 50%; bottom: auto; height: 148px; translate: 0 -50%; width: 15px; }
  .evrail-edge::before {
    width: 15px; background: var(--day-band); border-radius: 5px 0 0 5px;
    box-shadow: inset 1px 0 0 var(--border);
  }
  .evrail-edge-i { opacity: 1; }
}
/* 밀어내기 임계. 기본 열림 임계(1440)와 **다르다** — 블록 위 주석 참조.
   backdrop 을 여기서 같이 끄는 게 중요하다: 미는데 뒤가 어두워지면 "이건 모달이니
   닫아야 한다"로 읽힌다. 미는 순간부터는 모달이 아니라 그냥 옆 칸이다. */
@media (min-width: 640px) {
  html[data-evrail="open"] body { padding-right: 236px; }
  .evrail-backdrop { display: none; }
}

/* 레일이 대체하기 전의 가로 탭 줄. JS가 꺼져 있으면 이 규칙이 그대로 살아
   원래 탭 줄이 보인다 — 화면 이동 수단이 통째로 사라지지 않게. */
.tabs { display: flex; gap: 6px; margin: 16px 0 12px; flex-wrap: wrap; align-items: center; }
.tab {
  border: 1px solid var(--border); background: var(--surface-1); color: var(--text-secondary);
  border-radius: 8px; padding: 6px 16px; font-size: 14px; cursor: pointer;
}
.tab.active { color: var(--text-primary); font-weight: 700; border-color: var(--baseline); }
.tab-sep { width: 1px; height: 22px; background: var(--grid); margin: 0 4px; }

.chips { display: flex; gap: 6px; margin: 0 0 12px; flex-wrap: wrap; }
.chip {
  border: 1px solid var(--border); background: var(--surface-1); color: var(--text-secondary);
  border-radius: 999px; padding: 4px 12px; font-size: 12.5px; cursor: pointer;
}
.chip.active { color: var(--text-primary); font-weight: 700; border-color: var(--baseline); }
.chip:disabled { opacity: .38; cursor: default; }
/* 날짜 선택 — 네이티브 date 입력을 칩과 같은 높이·모양으로 (모바일 기본 UI 그대로 쓰기 위해) */
.day-input {
  font-family: inherit; color: var(--text-primary); font-weight: 600;
  padding: 3px 10px; min-height: 26px;
}
.day-input::-webkit-calendar-picker-indicator { cursor: pointer; }
.chip.nav { padding: 4px 10px; line-height: 1; }
.day-msg { align-self: center; font-size: 12px; color: var(--muted); }
/* 종목 상세 바 — 뒤로가기 + 종목명 + 스택 방식 토글 */
.stock-bar { align-items: center; }
.stock-bar .stock-name { font-weight: 700; color: var(--text-primary); font-size: 14px; }
.stock-bar .stock-name small { font-weight: 400; color: var(--muted); margin-left: 4px; }
.stock-bar .chip-group { display: inline-flex; gap: 6px; margin-left: auto; }
/* 클릭 가능한 종목 행 */
.rank tbody tr.clk { cursor: pointer; }
.rank tbody tr.clk:hover td, .rank tbody tr.clk:focus-visible td {
  background: color-mix(in srgb, var(--s-frgn) 10%, transparent);
}
.rank tbody tr.clk .go { color: var(--muted); }
.rank tbody tr.sel td:first-child { box-shadow: inset 2px 0 0 var(--s-frgn); }
.rank tbody tr.sel td { font-weight: 600; }
.hidden { display: none; }

.tiles { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; margin-bottom: 12px; }
/* 타일이 5개 이상인 화면(신용·미수). 고정 열 수로 두면 반폭 브라우저에서 135px까지 눌려
   이름이 잘리므로, 최소 폭을 정하고 열 수를 폭에 맡긴다. `.tiles.t5`가 아래 미디어쿼리의
   `.tiles`보다 특이도가 높아 좁은 화면에서도 이 규칙이 유지된다(minmax가 알아서 2열로 접힘). */
.tiles.t5, .tiles.t6 { grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); }
/* 2개짜리 줄(프로그램매매: 차익·비차익). 아래 미디어쿼리의 `.tiles`(2열)와 같은 값이지만
   특이도가 높아야 좁은 화면에서도 이 규칙이 유지된다. */
.tiles.t2 { grid-template-columns: repeat(2, 1fr); }
/* 격자 칸의 기본 min-width는 auto라 **내용보다 좁아지지 않는다**. 칸이 좁아지면
   폭을 따르는 대신 격자를 밀어내 페이지에 가로 스크롤이 생긴다(390px 에서 37px 실측).
   0으로 풀어 칸이 폭을 따르게 하고, 넘치는 이름은 `.name`의 ellipsis가 받는다. */
.tile-row .tile { min-width: 0; }
/* 값은 숫자 하나다. 접히게 두면 「+8,848억」이 «+8,848 / 억» 으로 갈려 타일만 한 줄
   높아지고, 좌우로 세운 두 묶음의 한 줄이 조용히 깨진다(실측). 넘치면 눈에 보이도록. */
.tile-row .tile .val { white-space: nowrap; }

/* 상단 타일 두 묶음의 배치. 축이 다르므로 컨테이너는 분리한 채, 폭이 되는 화면에서만
   한 줄에 나란히 세운다(4 + 2 = 6칸). 좁은 화면에서 6칸을 밀어넣으면 한 칸이 55px까지
   눌려 이름과 값이 통째로 잘리므로, 그때는 다시 위아래로 쌓는다. */
.tile-row { display: grid; grid-template-columns: 1fr; gap: 12px; margin-bottom: 12px; }

/* 타일 묶음 — 성격이 다른 타일 줄을 끊는다. 가는 구분선 + 작은 제목이면 충분하고,
   판(카드)을 하나 더 씌우면 유리 위 유리가 되어 안 된다. */
.tile-group { padding-top: 10px; border-top: 1px solid var(--grid); }
/* 첫 묶음은 위에 끊을 것이 없다 (선만 떠 있으면 헤더와 붙어 군더더기로 읽힌다). */
.tile-group.tg-inv { padding-top: 0; border-top: 0; }
.tile-group .tiles { margin-bottom: 0; }
.tile-group-h {
  font-size: 12px; font-weight: 600; color: var(--text-secondary);
  margin: 0 0 6px; display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap;
  word-break: keep-all;
}
.tile-group-sub { font-weight: 400; font-size: 11px; color: var(--muted); }

/* ── 6칸 한 줄 모드 ────────────────────────────────────────────────────────
   투자자 4 + 프로그램 2 를 좌우로 세운다. 문턱(760px)은 눈대중이 아니라 실측이다 —
   16px/700 에서 가장 긴 값이 「-999.99조」 85px · 「+8,848억」 81px 인데,
   700px 뷰포트에서는 칸 안쪽이 79px 밖에 안 남아 값이 두 줄로 접혔다(실측된 실패).
   접히면 프로그램 타일만 19px 높아져 **한 줄이 조용히 깨진다** → 문턱을 값 기준으로 잡고,
   `.val` 에 nowrap 을 걸어 다음에 모자랄 때는 접히는 대신 넘쳐서 검사에 잡히게 한다.
   본문은 860px 고정이라 1280 이든 반폭(855) 이든 칸이 126px 로 같다.
   `4fr 2fr` — 두 묶음의 안쪽 gap 이 같으면 칸 폭이 4px 안에서 맞는다(subgrid 는
   사파리 폴백이 1열로 무너져 안 쓴다). */
@media (min-width: 760px) {
  .tile-row { grid-template-columns: 4fr 2fr; align-items: end; }
  /* 좌우로 서면 끊는 방향도 위→왼쪽으로 바뀐다. */
  .tile-group.tg-prog {
    padding-top: 0; border-top: 0;
    padding-left: 12px; border-left: 1px solid var(--grid);
  }
  /* 칸이 126px 로 좁아지므로 글자도 같이 줄인다 (기본 12/19px 이면 값이 넘친다). */
  .tile-row .tile { padding: 8px 9px; }
  .tile-row .tile .name { font-size: 11px; gap: 4px; }
  .tile-row .tile .name .dot { width: 7px; height: 7px; flex: 0 0 auto; }
  .tile-row .tile .val { font-size: 16px; }
}
.tile {
  background: var(--surface-1); border: 1px solid var(--border); border-radius: 10px;
  padding: 10px 12px;
}
.tile .name {
  font-size: 12px; color: var(--text-secondary); display: flex; align-items: center; gap: 6px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.tile .name small { color: var(--muted); }
.tile .dot { width: 9px; height: 9px; border-radius: 50%; display: inline-block; }
.dot-prsn { background: var(--s-prsn); }
.dot-frgn { background: var(--s-frgn); }
.dot-orgn { background: var(--s-orgn); }
.dot-etc_corp { background: var(--s-etcc); }
/* 색점 = «아래 차트에 이 색 선이 있다» 는 약속. 선이 없는 값에는 점을 주지 않는다. */
.dot-arbt { background: var(--s-arbt); }
.dot-nabt { background: var(--s-nabt); }
.tile .val { font-size: 19px; font-weight: 700; margin-top: 3px; }

/* ── /analytics (방문 현황) ──────────────────────────────────────────────────
   타일 밑 한 줄짜리 보조값(비율·고유 IP). 값과 같은 굵기로 두면 어느 쪽이 주인지
   안 읽혀서 muted 로 눌러 둔다. */
.tile .chg { font-size: 11px; color: var(--muted); margin-top: 2px; }
/* 분류 색점. 등락색(--series-2·5·8 = 초록/빨강)은 피한다 — 여기 숫자는 오르내림이
   아니라 «어떤 종류의 요청인가» 라서, 초록/빨강이면 좋다/나쁘다로 잘못 읽힌다. */
.dot-vhuman { background: var(--series-1); }
.dot-vbot   { background: var(--series-4); }
.dot-vscan  { background: var(--series-6); }
.dot-vself  { background: var(--series-7); }
/* 표의 경로(/credit 등)는 화면 이름 옆의 보조 정보다. */
.rank td.nm .go { color: var(--muted); font-size: 11px; margin-left: 6px; }

.chart-card {
  background: var(--surface-1); border: 1px solid var(--border); border-radius: 10px;
  padding: 12px 8px 4px;
}
.panel + .panel { margin-top: 10px; border-top: 1px solid var(--grid); padding-top: 8px; }
.panel-h {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 8px; padding: 0 8px 2px; font-size: 12px;
  /* 제목이 길면 캡션이 flex-shrink:999로 폭 0까지 눌리는데, 박스만 0이 되고 글자는
     그대로 그려져 페이지에 가로 스크롤이 생겼다(390px에서 2px). 줄바꿈을 허용해
     자리가 없으면 캡션이 아래로 내려가게 한다 — 잘라 숨기지 않고 항상 읽히도록. */
  flex-wrap: wrap;
}
/* 한글은 글자 사이가 기본 줄바꿈 지점이라, 제목이 좁아지면 "지수"가 "지"/"수"로 쪼개진다.
   keep-all로 띄어쓰기에서만 끊고, 줄어들 쪽은 캡션(min-width:0)으로 몰아준다. */
.panel-t { color: var(--text-secondary); font-weight: 600; word-break: keep-all; }
.panel-r {
  color: var(--text-primary); font-variant-numeric: tabular-nums;
  min-width: 0; text-align: right; margin-left: auto;  /* 줄바꿈돼도 오른쪽 정렬 유지 */
  /* 둘 다 기본 flex-shrink:1이라 긴 캡션 옆에서 제목까지 같이 눌려 줄바꿈됐다.
     캡션이 먼저(=사실상 전부) 줄어들게 하되, 0이 아니라 큰 값이라 진짜 좁아지면
     제목도 마지막엔 줄어든다 → 가로 넘침은 안 생긴다. */
  flex-shrink: 999;
}
.empty { color: var(--muted); font-size: 13px; text-align: center; padding: 40px 0; }

.u-legend { font-size: 12px; color: var(--text-secondary); }
.u-legend .u-marker { border-radius: 50%; }

.table-view { margin-top: 14px; font-size: 13px; color: var(--text-secondary); overflow-x: auto; }
.table-view summary { cursor: pointer; }
.table-view table { border-collapse: collapse; margin-top: 8px; width: 100%; background: var(--surface-1); }
.table-view th, .table-view td {
  border-bottom: 1px solid var(--grid); padding: 5px 8px; text-align: right;
  font-variant-numeric: tabular-nums; color: var(--text-primary);
}
.table-view th { color: var(--text-secondary); font-weight: 600; }
.table-view th:first-child, .table-view td:first-child { text-align: left; }

.tab.link { text-decoration: none; }
.tile .sub-v { font-size: 11.5px; color: var(--text-secondary); margin-top: 1px; }

/* 교차검증 배지 — 화면에 "살짝" (본문 흐름을 방해하지 않는 크기) */
.xcheck { margin: 8px 0 18px; font-size: 11.5px; color: var(--muted); }
.xcheck.bad { color: var(--text-secondary); background: var(--warn-bg); border-radius: 4px; padding: 2px 6px; display: inline-block; }

/* 거래대금 상위 종목 */
.rank { margin-top: 14px; }
.rank-h {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 8px; font-size: 12px; margin: 12px 0 4px;
}
/* 200종목이라 세로도 스크롤한다. 헤더는 붙박이로 남겨 어느 열인지 잃지 않게. */
.rank-scroll { overflow: auto; max-height: 420px; }
.rank thead th { position: sticky; top: 0; z-index: 1; background: var(--surface-1); }
.rank table { border-collapse: collapse; width: 100%; background: var(--surface-1); font-size: 12.5px; }
.rank th, .rank td {
  border-bottom: 1px solid var(--grid); padding: 5px 8px; text-align: right;
  font-variant-numeric: tabular-nums; color: var(--text-primary); white-space: nowrap;
}
.rank th { color: var(--text-secondary); font-weight: 600; }
.rank td.rk { color: var(--muted); text-align: right; }
.rank td.nm, .rank th:nth-child(2) { text-align: left; white-space: normal; }

/* 랭킹표 아래 한 줄 설명 (소진율 vs 보유율처럼 헷갈리는 열이 있을 때) */
.rank-note { font-size: 11px; color: var(--muted); margin: 0 0 6px; line-height: 1.5; }
/* 한도가 없는 종목의 '–' 는 값이 아니라 해당 없음이라 톤을 낮춘다 */
.rank td.muted-cell { color: var(--muted); }
.rank td.lim { color: var(--text-secondary); }

.table-view td.nm, .table-view th:first-child { text-align: left; }
.table-view td.barcell { width: 34%; padding-left: 2px; }
.minibar { display: block; height: 8px; border-radius: 2px; background: var(--vol-fill); }

footer { margin-top: 18px; }
footer p { font-size: 11.5px; color: var(--muted); margin: 0; }

/* 테마 전환 버튼 — 화면 최우측 상단(헤더 안). 탭 줄에 두면 창 폭에 따라
   줄바꿈돼 애매한 위치로 밀려나므로 헤더 상태 블록 맨 위에 고정한다. */
.theme-btn {
  display: block; margin: 0 0 5px auto; padding: 4px 10px; font-size: 12px; line-height: 1.35;
}
/* 홈 · 테마 두 버튼을 헤더 최우측 상단 한 줄로. 세로로 쌓으면 상태 블록이 길어져
   헤더가 접히고, 탭 줄에 두면 폭에 따라 줄바꿈돼 위치가 흔들린다. */
.hdr-btns { display: flex; gap: 6px; justify-content: flex-end; margin: 0 0 5px; }
.hdr-btns .theme-btn { margin: 0; }
.home-btn { text-decoration: none; white-space: nowrap; }

/* 신용 · 미수 · 반대매매 화면 */
.dot-margin { background: var(--c-margin); }
.dot-dep { background: var(--c-dep); }
.dot-ddep { background: var(--c-ddep); }
.dot-rp { background: var(--c-rp); }
.dot-rcv { background: var(--c-rcv); }
.dot-liq { background: var(--c-liq); }
.dot-liqpct { background: var(--c-liqpct); }
.chart-gap { margin-top: 10px; }
.rank td.up { color: var(--pos); }
.rank td.dn { color: var(--neg); }
/* 날짜 칸은 좁은 화면에서도 줄바꿈 금지 (2023-\n10-24처럼 3줄로 깨졌던 것) */
.rank td.dt, .rank th:first-child { text-align: left; white-space: nowrap; }
/* 파생 화면 랭킹표는 2열이 종목명이라 좌측정렬 규칙이 있는데, 여기 2열은 숫자(비중)다 */
#spikes th:nth-child(2) { text-align: right; }

/* 차트 읽는 법 안내 (스택 발산형 차트) */
.note { font-size: 11.5px; color: var(--muted); margin: 6px 0 0; padding: 0 4px; line-height: 1.55; }
.note b { color: var(--text-secondary); font-weight: 600; }

/* ── 갱신 신호 (당일 뷰: 새 분봉이 실제로 들어온 순간에만 발동) ──
   차트 쪽 효과는 CSS가 아니라 app.js의 animateGrow() — 선의 꼬리를 다시 그려 "자라나는" 느낌.
   (파동 링은 라인 밖에서 번지는 느낌이라 제거) */
.tile.bump { animation: tile-bump 520ms ease-out; }
@keyframes tile-bump { 0% { transform: translateY(2px); } 55% { transform: translateY(-2px); } 100% { transform: none; } }
.tile .val.up   { color: var(--pos, #1d7a3f); }
/* `.down` 은 macro.js·app.js 의 값 변경 애니메이션이 붙이는 이름이고,
   `.dn` 은 `/us` 네 뷰의 타일이 쓰는 이름이다(`.rank td.dn` 과 같은 어법).
   `.dn` 규칙이 없어서 **상승만 초록이고 하락은 검정**이었다 — 등락색이 한쪽만
   칠해지면 "안 오른 것"과 "내린 것"이 화면에서 구분되지 않는다. 둘 다 건다. */
.tile .val.down,
.tile .val.dn   { color: var(--neg, #b3261e); }
.tile .val { transition: color 700ms ease-out; }
/* 갱신 순간 두 번 깜빡 — 시선이 안 가 있어도 "방금 바뀌었다"가 보이게 */
.updated.fresh {
  animation: badge-flash 1200ms ease-out;
  border-radius: 4px; padding: 1px 6px;
}
@keyframes badge-flash {
  0%   { background: var(--fresh-bg, rgba(29, 122, 63, 0.22)); color: var(--text-primary); opacity: 1; }
  22%  { background: var(--fresh-bg, rgba(29, 122, 63, 0.22)); opacity: 0.2; }
  44%  { background: var(--fresh-bg, rgba(29, 122, 63, 0.22)); color: var(--text-primary); opacity: 1; }
  66%  { opacity: 0.3; }
  100% { background: transparent; opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .tile.bump, .updated.fresh { animation: none; }
}

@media (max-width: 520px) {
  .tiles { grid-template-columns: repeat(2, 1fr); gap: 6px; }
  /* 프로그램 줄도 위 투자자별 줄과 같은 2열 리듬으로 접는다. */
  .tiles.t2 { grid-template-columns: repeat(2, 1fr); }
  .tile { padding: 8px 9px; }
  .tile .name { font-size: 10.5px; gap: 4px; }
  .tile .name .dot { width: 7px; height: 7px; flex: 0 0 auto; }
  .tile .val { font-size: 15px; }
  h1 { font-size: 17px; }
  /* 상태 블록을 좁혀 제목·부제가 쓸 폭을 돌려준다 (헤더가 한 줄로 붙으면서 부제 줄수가 늘어남) */
  .status { font-size: 11.5px; }
  .theme-btn { padding: 3px 8px; font-size: 11px; }
  /* 보유비중 표는 열이 8개라 폰(390px)에선 163px 넘쳤다. 표 안에서 또 가로로 스크롤하게
     두면 세로 스크롤을 삼켜서(이미 겪은 문제) 정보량이 낮은 두 열을 접는다.
       시장  — 종목명에서 대개 유추됨
       시총  — 행이 시총 내림차순이라 순위(#)로 서열은 남는다
     소진율·보유율·한도는 이 표의 본론이라 폰에서도 유지한다. */
  #topStocks th.mkt, #topStocks td.mkt,
  #topStocks th.cap, #topStocks td.cap { display: none; }
  #topStocks .wide-only { display: none; }
  /* 제목과 캡션이 한 줄에 붙으면 둘 다 글자 단위로 접혀 서로 파고든 것처럼 보인다.
     좁은 화면에선 캡션을 아랫줄로 내린다 (랭킹표가 있는 모든 화면 공통). */
  .rank-h { flex-wrap: wrap; }
  .rank-h .panel-r { flex-basis: 100%; }
  #topStocks th, #topStocks td { padding: 5px 6px; font-size: 12px; }
}

/* ── 커스텀 차트(/chart) ────────────────────────────────────────────────
   백지 차트 + 지표 선택 패널. 다른 화면은 조합이 고정이라 선택 UI가 없다. */
.picker { background: var(--surface-1); border: 1px solid var(--border); border-radius: 8px;
  padding: 10px 12px; margin-bottom: 12px; }

/* 선택 패널을 차트 왼쪽 레일로. minmax(0,1fr)이 없으면 그리드 아이템이 콘텐츠
   최소폭 밑으로 못 줄어들어 가로 스크롤이 생긴다(헤더에서 이미 겪은 그것). */
.chart-layout { display: grid; gap: 14px; align-items: start; }
.chart-main { min-width: 0; }
.chart-layout > .picker { margin-bottom: 0; }
.rail-toggle { display: none; border: 1px solid var(--border); background: var(--page);
  color: var(--text-secondary); border-radius: 6px; padding: 0 7px; font-size: 12px;
  line-height: 20px; cursor: pointer; }
.rail-toggle:hover { color: var(--text-primary); }

@media (min-width: 641px) {
  .chart-layout { grid-template-columns: 220px minmax(0, 1fr); }
  /* 레일을 붙여두면 차트를 스크롤하는 중에도 지표를 갈아끼울 수 있다.
     내부 스크롤은 overscroll-behavior:contain 이라 페이지 스크롤을 삼키지 않는다. */
  .chart-layout > .picker { position: sticky; top: 10px; max-height: calc(100vh - 20px);
    display: flex; flex-direction: column; }
  .chart-layout > .picker .rail-toggle { display: block; margin: 0 0 6px auto; }
  .chart-layout > .picker .pick-list { flex: 1 1 auto; min-height: 90px; max-height: none; }
  /* 접으면 얇은 띠 + 펼치기 버튼만 남는다 (차트 폭 확보) */
  .chart-layout.collapsed { grid-template-columns: auto minmax(0, 1fr); }
  .chart-layout.collapsed > .picker { padding: 6px; }
  .chart-layout.collapsed > .picker .picker-h,
  .chart-layout.collapsed > .picker .pick-list { display: none; }
  .chart-layout.collapsed > .picker .rail-toggle { margin: 0; }
}
.picker-h { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-bottom: 8px; }
.picker-h input[type="search"] {
  flex: 1 1 220px; min-width: 0; padding: 6px 10px; font-size: 13px;
  color: var(--text-primary); background: var(--page);
  border: 1px solid var(--border); border-radius: 6px;
}
.picker-h .hint { color: var(--muted); font-size: 11.5px; }
.pick-list { max-height: 260px; overflow: auto; overscroll-behavior: contain;
  border-top: 1px solid var(--border); padding-top: 8px; }
.pick-g { margin-bottom: 4px; }
.pick-g > summary { cursor: pointer; color: var(--text-secondary); font-size: 12.5px;
  font-weight: 600; padding: 3px 0; }
.pick-g > summary .cnt { color: var(--muted); font-weight: 400; margin-left: 5px; }
.pick-row { display: flex; align-items: center; gap: 7px; padding: 3px 0 3px 14px;
  font-size: 12.5px; color: var(--text-primary); }
.pick-row label { display: flex; align-items: center; gap: 7px; cursor: pointer; min-width: 0; }
/* 좁은 레일에서 '코스피 거래대금'은 공백에서만 접히고 '억원'은 세로로 쪼개지지 않게.
   (keep-all 없이 두면 글자 단위로 접혀 '거래대 / 금', '억 / 원'이 된다) */
.pick-row label > span { word-break: keep-all; }
.pick-row .span { color: var(--muted); font-size: 11px; white-space: nowrap; }
.pick-row .u { color: var(--text-secondary); font-size: 11px; white-space: nowrap;
  margin-left: auto; }
.pick-row input[disabled] + span { opacity: .5; }
.pick-stock { padding: 4px 0 4px 14px; font-size: 12.5px; }
.pick-stock .snm { color: var(--text-primary); font-weight: 600; margin-right: 6px; }
.pick-stock .smk { color: var(--muted); font-size: 11px; margin-right: 6px; }
.pick-stock .chip { padding: 2px 7px; font-size: 11px; margin-right: 4px; }

/* 선택된 계열 — 색점 + 이름을 항상 보이게 (light 슬롯 3개가 3:1 미달 → relief 규칙) */
.selbar { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; margin-bottom: 10px; }
.selchip { display: inline-flex; align-items: center; gap: 6px; padding: 3px 8px;
  font-size: 12px; background: var(--surface-1); color: var(--text-primary);
  border: 1px solid var(--border); border-radius: 999px; }
.selchip .dot { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto; }
.selchip button { border: 0; background: none; color: var(--muted); cursor: pointer;
  font-size: 13px; line-height: 1; padding: 0 0 0 2px; }
.selchip button:hover { color: var(--text-primary); }
.note-band { background: var(--warn-bg); color: var(--text-secondary); font-size: 12px;
  border-radius: 6px; padding: 6px 9px; margin-bottom: 10px; }
.note-band button { margin-left: 6px; }

@media (max-width: 520px) {
  .pick-list { max-height: 210px; }
  .picker { padding: 8px 9px; }
}

/* ── 매크로 메인 (`/`) — 카드 그리드 + 팝업 차트 ───────────────────────────────
   카드는 "값 + 신선도 + 흐름"을 한 덩어리로 보여주는 단위다. 신선도 배지는 장식이
   아니라 데이터의 일부다 — 같은 화면에 1분짜리(코스피)와 T+1(LBMA 금)이 섞여 있어서
   배지를 빼면 하루 묵은 숫자가 지금 값처럼 읽힌다. */
.mgroups { display: flex; flex-direction: column; gap: 14px; margin-bottom: 14px; }
.mg-t { font-size: 13px; font-weight: 700; color: var(--text-secondary);
  margin: 0 0 7px; letter-spacing: .01em; }
/* 열 수를 폭에 맡긴다 — 고정하면 반폭 브라우저(≈710px)에서 카드가 눌려 숫자가 잘린다 */
.mgrid { --mcard-min: 196px; display: grid; gap: 9px;
  grid-template-columns: repeat(auto-fit, minmax(var(--mcard-min), 1fr)); }
/* 4장짜리 그룹(증시·원자재)은 한 줄로 떨어지게 최소폭을 낮춘다.
   196px 이면 반폭(760px, 내용 728px)에서 4*196+3*9=811 > 728 이라 3+1 로 갈라진다.
   168px 이면 4*168+3*9=699 ≤ 728 → 한 줄. 390px 에서 이미 176px 카드가 안 잘리는 걸
   확인해서 이 폭은 안전하다. */
.mgrid.tight { --mcard-min: 168px; }
/* 카드가 하나뿐인 그룹(야간선물)은 auto-fit 이 한 트랙으로 접혀 화면 전체 폭을 먹는다.
   다른 그룹 카드와 크기가 달라 보이므로 한 장 폭으로 묶는다. */
.mgrid.solo { grid-template-columns: minmax(var(--mcard-min), 300px); }

/* ── 레일이 밀어낸 폭에서 «닫힘과 같은 배치» 유지 (macro.js `fitGrids`) ────────
   우측 이벤트 레일이 236px 를 밀면 반폭 맥북(855px)에서 본문이 823 → 587px 이 된다.
   그러면 4장짜리 그룹이 3+1 로, 3장짜리가 2+1 로 갈려 **외톨이 줄**이 생긴다
   (사용자 지적: "카드가 한줄에 3개씩 놓여서 보기가 별로").

   ## 왜 최소폭을 낮추는 것만으로는 안 되나 — 실측
   587px 에 4열을 넣으면 카드가 140px 이 되는데, 그 폭에서 카드가 **실제로 깨진다**
   (tools/probe_mcard_push.py: `mc-nm` 말줄임 + `.mc-foot` 가 카드를 넘침).
   잘리지 않는 CSS 카드 폭 하한은 **약 172px** 이다(zoom 스윕으로 좁혔다 —
   172px 은 잘리고 177px 은 안 잘린다).

   ## 그래서 «폭을 줄이는» 대신 «배율을 줄인다»
   `.mgroups` 에 zoom 을 걸면 레이아웃은 여전히 안전 폭 카드로 계산되고 **결과만**
   축소돼 화면에 들어간다. 열 수·줄바꿈·잘림이 닫힘 상태와 같아진다. 대가는
   글자가 그 배율만큼 작아지는 것뿐이다. 배율 하한(`FIT_FLOOR`)보다 더 줄여야
   하는 폭에서는 아예 손대지 않고 예전처럼 접는다 — 글자가 못 읽을 만큼 작아지는
   것보다 한 줄 더 접히는 게 낫다.

   ⚠️ 열 수를 auto-fit 에 맡기면 안 된다. 필요 폭과 실제 폭이 1px 안에서 만나면
      반올림에 따라 열이 하나 덜 잡힌다(zoom=0.84, 필요=실제=699px 에서 3열로
      떨어지는 걸 실측). 그래서 fit 중에는 JS 가 센 열 수를 그대로 박는다.

   ## 2026-08-29 — 하한이 «간발»이라 사용자 화면에서 2열로 떨어졌다
   FIT_FLOOR=0.78 이면 4열이 서는 마지막 뷰포트가 **844px** 이었다. 메모에 적힌
   반폭 855px 은 그 위지만 **여유가 11px 뿐**이라, 스크롤바가 보이거나 창이 딱
   반이 아니면 그대로 벼랑 아래로 떨어진다(4열 → 3열은 외톨이라 건너뛰고 **2열**).
   사용자가 본 게 이것이다. → FIT_FLOOR 만 0.78 → 0.60 으로 내렸다.

   ⚠️ FIT_SAFE 를 내리는 안(178→166, `.mc-top` 을 wrap 시켜 173.3→163.3px 확보)은
      **실측하고 기각**했다. 화면에 실제로 그려지는 카드 폭은 `avail/열수` 라
      FIT_SAFE 와 무관하다 — FIT_SAFE 를 내리면 **같은 렌더 폭에서 내용 공간만
      좁아지고 배율이 올라간다**. 855px 실측(둘 다 141px 카드):
        FIT_SAFE 178 → S&P500 카드 121px, 배지 같은 줄, 환율 값줄 31px
        FIT_SAFE 166 → 카드 142px, 배지 아래 줄, 환율 값줄 46px(단위가 세로로 쌓임)
      글자가 조금 커지는 대신 카드가 17% 높아지고 «원/달러» 가 세로로 쪼개진다.
      이미 잘 되던 폭(855)을 나쁘게 만드는 맞바꿈이라 안 했다. FIT_SAFE 를 그대로
      두면 **모든 폭에서 줄바꿈·잘림이 완전히 동일**하고 배율만 달라진다.
   ⇒ 4열이 서는 마지막 뷰포트 844 → **727px**. 그런데 731px 아래로는 레일을
     닫아도 어차피 3열이므로(본문 699 < 4*168+27), 결과적으로 **레일을 열든 닫든
     배치가 같아지는 구간에 빈틈이 없다**(640~1512 실측). */
.mgroups.fit { zoom: var(--mgrid-zoom, 1); }
.mgroups.fit .mgrid { grid-template-columns: repeat(var(--mc-cols, 1), minmax(0, 1fr)); }
/* solo 는 한 장짜리라 열 수 개념이 없다 — 원래 규칙을 그대로 둔다 */
.mgroups.fit .mgrid.solo { grid-template-columns: minmax(var(--mcard-min), 300px); }

.mcard {
  background: var(--surface-1); border: 1px solid var(--border); border-radius: 10px;
  padding: 10px 12px 8px; cursor: pointer; min-width: 0;
}
.mcard:hover { border-color: var(--baseline); }
.mcard:focus-visible { outline: 2px solid var(--series-1); outline-offset: 1px; }
.mc-top { display: flex; align-items: center; gap: 6px; }
.mc-nm { font-size: 12.5px; color: var(--text-secondary); font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.mc-val { display: flex; align-items: baseline; gap: 4px; margin-top: 2px; }
.mc-num { font-size: 21px; font-weight: 700; font-variant-numeric: tabular-nums;
  color: var(--text-primary); }
.mc-num.up { color: var(--pos); }
.mc-num.down { color: var(--neg); }
.mc-unit { font-size: 11px; color: var(--muted); }
.mc-chg { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums;
  min-height: 18px; display: flex; align-items: center; gap: 5px; }
.mc-chg.up, .up { color: var(--pos); }
.mc-chg.down, .down { color: var(--neg); }
/* 절대변동과 등락률이 같은 서식이면 두 숫자가 구분되지 않는다(사용자 지적).
   → 변동은 ▲/▼ + 숫자, 등락률은 색 채운 알약으로 형태 자체를 다르게 한다. */
.mc-ar { font-size: 9px; line-height: 1; }
.mc-pill { font-size: 11px; font-weight: 700; padding: 1.5px 6px; border-radius: 9px;
  background: var(--surface-2, rgba(128,128,128,.12)); color: var(--text-secondary); }
.mc-chg.up .mc-pill { background: var(--pos-bg); color: var(--pos); }
.mc-chg.down .mc-pill { background: var(--neg-bg); color: var(--neg); }
.mc-spark { display: block; width: 100%; height: 44px; margin: 4px 0 2px; }
.mc-subs { display: flex; flex-wrap: wrap; gap: 4px 10px; font-size: 11.5px;
  color: var(--text-secondary); font-variant-numeric: tabular-nums; }
.mc-s { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; }
/* 색점은 JS 가 카드 팔레트로 칠한다 (CSP 때문에 인라인 style 속성은 못 쓰지만 CSSOM
   조작은 허용된다). 계열 구분이 색상만으로 되니 파선용 선 조각(.seg)은 더 필요 없다. */
.mc-s i { width: 7px; height: 7px; border-radius: 50%; display: inline-block; flex: 0 0 auto; }
.mc-foot { font-size: 11.5px; color: var(--muted); margin-top: 3px;
  font-variant-numeric: tabular-nums; word-break: keep-all; }
/* 각주 조각은 한 줄씩 — 원화 환산과 확정치가 한 줄에 뭉치면 구분이 안 된다(금 국제가) */
.mc-fl { display: block; }
.mc-d { opacity: .75; }

/* 신선도 배지 — 색이 아니라 **글자**로도 구분되게 (색맹 안전). 배지는 오른쪽 끝 고정. */
.mc-badge { margin-left: auto; font-size: 10.5px; line-height: 1.5; padding: 1px 6px;
  border-radius: 999px; white-space: nowrap; flex: 0 0 auto;
  border: 1px solid var(--border); color: var(--text-secondary); }
.mc-badge.live { color: var(--pos); border-color: var(--pos); }
.mc-badge.lag  { color: var(--text-secondary); }
.mc-badge.day  { color: var(--muted); }
.mc-badge.low  { color: var(--c-rp); border-color: var(--c-rp); }
/* 갱신 플래시는 **이번 틱의 방향**을 따라간다. 예전엔 --fresh-bg(초록) 하나뿐이라
   하락 틱에도 초록이 번쩍여서, 숫자(빨강)와 배지(초록)가 서로 어긋났다(사용자 지적).
   틴트는 등락 알약과 같은 --pos-bg/--neg-bg 를 재사용 — 새 색을 만들지 않는다. */
.mc-badge.flash { --fl: var(--fresh-bg); animation: badgeflash 1.3s ease-out; }
.mc-badge.flash.fl-up { --fl: var(--pos-bg); }
.mc-badge.flash.fl-dn { --fl: var(--neg-bg); }
@keyframes badgeflash {
  0%   { background: var(--fl); }
  30%  { background: transparent; }
  60%  { background: var(--fl); }
  100% { background: transparent; }
}
/* 숫자는 **글자색만** 점멸시킨다. 예전엔 배경 틴트를 입혔는데 숫자 뒤에 색 블록이
   생겨 거슬렸다(사용자 지적). 색만 깜빡이면 배경도 레이아웃도 건드리지 않는다.
   100% 를 방향색으로 끝내는 이유 — 애니메이션이 끝나도 .up/.down 이 700ms 더 남아
   있어서, 여기서 기본색으로 끝내면 그 순간 방향색으로 되튀는 깜빡임이 생긴다. */
.mc-num.up { --fl: var(--pos); }
.mc-num.down { --fl: var(--neg); }
.mc-num.up, .mc-num.down { animation: numflash 1.1s ease-out; }
@keyframes numflash {
  0%   { color: var(--text-primary); }
  30%  { color: var(--fl); }
  60%  { color: var(--text-primary); }
  100% { color: var(--fl); }
}
.note .mc-badge { margin-left: 0; }

/* 팝업 차트 */
body.modal-open { overflow: hidden; }
/* ⚠️ `.hidden{display:none}` 은 파일 앞쪽에 있어 같은 특이도끼리는 뒤에 오는 이 규칙이
   이긴다 → 닫힌 팝업이 카드 위로 비쳐 보였다(실측). 명시적으로 덮는다. */
.modal-bd { position: fixed; inset: 0; z-index: 50; background: rgba(0, 0, 0, .55);
  display: flex; align-items: center; justify-content: center; padding: 16px; }
.modal-bd.hidden { display: none; }
.modal { background: var(--page); border: 1px solid var(--border); border-radius: 12px;
  width: min(860px, 100%); max-height: 92vh; overflow: auto; padding: 14px 16px 16px; }
.modal-h { display: flex; align-items: flex-start; gap: 10px; margin-bottom: 10px; }
.modal-h h2 { font-size: 17px; margin: 0; color: var(--text-primary); }
.modal-h .sub { margin: 2px 0 0; font-size: 12px; color: var(--muted); }
.modal-h .tab { margin-left: auto; padding: 4px 11px; flex: 0 0 auto; }
.modal-chart { background: var(--surface-1); border: 1px solid var(--border);
  border-radius: 10px; padding: 10px 8px 4px; min-height: 200px; }
.modal-note { margin-top: 10px; }
#modalToChart { display: inline-block; margin-top: 10px; text-decoration: none; }

@media (prefers-reduced-motion: reduce) {
  .mc-badge.flash, .mc-num.up, .mc-num.down { animation: none; }
}
@media (max-width: 520px) {
  /* 폰에서는 tight 도 같은 최소폭 — .mgrid.tight 가 특이도상 위라 여기서 같이 눌러야 한다 */
  .mgrid, .mgrid.tight { --mcard-min: 150px; gap: 7px; }
  .mcard { padding: 8px 10px 7px; }
  .mc-num { font-size: 18px; }
  .modal { padding: 12px 10px 14px; max-height: 96vh; }
  .modal-chart { padding: 8px 4px 4px; }
}
/* 대표 숫자가 어느 계열인지 (미국 국채 카드의 큰 숫자는 10년물, 금리차는 미국 10−2). */
.mc-hd { font-size: 11px; color: var(--muted); margin-left: 2px; }

/* ── 섹터 발산 막대 (/us 실시간 뷰) ───────────────────────────────────────
   가로 발산 막대 11칸. uPlot 을 쓰지 않는 이유는 us_rt.js 머리말에 적어 뒀다 —
   좁은 폭에서 축·눈금이 오히려 방해가 되고 라벨이 먼저 무너진다.
   구조는 「반쪽 두 개(각 50%) + 그 안의 실제 막대」다. 반쪽에 직접 폭을 주면
   flex-basis 가 width 를 이겨서 JS 대입이 조용히 먹지 않는다. */
.secbars { padding: 4px 2px 2px; }
.secrow { display: flex; align-items: center; gap: 8px; padding: 3px 0;
  font-size: 12.5px; }
.secnm { flex: 0 0 106px; color: var(--text-primary); font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.secnm small { color: var(--muted); font-weight: 400; margin-left: 4px; }
.secbar { flex: 1 1 auto; display: flex; align-items: center; min-width: 60px;
  position: relative; }
/* 0선 — 가운데 세로 눈금 */
.secbar::before { content: ""; position: absolute; left: 50%; top: -2px; bottom: -2px;
  width: 1px; background: var(--baseline); }
.secbar .half { flex: 0 0 50%; display: flex; min-width: 0; }
.secbar .half.l { justify-content: flex-end; }
.secbar i { display: block; height: 11px; border-radius: 2px; width: 0; }
.secval { flex: 0 0 76px; text-align: right; font-variant-numeric: tabular-nums;
  font-weight: 700; white-space: nowrap; }
.secval.up { color: var(--pos); }
.secval.dn { color: var(--neg); }
.secraw { flex: 0 0 62px; text-align: right; color: var(--text-secondary);
  font-variant-numeric: tabular-nums; font-size: 11.5px; }
@media (max-width: 520px) {
  .secnm { flex-basis: 78px; font-size: 12px; }
  .secnm small { display: none; }
  /* ▲/▼ 와 숫자가 한 줄에 있어야 한다 — 62px 로 좁히면 줄바꿈돼 두 줄이 된다(실측) */
  .secval { flex-basis: 72px; font-size: 12px; }
  .secraw { display: none; }
}
/* 행이 툴팁의 앵커이고 tabindex 를 가진다 — 얹을 수 있다는 걸 눈에 보이게 한다. */
.secrow { cursor: help; border-radius: 4px; }
.secrow:hover, .secrow:focus-visible {
  background: var(--day-band); outline: none; }
.secrow:focus-visible { box-shadow: inset 0 0 0 1px var(--baseline); }

/* ── 보유종목 툴팁 (/us 실시간 뷰) ───────────────────────────────────────
   섹터 ETF 안에 무슨 회사가 들었는지. 브라우저 기본 `title` 을 쓰지 않는 이유는
   us_rt.js 머리말에 있다(표 정렬 불가·지연·터치에서 안 나옴).

   `position: fixed` 라 좌표는 뷰포트 기준이고, 위치는 JS 가 CSSOM 으로 넣는다
   (CSP `style-src 'self'` 가 인라인 style 속성을 막는다). 폭은 여기서만 정한다 —
   JS 가 폭까지 계산하면 390px 에서 두 규칙이 싸운다. */
.hold-tip {
  position: fixed; z-index: 60; left: 0; top: 0;
  width: max-content; max-width: min(360px, calc(100vw - 16px));
  background: var(--surface-1); color: var(--text-primary);
  border: 1px solid var(--border); border-radius: 8px;
  box-shadow: 0 6px 20px rgba(11, 11, 11, 0.18);
  padding: 8px 10px 7px; font-size: 12px; line-height: 1.45;
  /* 툴팁 자신이 포인터를 먹으면 그 아래 행에서 pointerleave 가 떠서 깜빡인다. */
  pointer-events: none;
}
.hold-tip[hidden] { display: none; }
.ht-h { display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap;
  padding-bottom: 5px; margin-bottom: 4px; border-bottom: 1px solid var(--border); }
.ht-h small { color: var(--muted); font-weight: 400; }
.ht-as { margin-left: auto; color: var(--muted); font-size: 11px; white-space: nowrap; }
.ht-tb { border-collapse: collapse; width: 100%; }
.ht-tb td { padding: 1px 0; vertical-align: baseline; }
.ht-i { color: var(--muted); text-align: right; width: 1.6em; padding-right: 6px !important;
  font-size: 11px; }
.ht-t { font-weight: 700; padding-right: 7px !important; white-space: nowrap; }
.ht-n { color: var(--text-secondary); font-size: 11.5px;
  /* 종목명이 길어도 툴팁 폭을 밀어내지 않게 잘라낸다 (`ADVANCED MICRO DEVICES`). */
  max-width: 168px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ht-w { text-align: right; font-variant-numeric: tabular-nums; font-weight: 600;
  padding-left: 8px !important; white-space: nowrap; }
.ht-f { margin-top: 5px; padding-top: 4px; border-top: 1px solid var(--border);
  color: var(--text-secondary); font-size: 11px; }
.ht-none { margin: 2px 0 0; color: var(--text-secondary); }
@media (max-width: 520px) {
  .hold-tip { font-size: 11.5px; padding: 7px 8px 6px; }
  .ht-n { max-width: 116px; }
}

/* ── 경제 이벤트 캘린더 (/calendar) ──────────────────────────────────────
   좌 달력 + 우 목록.

   **목록 폭을 320px 로 못 박고, 남는 폭은 달력에 준다.** 처음엔 반대였다 —
   달력을 clamp 로 고정하고 목록에 `1fr` 을 줬는데, 1180px 페이지에서 목록이
   800px 까지 벌어져 한 줄에 여백만 남았다. 참고한 saveticker 는 목록이 ~428px 인데,
   그보다도 좁게 간다(400 → 320). 목록 행은 이미 620px 미만에서 ★·수치가 이름
   아래 줄로 내려간 3칸 배치라, 400 → 320 은 **행 구조를 바꾸지 않고** 남는 여백만
   달력으로 넘긴다. 반폭(760)에서 달력이 314 → 394px 로 25% 넓어진다.

   목록을 좁혀도 되는 근거는 **지표명이 한국어**라는 것이다. 영문일 때는 470px
   에서 "S&P Global Manufacturing PMI" 가 3줄로 접혀서 넓혀야 했지만,
   "S&P글로벌 제조업 PMI" 는 400px 에서 한 줄이다. 400px 는 아래 컨테이너
   쿼리(620px) 아래라 ★·수치가 이름 밑줄로 내려간다 — saveticker 카드와 같은 꼴.

   그리고 목록이 400px 로 고정되니 **반폭(≈760px)에서도 좌우 배치가 들어간다**
   (728px 콘텐츠 = 달력 314 + 여백 14 + 목록 400). 그래서 쌓기 기준을 1080 →
   720px 로 내렸다. 사용자는 맥북 에어를 반폭으로 쓰므로 이 폭이 기본 경험이다. */
.cal-wrap { display: grid; gap: 14px; align-items: start; }
@media (min-width: 720px) {
  .cal-wrap { grid-template-columns: minmax(0, 1fr) minmax(0, 320px); }
  /* 스크롤을 목록에만 주고 달력은 붙여둔다 — 아래로 내려가도 날짜를 계속 고를 수 있다. */
  .cal-side { position: sticky; top: 10px; }
  /* 목록에 **자기 스크롤**을 준다. 예전엔 페이지 전체가 스크롤됐는데, 부팅 때
     오늘 날짜로 scrollIntoView 를 걸어서 열자마자 헤더·메뉴·필터 칩이 통째로
     위로 밀려 올라갔다(상단에 닿으려면 목록을 끝까지 되감아야 했다).
     목록만 움직이면 상단은 제자리에 남는다 — /chart 좌측 레일과 같은 어법. */
  .cal-main { position: sticky; top: 10px; }
}
.cal-side { min-width: 0; }
/* 행 배치는 **뷰포트가 아니라 목록 자신의 폭**으로 결정해야 한다. 2단일 때 목록은
   뷰포트가 아무리 넓어도 좁으므로, 뷰포트 미디어쿼리로는 이 상황을 표현할 수 없다
   (실측: 1280px 에서 목록이 477px 이라 지표명이 3줄로 접히고 "Opening s" 처럼
   단어 중간이 끊겼다). 컨테이너 쿼리가 이걸 정확히 표현한다. */
.cal-main { min-width: 0; container-type: inline-size; }

.cal-mon { display: flex; align-items: center; gap: 6px; margin-bottom: 8px; }
.cal-mon .mlab { font-weight: 700; font-size: 15px; font-variant-numeric: tabular-nums; }
.cal-mon .sp { margin-left: auto; }
.cal-nav {
  font: inherit; cursor: pointer; background: var(--surface-1);
  color: var(--text-primary); border: 1px solid var(--border);
  border-radius: 6px; padding: 3px 8px; line-height: 1.3;
}
.cal-nav:hover:not(:disabled) { background: var(--day-band); }
.cal-nav:disabled { opacity: .4; cursor: default; }

.cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 3px; }
.cal-dow {
  text-align: center; font-size: 11px; color: var(--muted); padding-bottom: 2px;
}
.cal-dow.sun { color: var(--neg); }
.cal-dow.sat { color: var(--series-1); }
.cal-day {
  position: relative; font: inherit; cursor: pointer; text-align: left;
  background: var(--surface-1); color: var(--text-primary);
  border: 1px solid var(--border); border-radius: 6px;
  padding: 4px 4px 3px; min-height: 44px;
  display: flex; flex-direction: column; gap: 2px;
}
.cal-day.pad { visibility: hidden; }
.cal-day:hover { background: var(--day-band); }
.cal-day .dn { font-size: 12px; font-variant-numeric: tabular-nums; line-height: 1; }
.cal-day.sun .dn { color: var(--neg); }
.cal-day.sat .dn { color: var(--series-1); }
/* 오늘 = 테두리, 선택 = 채움. 둘이 같은 날일 수 있어 서로 다른 채널을 쓴다. */
.cal-day.today { border-color: var(--text-primary); box-shadow: inset 0 0 0 1px var(--text-primary); }
.cal-day.sel { background: var(--warn-bg); border-color: var(--baseline); }
.cal-day .cd { display: flex; gap: 2px; flex-wrap: wrap; align-items: center; }
.cal-day .cd i {
  display: block; width: 5px; height: 5px; border-radius: 50%;
  background: var(--vol-fill);
}
.cal-day .cd i.hi { background: var(--series-8); }
.cal-day .cd em {
  font-style: normal; font-size: 10px; color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.cal-legend { margin-top: 8px; font-size: 11.5px; color: var(--text-secondary); }
.cal-legend i {
  display: inline-block; width: 5px; height: 5px; border-radius: 50%;
  background: var(--vol-fill); vertical-align: 1px; margin-right: 3px;
}
.cal-legend i.hi { background: var(--series-8); }

/* 목록 — 날짜 머리말이 스크롤 중 붙어 있어야 "지금 어느 날을 보는지"를 잃지 않는다. */
/* 목록에 **자기 스크롤**을 준다(폭과 무관하게). 예전엔 페이지 전체가 스크롤됐는데,
   부팅 때 오늘 날짜로 이동하느라 헤더·메뉴·필터 칩이 통째로 위로 밀려 올라갔다 —
   상단에 닿으려면 목록을 끝까지 되감아야 했다. 목록만 안에서 움직이면 위쪽은 제자리.
   overflow-x:hidden 이 예전 `overflow:hidden` 의 역할(둥근 모서리 클리핑)을 잇는다. */
.cal-list { background: var(--surface-1); border: 1px solid var(--border);
  border-radius: 10px; overflow-x: hidden;
  max-height: calc(100vh - 20px); overflow-y: auto; overscroll-behavior: contain; }
.cal-dh {
  position: sticky; top: 0; z-index: 2;
  display: flex; align-items: baseline; gap: 8px;
  padding: 6px 10px; font-size: 12.5px; font-weight: 700;
  background: var(--page); border-bottom: 1px solid var(--border);
  border-top: 1px solid var(--border);
}
.cal-dh:first-child { border-top: 0; }
.cal-dh small { font-weight: 400; color: var(--muted); }
.cal-dh .rel { margin-left: auto; font-weight: 400; color: var(--muted); font-size: 11.5px; }
.cal-dh.is-today { color: var(--series-1); }

.cal-ev {
  display: grid; grid-template-columns: 72px 34px 40px minmax(0, 1fr) auto;
  gap: 6px; align-items: baseline;
  padding: 5px 10px; border-bottom: 1px solid var(--grid);
  cursor: pointer; font-size: 13px;
}
.cal-ev:last-child { border-bottom: 0; }
.cal-ev:hover { background: var(--day-band); }
.cal-ev.past { color: var(--text-secondary); }
.cal-t { font-variant-numeric: tabular-nums; color: var(--text-secondary); font-size: 12px; }
.cal-c { font-size: 12px; white-space: nowrap; }
.cal-imp { font-size: 11px; letter-spacing: -1px; color: var(--series-4); white-space: nowrap; }
.cal-imp.i3 { color: var(--series-8); }
.cal-imp.i1 { color: var(--muted); }
/* 자체 분류(★를 우리가 매긴 것)는 점선 밑줄로 구분한다 — 소스 등급과 같아 보이면 안 된다. */
.cal-imp.est { text-decoration: underline dotted; text-underline-offset: 2px; }
/* `anywhere` 는 단어 중간을 끊는다 ("JOLTS Job Opening / s"). 한 단어가 칸보다
   길 때만 끊게 `break-word` 를 쓴다. */
.cal-nm { min-width: 0; overflow-wrap: break-word; }
.cal-nm b { font-weight: 600; }
.cal-nm .per { color: var(--muted); font-size: 11.5px; margin-left: 4px; }
/* 같은 이름이 두 번 나오는 이유를 적은 꼬리말. 이름보다 약하게 둔다. */
.cal-nm .per.dup { font-size: 11px; }
.cal-vals {
  display: flex; gap: 8px; align-items: baseline; justify-self: end;
  font-variant-numeric: tabular-nums; white-space: nowrap; font-size: 12px;
}
.cal-vals span { color: var(--muted); }
.cal-vals b { font-weight: 700; color: var(--text-primary); }
/* ▲▼ 는 "예상보다 높음/낮음"이고 좋다/나쁘다가 아니다 → 초록·빨강을 쓰지 않는다.
   이 대시보드에서 초록·빨강은 **가격 방향** 전용이다. */
.cal-vals .sp { color: var(--text-primary); font-weight: 700; }
.cal-descr {
  padding: 2px 10px 9px; font-size: 12px; line-height: 1.55;
  color: var(--text-secondary); background: var(--day-band);
  border-bottom: 1px solid var(--grid);
}
.cal-descr .src { display: block; margin-top: 4px; color: var(--muted); font-size: 11px; }
/* 영문 원문 — 한국어 이름이 우리 사전이라 대조할 원본이 화면에 있어야 한다.
   줄바꿈 없이 넘치면 320px 목록을 가로로 밀어버리므로 반드시 접히게 둔다. */
.cal-descr .src.en { overflow-wrap: break-word; }
.cal-empty { padding: 26px 10px; text-align: center; color: var(--muted); font-size: 13px; }
.cal-more { padding: 9px 10px; text-align: center; }

/* 목록이 620px 미만이면 ★와 수치를 이름 아래 줄로 내린다 — 5칸을 유지하면
   지표명이 3~5줄로 접힌다. 2단 배치(넓은 뷰포트·좁은 목록)와 좁은 화면이
   같은 규칙 하나로 처리된다. */
@container (max-width: 620px) {
  /* 🔑 시각 칸 46 → 72px. 실적 줄이 세션 경계를 병기하면서(`마감후 익일 05:00~`)
     46px 에서 **3줄로 접혔다** — 실측 .cal-t 높이 48px vs 지표 줄 31px. 늘린 26px 은
     3번째 칸(★)에서 온다. 320px 목록에서 그 칸은 208px 을 받는데 ★★★ 는 25px 이라
     남는 자리였다(실측). 지표 줄의 `21:30` 은 그대로 한 줄이다. */
  .cal-ev { grid-template-columns: 72px 34px minmax(0, 1fr); row-gap: 2px; }
  .cal-imp { grid-column: 3; justify-self: start; }
  .cal-nm { grid-column: 1 / -1; }
  .cal-vals { grid-column: 1 / -1; justify-self: start; font-size: 11.5px; }
}
@media (max-width: 560px) {
  .cal-ev { font-size: 12.5px; }
  .cal-day { min-height: 38px; }
}
