/* ==========================================================================
   Performance Report Dashboard — 실사이트 프런트
   색상은 원래 팔레트(다크 기본 = 블랙 + 인디고 시그널 / 라이트 = 웜그레이 +
   인디고)를 그대로 씁니다. 요소 구성·타이포·간격은 dub.co 언어를 따릅니다 —
   얇은 1px 테두리 + 옅은 그림자, 라운드 8px(컨트롤)/12px(카드), 굵기 400·500·600
   중심, 장식 요소 없음, 표는 zebra 대신 행 구분선, 차트는 점선 가로 가이드 +
   Y축 축약 라벨. 다크/라이트 토글은 <html data-theme="dark|light">로 전환되며
   값은 설정 화면에서 저장됩니다. 자세한 내용은 README.md / FEATURE_SPEC.md.
   ========================================================================== */

@import url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.css');

/* Geist 는 **가변 폰트로** 받습니다. Google Fonts 에도 있지만 정적 인스턴스만
   주고(실측: wght@400;450;500 요청에 450 face 0개), new_D 의 서명 굵기인 450
   ("heavier than a light but lighter than a semibold")을 쓸 수 없습니다.
   덤으로 예전 faux-bold 문제도 사라집니다 — Inter 를 400/500/600 만 불러다 쓰면서
   font-weight: 500 규칙 13개가 브라우저 가짜 굵게로 그려지고 있었습니다. */
@font-face {
  font-family: 'Geist Sans';
  src: url('https://cdn.jsdelivr.net/npm/geist@1.3.1/dist/fonts/geist-sans/Geist-Variable.woff2') format('woff2-variations');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Geist Mono';
  src: url('https://cdn.jsdelivr.net/npm/geist@1.3.1/dist/fonts/geist-mono/GeistMono-Variable.woff2') format('woff2-variations');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

/* hidden 속성이 항상 이기게 합니다.
   `display: flex|grid` 를 준 요소에 hidden 을 걸면 브라우저 기본
   `[hidden] { display: none }` 이 작성자 규칙에 밀려 그대로 보입니다.
   JS 가 el.hidden = true 로 감춘 영역(연동할 거래처, 새 거래처 만들기 등)이
   화면에 남던 원인이라 전역으로 한 번 못 박습니다. */
[hidden] { display: none !important; }

/* ---------- 테마와 무관한 공통 토큰 ---------- */
:root {
  /* new_D 규칙: 카드·버튼·테두리 컨테이너 전부 6px. "The sharpness is the point" —
     6px 보다 큰 모서리는 쓰지 않습니다. 알약(9999px)은 내비 액션·배지에만. */
  --radius-control: 6px;   /* 버튼·입력칸 */
  --radius-card: 6px;      /* 카드·패널 — 예전 12px */
  --radius-pill: 999px;    /* 배지·점·바 트랙 전용 — 버튼에는 쓰지 않습니다 */

  /* Geist 를 1순위에 두면 글리프 단위 폴백으로 라틴·숫자는 Geist, 한글은
     Pretendard 가 렌더됩니다. 순서를 뒤집으면 숫자까지 Pretendard 가 먹습니다.
     Geist 는 라틴 전용이라(한글 자소 없음) 한글 폰트 짝이 반드시 필요합니다. */
  --font-body: 'Geist Sans', 'Pretendard Variable', 'Pretendard', -apple-system, 'Apple SD Gothic Neo', 'Malgun Gothic', sans-serif;
  --font-display: 'Geist Sans', 'Pretendard Variable', 'Pretendard', -apple-system, 'Apple SD Gothic Neo', 'Malgun Gothic', sans-serif;
  /* Pretendard 를 2순위로 끼운 이유 — 모노 셀 안에 한글이 섞일 때(예: 예산 유형
     주석) CJK 고정폭으로 튀는 것을 막습니다. */
  --font-mono: 'Geist Mono', 'Pretendard Variable', ui-monospace, 'SFMono-Regular', Menlo, monospace;

  --sidebar-width: 240px;
  --page-max-width: 1220px;
}

/* ---------- 다크 테마 (기본) — 색상은 9/4 이전 원래 팔레트 ----------
   new_D(Vercel) 로 무채색화했다가 되돌렸습니다. 색만 원복이고 타이포·모서리는
   new_D 그대로입니다(Geist 450, 6px) — 그 둘은 색이 아닙니다.
   ------------------------------------------------------------------ */
:root,
:root[data-theme="dark"] {
  color-scheme: dark;

  /* Surfaces — 블랙 메인 */
  --bg-base: #0b0b0d;
  --bg-surface: #141416;
  --bg-surface-alt: #1b1b1e;
  --bg-inset: #0f0f11;

  /* Borders */
  --hairline: rgba(255, 255, 255, 0.08);
  --border-strong: rgba(255, 255, 255, 0.14);

  /* Text */
  --text-primary: #f3f3f2;
  --text-secondary: #a6a6ac;
  --text-muted: #6e6e76;
  --text-on-accent: #0b0b0d;  /* --text-primary(밝은색) 채움 위 글자 — .brand-mark */
  --text-on-signal: #ffffff;  /* --signal-solid(인디고) 채움 위 글자 — 아바타·선택 날짜 */

  /* Brand signal (인디고 — 다크 배경용으로 밝기 보정) */
  --signal: #8b8cf6;
  --signal-solid: #4f46e5;
  --signal-solid-hover: #6058ee;
  --signal-tint: rgba(139, 140, 246, 0.14);

  /* Status */
  --positive: #3ddc84;
  --positive-tint: rgba(61, 220, 132, 0.14);
  --negative: #f87171;
  --negative-tint: rgba(248, 113, 113, 0.14);
  --warning: #f2b84b;
  --warning-tint: rgba(242, 184, 75, 0.14);

  /* 매체(플랫폼) 아이덴티티 칩 — 실제 브랜드 컬러를 그대로 쓰지 않고
     배경 가독성 기준으로 새로 정의 */
  --platform-google: #5b9bd8;
  --platform-meta: #b08cf7;
  --platform-naver: #4ade80;
  --platform-kakao: #f2b84b;

  /* Chart — axis 는 점선 가로 가이드, guide 는 hover 크로스헤어입니다
     (이름은 JS 가 읽으므로 고정). */
  --chart-bar: #8b8cf6;
  --chart-line: #4ade80;
  --chart-alt: #5b9bd8;             /* KPI 3번째 색점 */
  --chart-axis: rgba(255, 255, 255, 0.10);
  --chart-guide: rgba(255, 255, 255, 0.35);
  --chart-dot-stroke: #141416;      /* 차트가 얹히는 --bg-surface 와 같은 값 */
  --chart-area-opacity: 0.26;

  /* 그림자·링 — 색이 아니라 깊이 언어라 현행 유지 */
  --shadow-card: 0 1px 3px 0 rgba(0, 0, 0, 0.5);
  --shadow-ring: 0 0 0 1px rgba(255, 255, 255, 0.1);
  --shadow-popover: 0 10px 15px -3px rgba(0, 0, 0, 0.5), 0 4px 6px -4px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.1);
  --shadow-btn-inset: inset 0 1px 0 0 rgba(0, 0, 0, 0.1);
  --focus-ring: rgba(255, 255, 255, 0.14);

  /* 툴팁·모달 — 글라스 블러를 걷어냈으므로 불투명 표면색 */
  --glass-bg: #141416;
  --glass-border: rgba(255, 255, 255, 0.16);
  --glass-highlight: transparent;
  --overlay-bg: rgba(6, 6, 8, 0.62);

  --btn-primary-hover: #d8d8d6;

  /* 아래 4개는 어디에도 정의된 적이 없어 8곳이 회색 폴백 rgba(127,127,127,…)
     으로 칠해지고 있었습니다. 팔레트 안에 두되 값은 위 색들에 맞춥니다
     (= --bg-surface-alt / 한 단계 위 / --border-strong / --signal-solid). */
  --surface-2: #1b1b1e;
  --bg-elevated: #26262a;
  --border: rgba(255, 255, 255, 0.14);
  --accent: #4f46e5;
}

/* ---------- 라이트 테마 — 색상은 9/4 이전 원래 팔레트 ---------- */
:root[data-theme="light"] {
  color-scheme: light;

  --bg-base: #f6f6f4;
  --bg-surface: #ffffff;
  --bg-surface-alt: #f0f0ed;
  --bg-inset: #f6f6f4;

  --hairline: rgba(20, 20, 15, 0.09);
  --border-strong: rgba(20, 20, 15, 0.18);

  --text-primary: #14140f;
  --text-secondary: #52514a;
  --text-muted: #8a887d;
  --text-on-accent: #ffffff;
  --text-on-signal: #ffffff;

  --signal: #4f46e5;
  --signal-solid: #4f46e5;
  --signal-solid-hover: #3730a3;
  --signal-tint: rgba(79, 70, 229, 0.10);

  --positive: #16743f;
  --positive-tint: rgba(22, 116, 63, 0.10);
  --negative: #b3261e;
  --negative-tint: rgba(179, 38, 30, 0.09);
  --warning: #9a6414;
  --warning-tint: rgba(154, 100, 20, 0.10);

  /* 아래 4개는 원래 어디에도 정의돼 있지 않아 회색 폴백이 칠해지던 토큰입니다.
     다크 블록에서 정의하는 순간 선택자(`:root,`) 때문에 라이트까지 어두워져서,
     여기서도 라이트 값을 명시합니다. 예전 회색 폴백의 모습을 재현하는 값입니다. */
  --surface-2: #f0f0ed;
  --bg-elevated: #e6e6e2;
  --border: #d4d4d0;
  --accent: #4f46e5;

  --platform-google: #2563a8;
  --platform-meta: #7c3aed;
  --platform-naver: #16a34a;
  --platform-kakao: #b45309;

  --chart-bar: #6b62ea;
  --chart-line: #16a34a;
  --chart-alt: #2563a8;
  --chart-axis: rgba(20, 20, 15, 0.10);
  --chart-guide: rgba(20, 20, 15, 0.35);
  --chart-dot-stroke: #ffffff;
  --chart-area-opacity: 0.18;

  --shadow-card: 0 0 0 1px rgba(0, 0, 0, 0.02), 0 1px 3px 0 rgba(0, 0, 0, 0.08);
  --shadow-ring: 0 0 0 1px rgba(0, 0, 0, 0.06);
  --shadow-popover: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
  --shadow-btn-inset: inset 0 1px 0 0 rgba(255, 255, 255, 0.12);
  --focus-ring: rgba(0, 0, 0, 0.08);

  --glass-bg: #ffffff;
  --glass-border: rgba(20, 20, 15, 0.12);
  --glass-highlight: transparent;
  --overlay-bg: rgba(20, 20, 15, 0.35);

  --btn-primary-hover: #33322c;

  --btn-primary-hover: #e5e5e5;
}

/* ---------- Reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0;
  font-family: var(--font-body);
  /* 예전엔 line-height 가 없어 브라우저 기본값(normal)을 썼습니다. new_D 의
     본문 행간 1.5 를 기준으로 깔고, 밀도가 필요한 곳만 개별로 조입니다. */
  line-height: 1.5;
  color: var(--text-primary);
  background: var(--bg-base);
  -webkit-font-smoothing: antialiased;
  font-feature-settings: 'tnum' 1;
}
h1, h2, h3, p, ul { margin: 0; }
ul { list-style: none; padding: 0; }
a { color: inherit; text-decoration: none; }
button, input, select, textarea { font-family: inherit; font-size: inherit; color: inherit; }
button { cursor: pointer; background: none; border: none; }
table { border-collapse: collapse; width: 100%; }

:focus-visible {
  outline: 2px solid var(--text-primary);
  outline-offset: 2px;
  border-radius: 4px;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  .hover-lift:hover { transform: none !important; }
}

/* 공통 호버 트랜지션 — dub 은 요소를 들어올리지 않고 배경·테두리만 바꿉니다.
   클래스명은 HTML 172곳에 박혀 있어 그대로 두고 transform 만 뺐습니다. */
.hover-lift {
  transition: background 0.16s ease, border-color 0.16s ease, box-shadow 0.16s ease, color 0.16s ease;
}

/* ==========================================================================
   로그인 화면
   ========================================================================== */

.login-page {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: var(--bg-base);
}

.login-card {
  width: 100%;
  max-width: 400px;
  background: var(--bg-surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  padding: 32px 28px 28px;
}
.login-card .btn-primary { width: 100%; }

.brand-mark {
  width: 32px;
  height: 32px;
  border-radius: var(--radius-card);
  background: var(--text-primary);
  color: var(--text-on-accent);
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 600;
  font-size: 15px;
  margin-bottom: 18px;
}

.login-card h1 {
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.02em;
}

.login-sub {
  margin-top: 4px;
  font-size: 13px;
  color: var(--text-muted);
}

.login-form {
  margin-top: 28px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.field { display: flex; flex-direction: column; gap: 6px; }
/* label 자체도 flex 입니다 — campaign-builder.html 은 <label>텍스트<input></label>
   처럼 컨트롤을 label 안에 넣는데, 그러면 label 이 .field 의 유일한 자식이 되어
   위의 gap/stretch 가 컨트롤에 닿지 않습니다(모든 입력칸이 라벨 옆 좁은 인라인
   박스로 렌더되던 원인). 컬럼이 아니라 wrap 되는 행인 이유는 <span class="field-optional">
   (선택)</span> 같은 보조 라벨이 같은 줄에 남아야 하기 때문입니다 — 컴럼으로 두면
   보조 라벨이 저 혼자 한 줄을 차지합니다. 컨트롤만 flex-basis:100% 로 다음 줄로 내립니다. */
.field label { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px; font-size: 12px; font-weight: 500; color: var(--text-secondary); }
.field label > input, .field label > select, .field label > textarea { flex: 1 0 100%; }

.field input, .field textarea {
  width: 100%;
  padding: 0 12px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-control);
  background: var(--bg-surface);
  color: var(--text-primary);
  font-size: 14px;
  transition: box-shadow 0.15s ease, border-color 0.15s ease;
}
.field input { height: 40px; }
/* 여러 줄 카피 입력 — 예전엔 스타일시트에 textarea 규칙이 하나도 없어서 UA 기본
   모노스페이스 20자 박스로 렌더됐습니다(광고 본문 입력이 답답했던 이유). */
.field textarea { min-height: 132px; padding: 10px 12px; line-height: 1.55; resize: vertical; }
.field input::placeholder, .field textarea::placeholder { color: var(--text-muted); }
/* 포커스 링은 무채색 — dub 은 크롬에 브랜드색을 안 씁니다 */
.field input:focus, .field textarea:focus {
  outline: none;
  box-shadow: 0 0 0 3px var(--focus-ring);
  border-color: var(--border-strong);
}

/* dub primary — 검정(라이트)/흰(다크) 채움 + 위쪽 내부광 */
.btn-primary {
  height: 40px;
  border: none;
  border-radius: var(--radius-control);
  background: var(--text-primary);
  color: var(--text-on-accent);
  font-size: 13px;
  font-weight: 500;
  margin-top: 4px;
}
.btn-primary:hover { background: var(--btn-primary-hover); }
.btn-primary:disabled { opacity: 0.4; cursor: not-allowed; }
.btn-primary:disabled:hover { background: var(--text-primary); }

.btn-sm { height: 30px; padding: 0 10px; font-size: 13px; }

/* dub secondary — 흰 배경 + 헤어라인 테두리 */
.btn-ghost {
  height: 32px;
  padding: 0 12px;
  border-radius: var(--radius-control);
  background: var(--bg-surface);
  border: 1px solid var(--hairline);
  color: var(--text-secondary);
  font-size: 13px;
  font-weight: 500;
}
.btn-ghost:hover { background: var(--bg-inset); color: var(--text-primary); }
/* 표 헤더의 로컬 라이브 필터 버튼이 켜졌을 때 — 크롬은 무채색이라 파랑 대신
   표면 대비로만 켜짐을 표시합니다(.sidebar-nav a.is-active 와 동일 관례). */
.btn-ghost.is-active { background: var(--bg-surface-alt); color: var(--text-primary); border-color: var(--border-strong); }

.form-error { white-space: pre-line;
  display: none;
  font-size: 13px;
  color: var(--negative);
  background: var(--negative-tint);
  /* 예전엔 다크 --negative 값을 복사해 뒀습니다 — 토큰을 바꾸면 안 따라왔습니다. */
  border: 1px solid color-mix(in srgb, var(--negative) 30%, transparent);
  border-radius: var(--radius-control);
  padding: 9px 12px;
}
.form-error.is-visible { display: block; }

/* 텍스트처럼 보이는 버튼 — 비밀번호 찾기 등 부차적 동작에 씁니다.
   <a> 로 두면 href 가 없어 키보드 접근이 어긋나므로 button 을 쓰고 모양만 링크로 만듭니다. */
.link-btn {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: var(--text-muted);
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}
.link-btn:hover { color: var(--text-primary); }

/* 가입 메일 인증 코드 입력 — 6자리를 한 눈에 세어볼 수 있게 넓게 띄웁니다. */
.field input.code-input {
  font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: 24px;
  font-weight: 500;
  letter-spacing: 0.32em;
  text-align: center;
  text-indent: 0.32em;   /* 자간 때문에 오른쪽으로 쏠리는 것을 보정 */
}

.login-hint {
  margin-top: 22px;
  text-align: center;
  font-size: 12px;
  color: var(--text-muted);
}
.login-hint code {
  font-family: var(--font-mono);
  background: var(--bg-inset);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-control);
  padding: 1px 6px;
}

/* ---------- 로그인 스플릿 스크린(좌: 히어로, 우: 카드) ----------
   색은 전부 토큰만 써서 대시보드와 같은 다크/라이트를 그대로 따라갑니다
   (저장된 테마 설정 무관하게 강제하지 않음 — 다크로 쓰면 로그인도 다크). */
.login-shell { min-height: 100vh; display: flex; }

.login-hero {
  flex: 7 1 0;
  display: flex;
  align-items: center;
  padding: 64px 72px;
  background: var(--bg-surface-alt);
  border-right: 1px solid var(--hairline);
}
.login-hero-inner { max-width: 440px; }
.login-hero-brand { display: flex; align-items: center; gap: 10px; margin-bottom: 24px; }
.login-hero-mark { font-size: 28px; line-height: 1; color: var(--signal); }
.login-hero-word { font-size: 26px; font-weight: 500; letter-spacing: -0.01em; color: var(--text-primary); }
.login-hero-tagline { font-size: 20px; font-weight: 500; color: var(--text-secondary); margin-bottom: 14px; line-height: 1.4; }
.login-hero-desc { font-size: 14px; color: var(--text-muted); line-height: 1.6; }

.login-panel {
  flex: 3 1 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: var(--bg-base);
}

.pw-toggle-btn { margin-left: auto; }

@media (max-width: 720px) {
  .login-hero { display: none; }
  .login-shell { flex-direction: column; }
}

/* ==========================================================================
   대시보드 셸
   ========================================================================== */

.app-shell { display: flex; min-height: 100vh; }

/* ---------- Sidebar ----------
   dub 은 사이드바를 본문 표면보다 한 톤 죽여 깊이를 만듭니다. */
.sidebar {
  width: var(--sidebar-width);
  flex-shrink: 0;
  background: var(--bg-inset);
  border-right: 1px solid var(--hairline);
  display: flex;
  flex-direction: column;
  padding: 20px 14px;
  position: sticky;
  top: 0;
  height: 100vh;
}
.sidebar-brand { display: flex; align-items: center; gap: 10px; padding: 0 6px; margin-bottom: 28px; }
.sidebar-brand .brand-mark { margin-bottom: 0; width: 28px; height: 28px; font-size: 13px; border-radius: var(--radius-card); flex-shrink: 0; font-weight: 600; }
.sidebar-brand-text { display: flex; flex-direction: column; line-height: 1.25; flex: 1; min-width: 0; overflow: hidden; }
.sidebar-brand-text strong { font-size: 14px; font-weight: 600; white-space: nowrap; }
.sidebar-brand-text span { font-size: 11px; color: var(--text-muted); white-space: nowrap; }
.sidebar-collapse-btn { flex-shrink: 0; }

.sidebar-nav { display: flex; flex-direction: column; gap: 2px; }
.sidebar-nav a {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 10px;
  border-radius: var(--radius-control);
  font-size: 13px;
  font-weight: 500;
  color: var(--text-secondary);
  white-space: nowrap;
  overflow: hidden;
}
.sidebar-nav a:hover { background: var(--bg-surface-alt); color: var(--text-primary); }
.sidebar-nav a.is-active { background: var(--signal-tint); color: var(--signal); }
.nav-icon { flex-shrink: 0; }

.sidebar-footer { margin-top: auto; padding-top: 14px; border-top: 1px solid var(--hairline); }
.sidebar-footer button {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 10px;
  text-align: left;
  padding: 9px 10px;
  border-radius: var(--radius-control);
  font-size: 13px;
  font-weight: 500;
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
}
.sidebar-footer button:hover { background: var(--bg-surface-alt); color: var(--negative); }

/* ---- 사이드바 반응형 접기 (Meta 광고 관리자 참고) ---- */
.sidebar.is-collapsed { width: 68px; }
.sidebar.is-collapsed .sidebar-brand-text,
.sidebar.is-collapsed .nav-label,
.sidebar.is-collapsed .sidebar-footer button span:not(.nav-icon) {
  display: none;
}
.sidebar.is-collapsed .sidebar-brand { flex-direction: column; justify-content: center; padding: 0; gap: 8px; }
.sidebar.is-collapsed .sidebar-nav a,
.sidebar.is-collapsed .sidebar-footer button { justify-content: center; padding: 10px; }

/* ---------- Main / Topbar ---------- */
.main { flex: 1; min-width: 0; }

.topbar {
  border-bottom: 1px solid var(--hairline);
  background: var(--bg-surface);
  padding: 14px 28px;
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.topbar-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
.topbar-left, .topbar-right { display: flex; align-items: center; gap: 10px; }
.topbar-label { font-size: 12px; color: var(--text-muted); font-weight: 500; margin-right: 2px; }

/* dub 세그먼트 컨트롤 — 회색 트랙 안에서 활성 칸만 흰 카드로 떠오릅니다 */
.toggle-group {
  display: flex;
  align-items: center;
  gap: 2px;
  background: var(--bg-inset);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-control);
  padding: 2px;
}
.toggle-btn {
  height: 28px;
  padding: 0 12px;
  border-radius: 6px;
  font-size: 13px;
  font-weight: 500;
  color: var(--text-secondary);
  white-space: nowrap;
}
.toggle-btn.is-active {
  background: var(--bg-surface); color: var(--text-primary);
  box-shadow: var(--shadow-ring);
}

.icon-btn {
  width: 28px; height: 28px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 6px;
  color: var(--text-secondary);
  font-size: 15px;
}
.icon-btn:hover { background: var(--bg-inset); color: var(--text-primary); }

.user-chip {
  display: flex; align-items: center; gap: 8px;
  padding: 4px 10px 4px 4px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-pill);
}
.user-avatar {
  width: 24px; height: 24px; border-radius: 50%;
  background: var(--signal-solid); color: var(--text-on-signal);
  display: flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 600;
}
.user-chip span { font-size: 12px; font-weight: 500; }

/* ---------- Calendar popover ---------- */
.calendar-popover {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  z-index: 30;
  width: 300px;
  background: var(--bg-surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-control);
  box-shadow: var(--shadow-popover);
  padding: 14px;
}
.calendar-nav {
  display: flex; align-items: center; justify-content: space-between;
  font-size: 13px; font-weight: 600; margin-bottom: 10px;
}
.calendar-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 3px;
}
.cal-dow { font-size: 10px; color: var(--text-muted); text-align: center; padding-bottom: 4px; }
.cal-day {
  height: 30px;
  border-radius: 6px;
  font-size: 12px;
  color: var(--text-secondary);
  display: flex; align-items: center; justify-content: center;
}
.cal-day:hover { background: var(--bg-surface-alt); color: var(--text-primary); }
.cal-day.is-empty { visibility: hidden; }
/* 수집된 데이터가 없는 날 — 고를 수 없다는 걸 눈으로도 보이게 합니다 */
.cal-day.is-out { opacity: 0.32; cursor: not-allowed; }
.cal-day.is-out:hover { background: none; color: var(--text-secondary); }
.cal-day.is-selected { background: var(--signal-solid); color: var(--text-on-signal); font-weight: 600; }
.cal-day.is-in-range { background: var(--signal-tint); color: var(--text-primary); }
.calendar-footer {
  margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--hairline);
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
}
.calendar-footer span#calRangeHint { font-size: 11px; color: var(--text-muted); }
.calendar-actions { display: flex; gap: 6px; }

/* ---------- 지표 선택 리스트박스 (우측 정렬 드롭다운) ---------- */
.listbox-wrap { position: relative; margin-left: auto; }
.listbox-popover {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 30;
  width: 200px;
  max-height: 320px;
  overflow-y: auto;
  background: var(--bg-surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  padding: 6px;
}
.listbox-row {
  display: flex; align-items: center; gap: 9px;
  padding: 8px 10px;
  border-radius: var(--radius-card);
  font-size: 13px;
  color: var(--text-secondary);
}
.listbox-row:hover { background: var(--bg-surface-alt); color: var(--text-primary); }
.listbox-row input { accent-color: var(--signal-solid); }
.listbox-row[hidden] { display: none; }

/* ---------- 지표 리스트박스 드래그 순서 변경 (표시 지표) ---------- */
.listbox-row--draggable { padding-left: 6px; }
/* 유니코드 손잡이 글리프는 폰트마다 렌더링이 달라 순수 CSS 점 6개로 그립니다 */
.drag-handle {
  flex: none; width: 16px; height: 18px; position: relative; cursor: grab; touch-action: none;
}
.drag-handle::before {
  content: ''; position: absolute; left: 5px; top: 3px;
  width: 2px; height: 2px; border-radius: 50%; background: var(--text-muted);
  box-shadow:
    0 5px 0 var(--text-muted), 0 10px 0 var(--text-muted),
    6px 0 0 var(--text-muted), 6px 5px 0 var(--text-muted), 6px 10px 0 var(--text-muted);
}
.drag-handle:active { cursor: grabbing; }
.listbox-row--draggable.is-dragging { opacity: 0.45; }
.listbox-row--draggable.is-drop-target { box-shadow: inset 0 2px 0 var(--signal); }
.listbox-row.is-locked .drag-handle { cursor: default; opacity: 0.4; }

/* 긴 목록(캠페인 80개·광고세트 500개+)용 검색 줄.
   팝오버 자체가 스크롤 컨테이너(overflow-y:auto)라 sticky 로 상단에 붙입니다 —
   top 의 음수값은 팝오버 padding(6px)을 되돌리는 값입니다. */
.listbox-toolbar {
  position: sticky; top: -6px; z-index: 1;
  display: flex; gap: 6px; align-items: center;
  padding: 2px 2px 6px; margin-bottom: 2px;
  background: var(--bg-surface); border-bottom: 1px solid var(--hairline);
}
.listbox-search {
  flex: 1 1 auto; min-width: 0; height: 28px; padding: 0 8px;
  font-size: 12px; color: var(--text-primary);
  background: var(--bg-inset); border: 1px solid var(--border-strong);
  border-radius: var(--radius-control);
}
.listbox-clear {
  flex: none; height: 28px; padding: 0 8px;
  font-size: 11px; color: var(--text-secondary);
  background: transparent; border: 1px solid var(--border-strong);
  border-radius: var(--radius-control); cursor: pointer;
}
.listbox-clear:hover { color: var(--text-primary); background: var(--bg-surface-alt); }
.listbox-clear[hidden] { display: none; }

/* ---------- Content ---------- */
.content {
  max-width: var(--page-max-width);
  margin: 0 auto;
  padding: 24px 28px 64px;
  display: flex;
  flex-direction: column;
  gap: 18px;
}

/* 실데이터 도착 전 — 목업 숫자를 실적으로 오해하지 않게 흐리게 두고 알립니다.
   (조회 조건 바는 조작 가능해야 하므로 흐리게 하지 않습니다) */
.content.is-loading > .panel:not(.scope-panel),
.content.is-loading > .kpi-grid { opacity: 0.4; pointer-events: none; }
.content.is-loading::before {
  content: '데이터를 불러오는 중입니다…';
  display: block; margin-bottom: -6px;
  font-size: 12px; color: var(--text-muted);
}

.content-heading { display: flex; align-items: baseline; justify-content: space-between; flex-wrap: wrap; gap: 8px; }
.content-heading h1 {
  font-family: var(--font-display);
  font-size: 22px; font-weight: 450; letter-spacing: -0.02em; line-height: 1.2;
}
.content-heading p { font-size: 13px; color: var(--text-muted); }

/* ---------- 데이터 출처 배너 (실데이터 로드 시에만) ---------- */
.source-banner {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px;
  padding: 10px 14px; border-radius: var(--radius-card);
  background: var(--bg-surface); border: 1px solid var(--hairline);
  font-size: 11px; color: var(--text-secondary);
}
.source-banner code { font-size: 11px; color: var(--text-primary); }
.source-banner__tag {
  font-weight: 600; font-size: 10px; letter-spacing: 0.02em;
  padding: 2px 7px; border-radius: var(--radius-pill);
  color: var(--positive); background: var(--positive-tint);
}
.source-banner__sep { width: 1px; height: 11px; background: var(--hairline); }
.source-banner__at { color: var(--text-muted); margin-left: auto; }
.source-banner__warn {
  padding: 2px 7px; border-radius: var(--radius-pill);
  color: var(--warning); background: var(--warning-tint); font-weight: 500;
}
.source-banner--muted .source-banner__tag { color: var(--text-muted); background: transparent; }
/* 데이터를 못 받아온 매체 안내 — 목업이 실적처럼 남는 것을 막습니다 */
.source-banner--error {
  color: var(--negative); background: var(--negative-tint); border-color: transparent;
}
.source-banner--error a { color: inherit; text-decoration: underline; }
.source-banner--error strong { color: inherit; }

@media (max-width: 720px) {
  .source-banner__at { margin-left: 0; }
}

/* ---------- KPI cards ---------- */
/* 레퍼런스의 스탯 블록 문법 — 지표를 낱개 카드로 흩지 않고 하나의 박스 안에서
   세로 실선으로만 나눕니다. 그래서 테두리·배경·코너마크는 그리드가 갖고,
   카드는 칸막이(border-left)만 갖습니다. */
.kpi-grid {
  position: relative;
  display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 0;
  background: var(--bg-surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  overflow: hidden;
}
.kpi-card {
  border-left: 1px solid var(--hairline);
  padding: 18px 18px 16px;
}
.kpi-card:first-child { border-left: none; }
.kpi-card:hover { background: var(--bg-inset); }

/* 라벨은 작은 대문자형 캡션, 값은 크고 굵게 — 레퍼런스의 스탯 블록 문법 */
/* dub 스탯 타일 — 작은 색 사각점 + 회색 라벨 + 큰 모노 숫자.
   색점은 CSS 만으로 붙입니다(JS 마크업 무편집): 타일 순서에 따라 차트 3계열
   색을 돌려 씁니다. */
.kpi-card__label {
  display: flex; align-items: center; gap: 6px;
  font-size: 13px; color: var(--text-muted); font-weight: 500;
}
.kpi-card__label::before {
  content: ''; width: 8px; height: 8px; border-radius: 2px; flex: none;
  background: var(--chart-bar);
}
.kpi-card:nth-child(3n+2) .kpi-card__label::before { background: var(--chart-line); }
.kpi-card:nth-child(3n) .kpi-card__label::before { background: var(--chart-alt); }
/* 값 크기는 clamp — dub 은 스탯이 3칸이라 28px 를 쓰지만 우리는 6칸이라
   "₩90,899,007"(11자)이 28px 면 한 칸(≈190px)을 넘겨 두 줄로 접힙니다.
   상한을 22px 로 낮춰 6칸에서도 한 줄로 떨어지게 합니다. */
.kpi-card__value {
  margin-top: 10px; font-family: var(--font-mono);
  font-size: clamp(17px, 1.5vw, 22px); font-weight: 500;
  letter-spacing: -0.02em; line-height: 1.2; overflow-wrap: anywhere;
}
.kpi-card__delta {
  margin-top: 8px; display: inline-flex; align-items: center; gap: 3px;
  font-size: 11px; font-weight: 600; padding: 2px 7px; border-radius: var(--radius-pill);
}
.kpi-card__delta--up { color: var(--positive); background: var(--positive-tint); }
.kpi-card__delta--down { color: var(--negative); background: var(--negative-tint); }
.kpi-card__delta--flat { color: var(--text-muted); background: var(--bg-surface-alt); }
.kpi-card__delta-note { font-weight: 400; opacity: 0.75; margin-left: 2px; }

/* 합산할 수 없는 지표(도달·빈도)를 값 대신 '—' 로 표시할 때 */
.cell-na { color: var(--text-muted); cursor: help; border-bottom: 1px dotted var(--hairline); font-size: 0.92em; white-space: nowrap; }

/* 일부 매체만 제공하는 지표 — 값은 보여주되 불완전함을 표시.
   width:0 으로 두어 이 별표가 붙은 행만 숫자가 왼쪽으로 밀리는 일이 없게 합니다
   (별표는 셀 우측 padding 쪽으로 넘겨 그립니다). */
.cell-partial {
  display: inline-block; width: 0; overflow: visible;
  margin-left: 1px; font-size: 0.7em; font-weight: 600;
  color: var(--warning); cursor: help;
}

/* 계정 선택 팝업의 거래처·기간 옵션 */
.picker-options {
  display: flex; flex-wrap: wrap; gap: 14px;
  margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--hairline);
}
.picker-options label {
  display: flex; align-items: center; gap: 7px;
  font-size: 12px; color: var(--text-secondary);
}
.picker-options select { min-width: 132px; }
.picker-warn { color: var(--warning); font-style: normal; }
.picker-hint { margin-top: 8px; font-size: 11px; color: var(--text-muted); }


/* ---------- Panels ----------
   레퍼런스(axelar.network)의 컨테이너 패턴: 테두리 박스 안에서 헤더가 본문과
   가로 실선으로 완전히 갈리고, 제목 앞에는 도트 불릿이 붙습니다. 구분선을
   카드 안쪽 패딩까지 꽉 채우려고 헤더에만 음수 마진을 줍니다. */
/* --panel-pad 를 변수로 둔 이유: 헤더 구분선을 카드 좌우 끝까지 빼려면 음수 마진이
   패널 패딩과 정확히 같아야 하는데, 패널마다 패딩이 다릅니다(기본 20 / 탭패널 24 /
   사이드카드 16). 각 변형이 이 변수만 바꾸면 구분선이 자동으로 맞습니다. */
.panel {
  --panel-pad: 20px; position: relative;
  background: var(--bg-surface); border: 1px solid var(--hairline);
  border-radius: var(--radius-card); padding: var(--panel-pad);
  box-shadow: var(--shadow-card);
}
.panel-head {
  display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap;
  margin: calc(var(--panel-pad, 20px) * -1) calc(var(--panel-pad, 20px) * -1) 16px;
  padding: 14px var(--panel-pad, 20px);
  border-bottom: 1px solid var(--hairline);
}
/* 헤더만 있는 패널(지표 선택 바 등)은 아래에 가를 본문이 없으니 선을 빼줍니다 */
.panel-head:only-child { border-bottom: none; margin-bottom: calc(var(--panel-pad, 20px) * -1); }
.panel-head h2 {
  font-family: var(--font-display);
  font-size: 14px; font-weight: 450; letter-spacing: -0.01em;
}
.panel-head span { font-size: 12px; color: var(--text-muted); }

.metric-select {
  height: 30px; padding: 0 10px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-control);
  background: var(--bg-inset);
  font-size: 12px;
}

.chart-wrap { width: 100%; }
.chart-wrap svg { width: 100%; height: auto; display: block; }
.chart-axis-label { font-size: 10px; fill: var(--text-muted); font-family: var(--font-body); }

/* ---------- Platform list ---------- */
.platform-list { display: flex; flex-direction: column; gap: 14px; }
.platform-row { display: grid; grid-template-columns: 148px 1fr 96px 84px; align-items: center; gap: 14px; }
.platform-chip { display: inline-flex; align-items: center; gap: 8px; font-size: 13px; font-weight: 500; }
.platform-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.platform-dot--google { background: var(--platform-google); }
.platform-dot--meta { background: var(--platform-meta); }
.platform-dot--naver { background: var(--platform-naver); }
.platform-dot--kakao { background: var(--platform-kakao); }

.platform-bar-track { display: block; height: 6px; border-radius: 3px; background: var(--bg-inset); overflow: hidden; }
.platform-bar-fill { display: block; height: 100%; border-radius: 3px; }
.platform-bar-fill--google { background: var(--platform-google); }
.platform-bar-fill--meta { background: var(--platform-meta); }
.platform-bar-fill--naver { background: var(--platform-naver); }
.platform-bar-fill--kakao { background: var(--platform-kakao); }

.platform-spend { font-family: var(--font-mono); font-size: 13px; text-align: right; }
.platform-roas { font-family: var(--font-mono); font-size: 13px; text-align: right; color: var(--text-secondary); }

/* ---------- Data table (상시 고정) ---------- */
.table-scroll { overflow-x: auto; max-height: 360px; overflow-y: auto; border-radius: var(--radius-card); border: 1px solid var(--hairline); }

/* "상위 N행" 모드 — 기본 상태에서는 세로 스크롤을 걸지 않습니다. 표 안에 스크롤
   창이 있으면 페이지 스크롤과 휠이 충돌하고, 401행을 6행 창으로 보게 됩니다.
   펼친 뒤에만(.is-expanded) 뷰포트 높이 기준으로 스크롤을 겁니다. */
.table-scroll--paged { max-height: none; }
.table-scroll--paged.is-expanded { max-height: 70vh; }

/* 표 하단 "상위 N행 · 전체 M행 + 더 보기" 푸터 */
.table-more {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 8px 2px 0; font-size: 11px; color: var(--text-muted);
}
.table-more[hidden] { display: none; }
.table-more__count { font-variant-numeric: tabular-nums; }

/* 마지막으로 남은 지표(해제 불가) */
.listbox-row.is-locked { opacity: 0.55; cursor: not-allowed; }

/* "매체별 분리" 보기 — 매체마다 독립된 표를 세로로 나열하고, 전체 영역에만
   스크롤을 겁니다(표가 길어질 수 있어 페이지가 무한히 늘어나지 않게).
   스크롤 컨테이너를 .split-table-list 하나로만 두는 이유: .data-table thead th
   는 position:sticky 로 "가장 가까운 스크롤 컨테이너"를 기준으로 고정되는데,
   .split-table-block 에도 overflow-x:auto 를 주면 CSS 스펙상 그 요소의
   overflow-y 도 자동으로 auto 로 계산되어 블록 자신이 스크롤 컨테이너가
   되어버립니다 — 그러면 sticky thead 가 리스트 전체 스크롤이 아니라 각
   블록 내부를 기준으로 고정되려다 실제로는 안 붙는 회귀가 생깁니다. 모든
   분리 표가 같은 컬럼셋(CAMPAIGN_COLS)을 쓰므로 폭이 거의 같아, 가로
   스크롤도 이 하나의 컨테이너로 충분합니다. */
.split-table-list { display: flex; flex-direction: column; gap: 16px; max-height: 640px; overflow: auto; }
.split-table-list[hidden] { display: none; }
.split-table-block { border-radius: var(--radius-card); border: 1px solid var(--hairline); }
.split-table-block .data-table { margin: 0; }
.split-table-title {
  font-size: 12px; font-weight: 600; color: var(--text-secondary);
  display: flex; align-items: center; gap: 6px;
  padding: 10px 14px; border-bottom: 1px solid var(--hairline);
}
.split-table-count { margin-left: auto; font-weight: 500; font-size: 11px; color: var(--text-muted); font-variant-numeric: tabular-nums; }
/* 표의 규칙은 두 가지입니다: 1번 컬럼은 "정체성"(이름 — 좌측정렬·본문폰트·말줄임),
   나머지는 "지표"(우측정렬·모노·tabular). 매체·게재상태처럼 행마다 반복되는 값은
   별도 컬럼을 두지 않고 이름 셀의 보조줄(.ad-subtext)로 합칩니다.
   행 구분선 대신 zebra(--row-stripe)를 씁니다 — 수백 행 × 20컬럼을 가로로 훑을 때
   구분선보다 행 추적이 쉽고, 선과 줄무늬를 함께 쓰면 과합니다. */
.data-table { font-size: 12px; }
/* 헤더 라벨 — 레퍼런스의 작은 대문자 캡션 문법 */
.data-table thead th {
  position: sticky; top: 0; z-index: 2;
  background: var(--bg-inset);
  color: var(--text-muted);
  font-weight: 500;
  font-size: 12px;
  text-align: right;
  padding: 10px 14px;
  border-bottom: 1px solid var(--hairline);
  white-space: nowrap;
}
.data-table thead th:first-child { text-align: left; }
.data-table tbody td {
  padding: 8px 14px;
  text-align: right;
  font-family: var(--font-mono);
  white-space: nowrap;
  background: var(--bg-surface);
}
/* dub 은 zebra 대신 행 사이 헤어라인으로 행을 가릅니다.
   히트맵 표(.heatmap-table)는 같은 .data-table 클래스를 공유하지만 색이 곧
   데이터라서 배경을 덮으면 안 되므로 예전 zebra 규칙과 동일하게 제외합니다
   (제외하지 않으면 히트맵 색이 전부 덮여 "격줄이 비어 보이는" 문제가 재발). */
.data-table:not(.heatmap-table) tbody tr + tr td {
  border-top: 1px solid var(--hairline);
}
.data-table tbody td:first-child { text-align: left; font-family: var(--font-body); font-weight: 500; }
/* 위와 같은 이유로 호버 시에도 단색으로 덮으면 안 됩니다 — 명시적으로 제외합니다. */
.data-table:not(.heatmap-table) tbody tr:hover td { background-color: var(--bg-inset); }

/* 1번 컬럼(이름)을 가로 스크롤 중에도 고정 — 20컬럼 표에서 어느 행인지 잃지 않게.
   헤더 코너 셀은 헤더(2)보다 위(3)에 둬야 세로·가로 스크롤이 겹치지 않습니다. */
.data-table thead th:first-child { position: sticky; left: 0; z-index: 3; }
.data-table tbody td:first-child { position: sticky; left: 0; z-index: 1; }

/* ---------- 이름 셀 (모든 .data-table 공용 — 캠페인/소재/광고/위너/팀원 표) ----------
   정렬·폰트를 이 클래스가 직접 갖습니다. td:first-child 규칙에만 의존하면 위너 표처럼
   이름이 2번째 컬럼인 표에서 숫자 스타일(우측정렬·모노)이 그대로 남습니다. */
.data-table .ad-name-cell {
  text-align: left; font-family: var(--font-body); font-weight: 500;
  min-width: 190px; max-width: 320px; overflow: hidden; text-overflow: ellipsis;
}
.data-table .ad-subtext {
  font-family: var(--font-body); font-size: 10px; color: var(--text-muted); margin-top: 1px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* 캠페인/광고세트/소재 표 "예산" 컬럼의 유형·레벨 배지(일예산·캠페인 / 총예산·광고세트 상속 등) */
.data-table .budget-type-note {
  font-family: var(--font-body); font-size: 10px; color: var(--text-muted);
  margin-top: 1px; white-space: nowrap;
}
/* 매체 색 점을 이름 앞에 둡니다(별도 컬럼 대신) */
.ad-name-cell .platform-dot { margin-right: 6px; vertical-align: 1px; }
.status-dot { display: inline-block; width: 6px; height: 6px; border-radius: 50%; margin-right: 6px; }
.status-dot--on { background: var(--positive); }

/* ---------- 소재 성과 표 썸네일 칸 (맨 왼쪽) ----------
   행 높이는 그대로 두고, 그 안에 꽉 차는 정사각형으로 크게 보여줍니다. 폭을
   고정 폭(52px)으로 못박아야 옆 이름 칸의 sticky left 오프셋을 맞출 수 있습니다. */
.data-table .thumb-th,
.data-table .thumb-cell { width: 52px; min-width: 52px; max-width: 52px; padding: 6px !important; }
.data-table .thumb-cell .thumb-img {
  display: block; width: 40px; height: 40px; object-fit: cover; border-radius: 6px;
  background: var(--bg-inset);
}
.data-table .thumb-cell--none { background-color: var(--bg-inset); border-radius: 6px; }
/* 썸네일 칸이 생기면 이름 칸이 2번째 컬럼이 됩니다 — 가로 스크롤 고정을 두
   칸(썸네일+이름) 모두에 걸어야, 스크롤했을 때 소재명을 잃지 않습니다
   (썸네일만 남고 "이게 무슨 소재였는지" 안 보이는 상태 방지). 1번째 칸
   sticky 는 기존 :first-child 규칙이 이미 처리합니다. */
.data-table thead th.thumb-th + .sort-th,
.data-table tbody td.thumb-cell + .ad-name-cell { position: sticky; left: 52px; }
.data-table thead th.thumb-th + .sort-th { z-index: 3; }
.data-table tbody td.thumb-cell + .ad-name-cell { z-index: 1; }

/* 좁은 화면에서는 셀 여백과 이름 폭을 줄여 가로 스크롤 거리를 좁힙니다.
   (이전에는 표를 다루는 미디어쿼리가 아예 없어 모바일에서 그대로 넘쳤습니다) */
@media (max-width: 720px) {
  .data-table thead th { padding: 8px 10px; }
  .data-table tbody td { padding: 7px 10px; }
  .data-table .ad-name-cell { min-width: 150px; max-width: 200px; }
  .table-scroll--paged.is-expanded { max-height: 60vh; }
}

/* ==========================================================================
   설정 페이지
   ========================================================================== */
select {
  height: 36px; padding: 0 10px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-control);
  background: var(--bg-inset);
  color: var(--text-primary);
}
.settings-form { display: flex; flex-direction: column; gap: 16px; max-width: 420px; }
.settings-toggle-list { display: flex; flex-direction: column; gap: 4px; }
.settings-toggle-row {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 12px 4px; font-size: 13px; color: var(--text-secondary);
  border-bottom: 1px solid var(--hairline);
}
.settings-toggle-row:last-child { border-bottom: none; }
.settings-toggle-row input[type="checkbox"] { accent-color: var(--signal-solid); width: 16px; height: 16px; flex-shrink: 0; }
.settings-toggle-row select { width: auto; min-width: 180px; flex-shrink: 0; }
.settings-toggle-row > span { display: flex; flex-direction: column; gap: 3px; }
.settings-note { font-style: normal; font-size: 11px; color: var(--text-muted); }

.settings-actions {
  display: flex; justify-content: flex-end; gap: 8px;
  padding: 4px 0 8px;
}

/* 초대 대기중 상태 점 */
.status-dot--pending { background: var(--warning); }

/* ---------- 변경 이력 ---------- */
.log-entry { padding: 12px 0; border-bottom: 1px solid var(--hairline); }
.log-entry:last-child { border-bottom: none; }
.log-entry:first-child { padding-top: 0; }
.log-entry__time { font-size: 11px; color: var(--text-muted); font-family: var(--font-mono); margin-bottom: 7px; }
.log-entry__items { display: flex; flex-direction: column; gap: 5px; }
.log-entry__items li { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 12px; }
.log-field { font-weight: 600; color: var(--text-primary); min-width: 120px; }
.log-from { color: var(--text-muted); text-decoration: line-through; }
.log-arrow { color: var(--text-muted); }
.log-to { color: var(--positive); font-weight: 500; }

/* 저장 완료 팝업 */
.save-modal-head { display: flex; align-items: flex-start; gap: 12px; }
.save-check {
  width: 28px; height: 28px; flex-shrink: 0; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--positive-tint); color: var(--positive);
  font-size: 15px; font-weight: 500;
}
.change-diff { margin-top: 18px; display: flex; flex-direction: column; gap: 8px; }
.diff-row {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  font-size: 12px; padding: 9px 11px;
  background: var(--bg-inset); border: 1px solid var(--hairline);
  border-radius: var(--radius-control);
}
.diff-row--empty { color: var(--text-muted); }

/* ==========================================================================
   매체 연동 페이지
   ========================================================================== */
/* 카드 안에 거래처별 연동 현황이 들어가서 280px 로는 좁습니다 */
/* 매체는 구글·메타·네이버 셋으로 고정입니다. auto-fit + minmax(380px) 로 두면
   그리드 폭이 1168px(380×3 + 간격 28) 미만일 때 2+1 로 접혀서, 실제 화면(1109px)
   에서 네이버만 아래로 떨어졌습니다. 개수가 정해져 있으니 열도 정해 둡니다. */
.integration-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; align-items: start; }
@media (max-width: 1280px) { .integration-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 820px)  { .integration-grid { grid-template-columns: minmax(0, 1fr); } }
.integration-card { display: flex; flex-direction: column; gap: 14px; }
/* 매체 카드 헤더도 패널과 같은 문법으로 — 도트 불릿(이미 .platform-dot 이 그 역할)
   + 가로 구분선. .integration-card 는 gap:14px 인 flex 컬럼이라 아래 마진 대신
   음수 마진만으로 카드 끝까지 선을 뺍니다. */
.integration-card__head {
  display: flex; align-items: center; gap: 10px;
  margin: calc(var(--panel-pad, 20px) * -1) calc(var(--panel-pad, 20px) * -1) 0;
  padding: 14px var(--panel-pad, 20px);
  border-bottom: 1px solid var(--hairline);
}
.integration-card__head .platform-dot { width: 8px; height: 8px; margin-top: 0; }
.integration-card__head h2 { font-size: 14px; font-weight: 500; }
.integration-card__id { font-size: 11px; color: var(--text-muted); }
.integration-card__head .status-badge { margin-left: auto; flex-shrink: 0; }
.integration-card__meta { display: flex; flex-direction: column; gap: 6px; }
.integration-card__meta > div { display: flex; justify-content: space-between; font-size: 12px; }
.integration-card__meta dt { color: var(--text-muted); }
.integration-card__meta dd { margin: 0; color: var(--text-secondary); text-align: right; }

.status-badge {
  display: inline-flex; align-items: center; font-size: 11px; font-weight: 600;
  padding: 4px 10px; border-radius: var(--radius-pill);
}
.status-badge--connected { color: var(--positive); background: var(--positive-tint); }
.status-badge--expired { color: var(--warning); background: var(--warning-tint); }
.status-badge--error { color: var(--negative); background: var(--negative-tint); }
.status-badge--disconnected { color: var(--text-muted); background: var(--bg-inset); }

/* ---------- 자격증명 입력 폼 ---------- */
.credential-form { display: flex; flex-direction: column; gap: 10px; }
.credential-form .field input { height: 36px; font-size: 13px; }
.credential-actions { display: flex; gap: 8px; margin-top: auto; flex-wrap: wrap; }
.field-optional { color: var(--text-muted); font-weight: 400; }
.field-hint { font-size: 11px; color: var(--text-muted); }
.credential-hint {
  font-size: 12px; color: var(--text-secondary); line-height: 1.6;
  background: var(--bg-inset); border: 1px solid var(--hairline);
  border-radius: var(--radius-control); padding: 10px 12px;
}

/* 네이버 키 발급 안내 (접히는 영역) */
.credential-guide {
  font-size: 12px; color: var(--text-secondary);
  background: var(--bg-inset); border: 1px solid var(--hairline);
  border-radius: var(--radius-control); padding: 10px 12px;
}
.credential-guide summary { cursor: pointer; font-weight: 600; color: var(--text-primary); }
.credential-guide ol { margin: 10px 0 6px; padding-left: 18px; line-height: 1.8; }
.credential-guide li { list-style: decimal; }
.credential-guide code {
  font-family: var(--font-mono); font-size: 11px;
  background: var(--bg-surface-alt); border-radius: 4px; padding: 1px 5px;
}

/* ---------- 대행사 준비 상태 ---------- */
.readiness-list { display: flex; flex-direction: column; }
.readiness-row {
  display: grid; grid-template-columns: 150px 210px 84px 1fr;
  align-items: center; gap: 12px;
  padding: 11px 2px; font-size: 12px; color: var(--text-secondary);
  border-bottom: 1px solid var(--hairline);
}
.readiness-row:last-child { border-bottom: none; }
.readiness-note { font-size: 11px; color: var(--text-muted); }
.readiness-summary {
  margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--hairline);
  font-size: 12px; color: var(--text-muted);
}

@media (max-width: 720px) {
  .readiness-row { grid-template-columns: 1fr auto; row-gap: 4px; }
  .readiness-row > span:nth-child(2) { grid-column: 1 / -1; font-size: 11px; color: var(--text-muted); }
  .readiness-note { grid-column: 1 / -1; }
}

/* ---------- 카드 내 거래처별 연동 현황 ---------- */
.client-conn-list {
  display: flex; flex-direction: column;
  margin: 14px 0 4px;
  border-top: 1px solid var(--hairline);
}
.client-conn-row {
  display: grid; grid-template-columns: 64px 1fr auto;
  align-items: center; gap: 4px 10px;
  padding: 9px 0;
  border-bottom: 1px solid var(--hairline);
  font-size: 12px;
}
.client-conn-row__name { font-weight: 500; color: var(--text-secondary); }
/* 토큰 권한 배지 — 이름 칸이 64px로 좁아 대부분 이름 아래로 자연스럽게 줄바꿈됩니다
   (별도 grid 칸을 새로 만들지 않음 — 3칸 grid 구조를 그대로 유지하기 위해
   이름 span 안에 중첩). */
.client-conn-row__name .status-badge {
  margin: 2px 0 0 4px; padding: 2px 7px; font-size: 10px; vertical-align: middle;
}
.client-conn-row__detail { color: var(--text-muted); font-size: 11px; line-height: 1.5; }
.client-conn-row__detail code { font-family: var(--font-mono); font-size: 11px; }
.client-conn-row__actions { display: flex; gap: 6px; }
.client-conn-row__actions button { padding: 3px 8px; font-size: 11px; }
.client-conn-row--off .client-conn-row__name { color: var(--text-muted); }
.client-conn-row__err { grid-column: 2 / -1; color: var(--warning); font-size: 11px; }

@media (max-width: 520px) {
  .client-conn-row { grid-template-columns: 1fr auto; }
  .client-conn-row__detail { grid-column: 1 / -1; }
}

/* 연동할 거래처 선택 */
.credential-target {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px;
  margin: 14px 0 12px;
}
.credential-target label {
  display: flex; align-items: center; gap: 7px;
  font-size: 12px; color: var(--text-secondary);
}
.credential-target select { min-width: 120px; }
.credential-target__note { font-size: 11px; color: var(--warning); }

/* ---------- 계정 선택 팝업: 검색·전체선택 ---------- */
.picker-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.picker-total { font-size: 11px; color: var(--text-muted); }
.picker-toolbar { display: flex; gap: 6px; margin: 10px 0 8px; }
.picker-toolbar input[type="search"] {
  flex: 1; min-width: 0;
  padding: 7px 10px; font-size: 12px;
  color: var(--text-primary);
  background: var(--bg-inset);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-control);
}
.picker-toolbar input[type="search"]:focus { outline: none; border-color: var(--signal); }
.picker-toolbar button { flex-shrink: 0; padding: 6px 9px; font-size: 11px; }
.picker-empty { padding: 14px 2px; font-size: 12px; color: var(--text-muted); }
.picker-row[hidden] { display: none; }

/* ---------- 광고 계정 선택 ---------- */
.account-picker { margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--hairline); }
.account-picker__label { font-size: 12px; font-weight: 600; margin-bottom: 10px; }
.picker-row {
  display: grid; grid-template-columns: auto 1fr auto; gap: 4px 10px;
  align-items: center; padding: 9px 10px; border-radius: var(--radius-card);
  font-size: 12px; cursor: pointer;
}
.picker-row:hover { background: var(--bg-surface-alt); }
.picker-row input { accent-color: var(--signal-solid); }
.picker-name { font-weight: 500; }
.picker-id { font-family: var(--font-mono); font-size: 11px; color: var(--text-muted); }
.picker-note { grid-column: 2 / -1; font-size: 11px; color: var(--text-muted); }

.doc-note { font-size: 12px; color: var(--text-secondary); line-height: 1.7; }
.doc-note code { font-family: var(--font-mono); font-size: 11px; background: var(--bg-inset); border-radius: 4px; padding: 1px 5px; }

.form-success {
  display: none;
  font-size: 12px;
  color: var(--positive);
  background: var(--positive-tint);
  /* 예전엔 다크 --positive 값을 복사해 뒀습니다 — 토큰을 바꾸면 안 따라왔습니다. */
  border: 1px solid color-mix(in srgb, var(--positive) 30%, transparent);
  border-radius: var(--radius-control);
  padding: 9px 12px;
}
.form-success.is-visible { display: block; }

/* ==========================================================================
   진행 상황 모달 (화면 가운데, 글라스)
   ========================================================================== */
body.has-modal { overflow: hidden; }

.modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: var(--overlay-bg);
}
.modal-overlay[hidden] { display: none; }

.modal-card {
  width: 100%;
  max-width: 460px;
  /* 계정이 많아도 팝업이 화면을 넘지 않게: 본문만 스크롤하고 푸터는 고정.
     100vh 대신 오버레이 높이(100%)를 쓰는 이유 — 오버레이 padding 이 자동 반영되고,
     모바일에서 주소창 때문에 100vh 가 실제 보이는 높이보다 커지는 문제를 피합니다. */
  max-height: 100%;
  min-height: 0;
  display: flex;
  flex-direction: column;
  border-radius: var(--radius-card);
  overflow: hidden;
  background: var(--glass-bg);
  border: 1px solid var(--glass-border);
  box-shadow: var(--shadow-popover);
}
.modal-head { flex-shrink: 0; padding: 22px 24px 14px; }
.modal-card h2 { font-family: var(--font-display); font-size: 16px; font-weight: 450; letter-spacing: -0.01em; }
.modal-sub { margin-top: 4px; font-size: 12px; color: var(--text-muted); }

/* 스크롤되는 영역은 여기 하나뿐입니다 (중첩 스크롤은 더 불편해서 피했습니다) */
.modal-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 0 24px 4px;
}

/* 고정 푸터 — 계정이 100개여도 확인 버튼은 항상 여기 있습니다 */
.modal-actions {
  flex-shrink: 0;
  display: flex; justify-content: flex-end; align-items: center; gap: 8px;
  padding: 14px 24px 18px;
  border-top: 1px solid var(--hairline);
  background: var(--glass-bg);
}
.modal-actions__count { margin-right: auto; font-size: 11px; color: var(--text-muted); }

.progress-steps { margin: 14px 0 0; display: flex; flex-direction: column; gap: 2px; }
.progress-step {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 4px;
  font-size: 13px;
  color: var(--text-muted);
  transition: color 0.2s ease;
}
.progress-step__mark {
  width: 18px; height: 18px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  border: 1px solid var(--border-strong);
  font-size: 10px; font-weight: 500;
}
.progress-step.is-running { color: var(--text-primary); }
.progress-step.is-running .progress-step__mark { border-color: var(--signal); }
.progress-step.is-done { color: var(--text-secondary); }
.progress-step.is-done .progress-step__mark { border-color: var(--positive); color: var(--positive); background: var(--positive-tint); }
.progress-step.is-failed { color: var(--negative); }
.progress-step.is-failed .progress-step__mark { border-color: var(--negative); color: var(--negative); background: var(--negative-tint); }

.spinner {
  width: 10px; height: 10px;
  border: 1.6px solid var(--signal);
  border-top-color: transparent;
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

.modal-overlay .form-error { margin-top: 14px; }

/* ==========================================================================
   차트 hover 툴팁 (글라스) + 차트 보조 UI
   ========================================================================== */
.chart-tooltip {
  position: fixed;
  z-index: 90;
  pointer-events: none;
  min-width: 148px;
  padding: 10px 12px;
  border-radius: var(--radius-control);
  font-size: 12px;
  background: var(--glass-bg);
  border: 1px solid var(--glass-border);
  box-shadow: var(--shadow-popover);
  color: var(--text-primary);
}
.chart-tooltip[hidden] { display: none; }
.chart-tooltip .tt-date { font-size: 11px; color: var(--text-muted); margin-bottom: 6px; }
.chart-tooltip .tt-row { display: flex; align-items: center; gap: 6px; line-height: 1.9; }
.chart-tooltip .tt-row b { margin-left: auto; font-family: var(--font-mono); font-weight: 600; }

.legend-swatch { display: inline-block; width: 8px; height: 8px; flex-shrink: 0; }
.legend-swatch--bar { border-radius: 2px; background: var(--chart-bar); }
.legend-swatch--line { border-radius: 50%; background: var(--chart-line); }

.chart-legend { display: flex; gap: 16px; margin-top: 8px; font-size: 11px; color: var(--text-muted); }
.chart-legend > span { display: inline-flex; align-items: center; gap: 5px; }

/* 매체별 지표 — 지표 1개 × 매체 다중선택 라인 범례. 색상은 매체 기준(지표는
   전역 단일값이라 범례엔 매체명만 표시). hover/click 으로 강조·토글. */
.chart-legend--combo { flex-wrap: wrap; gap: 10px 14px; margin-top: 10px; }
.legend-line-combo { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; user-select: none; }
.legend-line-combo.is-dimmed { opacity: .45; }
.trend-line { transition: opacity 0.12s ease; }

.chart-hint { font-size: 11px; color: var(--warning); margin-top: 6px; }
.chart-hint:empty { display: none; }

.chart-wrap svg { cursor: crosshair; }
.chart-guide, .chart-focus-dot { transition: opacity 0.12s ease; }

.chart-metric-controls { display: flex; gap: 10px; flex-wrap: wrap; }
.chart-metric-field { display: inline-flex; align-items: center; gap: 6px; font-size: 11px; color: var(--text-muted); }

/* ---------- 일별 추이 — 매체별 패널 ----------
   매체 선택 수만큼 패널이 생깁니다. 기본 폭은 100%(테이블과 같은 반응형 폭),
   "작게 보기"는 프리셋 폭으로 줄여 여러 매체를 나란히 볼 수 있게 하고,
   오른쪽 가장자리 손잡이로는 그 사이 어떤 폭이든 자유롭게 잡을 수 있습니다. */
.combo-chart-list { display: flex; flex-wrap: wrap; gap: 16px; }
.combo-panel {
  position: relative;
  flex: 1 1 100%; min-width: 0; max-width: 100%;
  border: 1px solid var(--hairline); border-radius: var(--radius-card);
  padding: 14px 20px 16px;
}
.combo-panel.is-compact { flex: 0 1 560px; }
.combo-panel.is-resizing { transition: none; user-select: none; }
.combo-panel__head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 10px; }
.combo-panel__head h3 { display: flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 600; color: var(--text-secondary); }
.combo-panel__resize {
  position: absolute; top: 0; right: -8px; bottom: 0; width: 16px;
  cursor: ew-resize; touch-action: none;
}
.combo-panel__resize::after {
  content: ''; position: absolute; top: 50%; right: 7px; transform: translateY(-50%);
  width: 3px; height: 32px; border-radius: 2px; background: var(--border-strong);
  transition: background 0.12s ease;
}
.combo-panel__resize:hover::after, .combo-panel.is-resizing .combo-panel__resize::after { background: var(--signal); }
@media (max-width: 720px) {
  .combo-panel { flex-basis: 100% !important; width: auto !important; }
  .combo-panel__resize { display: none; }
}

/* ---------- 빈 상태 / 안내 ---------- */
.empty-state {
  padding: 24px;
  text-align: center;
  font-size: 13px;
  color: var(--text-muted);
  background: var(--bg-inset);
  border: 1px dashed var(--hairline);
  border-radius: var(--radius-control);
}
.empty-state a { color: var(--signal); text-decoration: underline; }

.platform-notice {
  margin-top: 16px; padding-top: 14px;
  border-top: 1px solid var(--hairline);
  font-size: 12px; color: var(--text-muted);
}
.platform-notice[hidden] { display: none; }
.platform-notice a { color: var(--signal); }

.inline-warn {
  font-size: 10px; font-weight: 600;
  color: var(--warning); background: var(--warning-tint);
  padding: 1px 6px; border-radius: var(--radius-pill);
  margin-left: 4px;
}

/* ==========================================================================
   반응형
   ========================================================================== */

@media (max-width: 860px) {
  .platform-row { grid-template-columns: 1fr; row-gap: 6px; text-align: left; }
  .platform-spend, .platform-roas { text-align: left; }
}

@media (max-width: 720px) {
  .app-shell { flex-direction: column; }
  .sidebar {
    position: static; height: auto; width: 100%;
    flex-direction: row; align-items: center;
    padding: 10px 12px; border-right: none; border-bottom: 1px solid var(--hairline);
    gap: 10px;
  }
  .sidebar-brand { margin-bottom: 0; flex-shrink: 0; }
  .sidebar-brand-text span { display: none; }
  .sidebar-brand-text strong { white-space: nowrap; font-size: 13px; }
  .sidebar-nav { flex-direction: row; flex: 1 1 auto; min-width: 0; overflow-x: auto; gap: 4px; }
  .sidebar-nav a { white-space: nowrap; flex-shrink: 0; }
  .sidebar-footer { margin-top: 0; margin-left: 0; padding-top: 0; border-top: none; flex-shrink: 0; }
  .sidebar-footer button { white-space: nowrap; padding: 9px 8px; }
  .sidebar-collapse-btn { display: none; } /* 모바일은 이미 가로형 축약 레이아웃이라 접기 토글 불필요 */
  .sidebar.is-collapsed { width: 100%; } /* 혹시 데스크톱에서 접은 채로 모바일 폭이 되어도 레이아웃 깨지지 않도록 */

  .topbar { padding: 12px 16px; position: static; }
  .topbar-row { flex-wrap: wrap; }
  .toggle-group { overflow-x: auto; max-width: 100%; }
  .content { padding: 20px 16px 48px; }
  .calendar-popover { left: 50%; transform: translateX(-50%); }
}

@media (max-width: 480px) {
  /* 2열로 접히면 세로 칸막이만으로는 행 구분이 안 되니 가로선을 함께 씁니다.
     (첫 행에는 윗선이 없어야 해서 앞 두 칸만 예외 처리) */
  .kpi-grid { grid-template-columns: 1fr 1fr; }
  .kpi-card { border-top: 1px solid var(--hairline); }
  .kpi-card:nth-child(-n+2) { border-top: none; }
  .kpi-card:nth-child(odd) { border-left: none; }
  .login-card { padding: 28px 22px; }
}

/* ==========================================================================
   상세 분석 페이지
   ========================================================================== */
.scope-panel { display: flex; flex-direction: column; gap: 14px; }

.scope-grid {
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: 10px 14px;
}
.scope-field {
  display: flex; flex-direction: column; gap: 5px;
  font-size: 11px; color: var(--text-muted); font-weight: 500;
}
.scope-field select { min-width: 132px; }
.scope-field--check {
  flex-direction: row; align-items: center; gap: 7px;
  font-size: 12px; color: var(--text-secondary); padding-bottom: 7px;
}
.scope-field--check input { accent-color: var(--signal-solid); }

.media-panel .panel-head h2 { display: flex; align-items: center; gap: 7px; }
.media-panel .panel-head span code {
  font-family: var(--font-mono); font-size: 11px;
  background: var(--bg-inset); border-radius: 4px; padding: 1px 5px;
}

/* ---------- 정렬 가능한 표 헤더 ---------- */
.data-table.sortable th.sort-th {
  cursor: pointer; user-select: none; white-space: nowrap;
  transition: color 0.15s ease;
}
.data-table.sortable th.sort-th:hover { color: var(--text-primary); }
.data-table.sortable th.sort-th:focus-visible { outline: 1px solid var(--signal); outline-offset: -1px; }
/* 정렬 화살표는 활성 컬럼에만 상시 표시합니다. 나머지 컬럼에 ⇅ 를 늘 띄우면
   9~20개 헤더에 기호가 흩어져 산만해지므로, hover/포커스 때만 드러냅니다.
   (visibility 가 아니라 opacity 라서 자리는 항상 차지 — 폭이 흔들리지 않습니다) */
.sort-arrow { font-size: 9px; opacity: 0; margin-left: 1px; transition: opacity 0.12s ease; }
.data-table.sortable th.sort-th:hover .sort-arrow,
.data-table.sortable th.sort-th:focus-visible .sort-arrow { opacity: 0.45; }
.data-table.sortable th.is-sorted { color: var(--text-primary); }
.data-table.sortable th.is-sorted .sort-arrow { opacity: 1; color: var(--signal); }

.data-table tr.total-row td {
  font-weight: 600;
  border-top: 1px solid var(--border-strong);
  background: var(--bg-surface-alt);
}

@media (max-width: 720px) {
  .scope-field select { min-width: 0; width: 100%; }
  /* 캠페인+광고세트 쌍은 폭이 부족해도 하나의 flex item(.scope-pair)으로
     함께 다음 줄로 내려갈 뿐 안에서 갈라지지 않습니다. */
  .scope-pair { width: 100%; }
  .scope-pair .scope-field--popover { flex: 1 1 0; min-width: 0; }
  .scope-pair .scope-popover-btn { width: 100%; min-width: 0; max-width: none; }
}

/* ==========================================================================
   조회 조건 바 · 캠페인 성과 (통합 대시보드)
   ========================================================================== */
.scope-panel { padding: 14px 20px; }
.scope-panel .scope-grid { align-items: flex-end; }

.scope-note { font-size: 11px; color: var(--warning); padding-bottom: 7px; }
.scope-note a { color: inherit; text-decoration: underline; }

.campaign-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }

/* 캠페인/소재/광고 상세 표 헤더의 로컬 라이브 필터 체크박스 — 상단 조회조건 바의
   "라이브 캠페인만"(.scope-field--check)과 같은 체크박스 언어를 쓰되, 그 클래스는
   .scope-grid(align-items:flex-end) 맥락에 맞춘 값이라 .campaign-toolbar
   (align-items:center)에는 안 맞아 새로 정의합니다. */
.toolbar-check { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-secondary); }
.toolbar-check input { accent-color: var(--signal-solid); }
.toolbar-check[hidden] { display: none; }

.confirm-list {
  margin-top: 10px; display: flex; flex-direction: column; gap: 3px;
  font-size: 12px; color: var(--text-secondary);
}

@media (max-width: 720px) {
  .campaign-toolbar { width: 100%; }
}

/* ---------- 캠페인/광고세트 팝오버 필드 (조회 조건 바) ----------
   지표 선택 리스트박스(.listbox-wrap/.listbox-popover, 487번 줄 부근)를
   재사용하되, .listbox-wrap 의 margin-left:auto(우측 정렬용)는 조회 조건
   바에서는 필요 없어 여기서 새 래퍼로 대체합니다. */
.scope-pair { display: flex; flex-wrap: nowrap; align-items: flex-end; gap: 10px 14px; }
.scope-field--popover { min-width: 0; }
.scope-popover-wrap { position: relative; }
.scope-popover-btn {
  height: 36px; padding: 0 10px; min-width: 132px; max-width: 220px;
  border: 1px solid var(--border-strong); border-radius: var(--radius-control);
  background: var(--bg-inset); color: var(--text-primary);
  font-size: 12px; text-align: left;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  cursor: pointer;
}
.scope-popover-btn:hover { background: var(--bg-surface-alt); }
.scope-popover-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.scope-popover { left: 0; right: auto; min-width: 220px; width: max-content; max-width: 280px; }
.listbox-row--empty { color: var(--text-muted); cursor: default; }
.listbox-row__sub { color: var(--text-muted); font-size: 11px; }

/* ==========================================================================
   소셜 로그인
   ========================================================================== */

/* 구분선 — "또는" 이 선 위에 떠 있지 않고 선을 끊고 앉게 합니다. */
.social-divider {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 16px 0 12px;
  font-size: 12px;
  color: var(--text-muted);
}
.social-divider::before,
.social-divider::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--hairline);
}

/* 구글 버튼은 흰 바탕 + 회색 테두리가 구글 브랜드 가이드의 기본형입니다.
   다크 테마에서도 이 조합을 유지합니다 — 테마에 맞춰 검게 칠하면 구글 로고가
   떠 보이고, 사용자가 "구글 버튼"으로 못 알아봅니다. */
.btn-social {
  width: 100%;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  border: 1px solid #dadce0;
  border-radius: var(--radius-control);
  background: #ffffff;
  color: #1f1f1f;
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
}
.btn-social:hover { background: #f7f8f8; }
.btn-social:disabled { opacity: 0.55; cursor: not-allowed; }
.btn-social__icon { flex: none; }
#googleError { margin-top: 10px; }

/* ==========================================================================
   회원가입 화면
   ========================================================================== */
.login-card--signup { max-width: 400px; }
.login-card--signup .login-form { gap: 14px; }

/* 고객분류 토글 — 폼 안에서 꽉 채워 두 갈래가 동등하게 보이도록 */
.role-toggle { display: grid; grid-template-columns: 1fr 1fr; gap: 4px; }
.role-toggle .toggle-btn { justify-content: center; text-align: center; }

.login-signup-btn { width: 100%; margin-top: -4px; }
.login-hint code { white-space: nowrap; }
/* 가입 완료 팝업의 라벨/값 한 줄 배치 */
.diff-label { color: var(--text-muted); min-width: 72px; }

/* 거래처 직접 만들기 / 이름 바꾸기 */
.client-create { display: flex; flex-wrap: wrap; gap: 6px; margin: -4px 0 12px; }
.client-create input[type="text"] {
  flex: 1; min-width: 0;
  padding: 7px 10px; font-size: 12px;
  color: var(--text-primary); background: var(--bg-inset);
  border: 1px solid var(--border-strong); border-radius: var(--radius-control);
}
.client-create input[type="text"]:focus { outline: none; border-color: var(--signal); }
.client-row-actions { display: flex; gap: 4px; }
.client-row-actions button {
  border: none; background: none; cursor: pointer; padding: 2px 4px;
  font-size: 11px; color: var(--text-muted); line-height: 1;
}
.client-row-actions button:hover { color: var(--text-primary); }
.client-row-actions button[data-act="del"]:hover { color: var(--negative); }
.client-empty-note { font-size: 12px; color: var(--warning); margin-bottom: 10px; }
.client-empty-note a { color: inherit; text-decoration: underline; }
.client-usage { align-self: center; font-size: 11px; color: var(--text-muted); }
.client-usage--full { color: var(--warning); }
.client-create input[type="text"]:disabled,
.client-create button:disabled { opacity: 0.5; cursor: not-allowed; }

/* ============================================================
   보고서 (report.html)
   일간/주간/월간 보고서를 아코디언으로 쌓습니다. 비교표는 기존
   .data-table 을 그대로 씁니다. 사용자가 쓰는 칸이 없어(전부 자동 생성)
   입력 관련 규칙은 두지 않습니다 — 위너 표·소재 카드·운영방안 전용
   스타일은 이 블록 끝(파일 맨 아래)에 따로 있습니다.
   ============================================================ */
.report-toolbar { display: flex; flex-wrap: wrap; gap: 8px; }
.report-hint { font-size: 12px; color: var(--warning); margin: -6px 0 10px; }
.report-hint:empty { display: none; }

/* "일간/주간/월간 생성" 버튼 클릭 피드백 — 토글이 아니라 순간 액션이므로
   버튼에 "선택됨" 상태를 영구히 남기지 않습니다. animation-fill-mode 를
   지정하지 않아 애니메이션이 끝나면 배경이 자동으로 원래(투명)로 돌아갑니다.
   prefers-reduced-motion 사용자는 전역 규칙(166번 줄)이 지속시간을 0으로
   줄여 깜빡임 없이 바로 원래 상태입니다. */
@keyframes report-btn-flash { from { background: var(--signal-tint); } to { background: transparent; } }
.report-toolbar .btn-flash { animation: report-btn-flash 0.45s ease-out; }
.report-empty { font-size: 12px; color: var(--text-muted); line-height: 1.7; padding: 18px 0; }

.report-list { display: flex; flex-direction: column; gap: 10px; }

.report-item {
  border: 1px solid var(--hairline);
  border-radius: var(--radius-card);
  background: var(--bg-inset);
  overflow: hidden;
}
.report-item.is-open { border-color: var(--border-strong); }

.report-head { display: flex; align-items: center; gap: 8px; padding: 0 12px 0 0; }
.report-toggle {
  flex: 1; min-width: 0;
  display: flex; align-items: center; gap: 10px;
  padding: 12px; border: 0; background: none; cursor: pointer;
  font: inherit; color: var(--text-primary); text-align: left;
}
.report-toggle:hover { background: var(--signal-tint); }
.report-caret { color: var(--text-muted); font-size: 11px; width: 12px; flex: none; }
.report-title { font-size: 13px; font-weight: 600; flex: none; }
.report-scope {
  font-size: 11px; color: var(--text-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.report-actions { display: flex; gap: 6px; flex: none; }

.report-body { padding: 0 12px 14px; }
.report-compare { margin-bottom: 14px; }
.report-compare .data-table { font-size: 12px; }
.delta-up { color: var(--positive); }
.delta-down { color: var(--negative); }
.report-note { font-size: 11px; color: var(--warning); margin: -8px 0 12px; }

/* 지표 비교표 전치 — 지표를 열로, [이번/전기/증감]을 행으로. 지표가 적을 때
   표가 억지로 안 늘어나게 width:auto 로 두고, 지표가 많아지면 .table-scroll
   (부모, 639번 줄)이 가로 스크롤을 만듭니다. 첫 열(행 레이블)은 스크롤해도
   무슨 행인지 보이게 고정합니다. */
.report-compare-table { width: auto; min-width: 100%; }
.report-compare-table th.report-compare__rowhead {
  /* thead 의 코너 셀은 헤더(z-index 2)보다 위에 있어야 가로·세로 스크롤이
     겹치지 않습니다. tbody 의 행 헤더는 본문 셀(1) 위면 충분합니다. */
  position: sticky; left: 0; z-index: 2;
  min-width: 104px; white-space: normal;
  text-align: left; font-weight: 600;
  background: var(--bg-inset); color: var(--text-secondary);
}
.report-compare-table thead th.report-compare__rowhead { z-index: 3; }
.report-compare-table th.report-compare__rowhead small { font-weight: 500; color: var(--text-muted); }
/* 숫자는 앱 전체와 같이 우측정렬 — center 면 자리수가 서로 안 맞습니다 */
.report-compare-table td, .report-compare-table thead th:not(.report-compare__rowhead) {
  min-width: 84px; white-space: nowrap; text-align: right;
}
.report-saved { font-size: 11px; color: var(--positive); min-height: 15px; }

@media (max-width: 640px) {
  /* 좁은 화면에서는 제목 줄과 버튼 줄을 따로 놓습니다. flex: 1 만 두면
     기준 크기가 0이라 버튼 옆에서 40px 로 찌그러집니다 — basis 로 한 줄을
     차지하게 해야 제목·기간이 온전히 보입니다. */
  .report-head { flex-wrap: wrap; padding: 0 12px 10px 0; }
  .report-toggle { flex: 1 0 100%; flex-wrap: wrap; }
  .report-scope { flex: 1 0 100%; padding-left: 22px; }
  .report-actions { flex: 1 0 100%; padding-left: 12px; }
}

/* ============================================================
   위너 판정 · 소재 카드 · 운영방안 (report.html)
   위너 표는 .data-table 위에 순위 배지·점수바·칩을 얹습니다.
   ============================================================ */
.report-anchor { font-size: 12px; color: var(--text-muted); margin: -4px 0 10px; }

.report-section-title {
  font-size: 12px; font-weight: 600; color: var(--text-secondary);
  margin: 18px 0 8px; padding-top: 14px; border-top: 1px solid var(--hairline);
}
/* 인사이트 프레임(페인포인트+운영방안+코멘트) 안의 첫 소제목은 위 여백이 필요 없습니다. */
.report-insight > .report-section-title:first-child { border-top: 0; padding-top: 0; margin-top: 0; }

/* 위너 캠페인/광고세트/소재 — 서로 다른 프레임으로 명확히 구분합니다.
   좌측 색띠는 .report-conf 와 같은 디자인 언어입니다. */
.report-frame {
  border: 1px solid var(--hairline);
  border-radius: var(--radius-card); background: var(--bg-surface);
  padding: 14px 14px 4px; margin: 0 0 16px;
}
.report-frame__title { font-size: 12px; font-weight: 500; margin: 0 0 10px; }
/* 좌측 색띠를 뺐습니다 — 어느 레벨인지는 제목('위너 캠페인' 등)이 이미 말하고,
   띠가 화면마다 세 가지 색으로 깔리면 그쪽이 먼저 눈에 들어옵니다. */
.report-frame--campaign .report-frame__title { color: var(--signal); }
.report-frame--adset .report-frame__title { color: var(--warning); }
.report-frame--ad .report-frame__title { color: var(--positive); }
/* 프레임 안의 winner-group/plan-block 은 자체 마진이 있으니 마지막 요소 여백만 정리 */
.report-frame__body > *:last-child { margin-bottom: 0; }

/* 페인포인트 + 운영방안 + 코멘트 — 하나의 프레임으로 묶습니다 */
.report-insight {
  border: 1px solid var(--hairline); border-radius: var(--radius-card);
  background: var(--bg-surface); padding: 14px 14px 4px; margin: 0 0 16px;
}

/* 캠페인 요약 & 다음 전략 — 보고서 맨 위, 가장 먼저 눈에 띄어야 하는 섹션 */
/* 띠 없이도 맨 위 섹션이라 먼저 읽힙니다. */
.report-summary-list { margin: 0 0 14px; padding-left: 18px; font-size: 12px; line-height: 1.8; color: var(--text-secondary); }
.report-summary-list li + li { margin-top: 4px; }

/* 위너 공통점 분석 — 좋은 점/확인이 더 필요한 점 두 컬럼으로 한눈에 대비되게 */
.commonality-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.commonality-col__title { font-size: 12px; font-weight: 500; margin: 0 0 8px; }
.commonality-col__title--good { color: var(--positive); }
.commonality-col__title--watch { color: var(--warning); }
.commonality-list { margin: 0 0 14px; padding-left: 18px; font-size: 12px; line-height: 1.8; color: var(--text-secondary); }
.commonality-list li + li { margin-top: 4px; }
.commonality-list--good li::marker { color: var(--positive); }
.commonality-list--watch li::marker { color: var(--warning); }
@media (max-width: 720px) {
  .commonality-grid { grid-template-columns: 1fr; gap: 12px; }
}

/* 신뢰도 — 레벨은 "이 보고서의 신뢰도 — 높음" 이라고 본문이 말합니다. */
.report-conf {
  border: 1px solid var(--hairline);
  border-radius: var(--radius-card); padding: 10px 12px; font-size: 12px; background: var(--bg-inset);
}
.report-conf strong { font-size: 12px; }
.report-conf ul { margin: 6px 0 0; padding-left: 18px; color: var(--text-secondary); line-height: 1.7; }


/* 위너 요약 카드 — 그룹당 위너 1명 + 핵심 지표만. 전체 순위(수십~수백 행)는
   기본으로 안 보이게 접어두고, 필요할 때만 펼치는 <details> 안에 둡니다. */
.winner-card-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 10px; margin-bottom: 10px; }
/* 예전엔 모든 카드에 왼쪽 초록 띠가 있었습니다. 위너 카드는 전부 위너라
   그 띠가 아무것도 구분하지 못했고, 화면만 시끄러웠습니다. 뺐습니다.
   반대로 "판정 못 함/대체 기준"은 진짜 신호라 그때만 띠를 남깁니다. */
.winner-card {
  border: 1px solid var(--hairline);
  border-radius: var(--radius-card); background: var(--bg-inset); padding: 12px;
}
/* '판정 못 함/대체 기준'도 본문에 이유가 적혀 있어 띠가 없어도 읽힙니다. */
.winner-card h3 { font-size: 11px; font-weight: 600; color: var(--text-muted); margin: 0 0 6px; }
.winner-card__name {
  font-size: 13px; font-weight: 500; margin-bottom: 8px; line-height: 1.4;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.winner-card__metrics { display: flex; flex-wrap: wrap; gap: 10px 14px; }
.winner-card__metric { display: flex; flex-direction: column; gap: 2px; }
.winner-card__metric-label { font-size: 10px; color: var(--text-muted); }
.winner-card__metric-value { font-size: 13px; font-weight: 600; font-variant-numeric: tabular-nums; }
.winner-card .report-note { margin: 6px 0 0; }
/* 대시보드 베스트 요약(§6) — 베스트 광고세트·소재 카드 전용 추가 요소.
   카드 자체(.winner-card)와 그리드(.winner-card-grid)는 report.html 위너 카드와
   그대로 공유해 새 반응형 규칙을 따로 만들 필요가 없습니다. */
.winner-card__thumb { width: 100%; height: 90px; object-fit: cover; border-radius: 6px; margin-bottom: 8px; display: block; }
.winner-card__sub { font-size: 11px; color: var(--text-secondary); line-height: 1.5; margin: 0; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
/* 코멘트 출처 배지 — 규칙엔진 문장과 모델 문장은 신뢰 수준이 달라서
   읽는 사람이 구분할 수 있어야 합니다. */
.comment-badge {
  display: inline-block; margin-left: 8px; padding: 1px 7px; border-radius: 999px;
  font-size: 10px; font-weight: 600; vertical-align: middle;
  color: var(--text-muted); background: var(--surface-2);
}
/* 채움색이 테마마다 밝기가 반대라(라이트=인디고, 다크=오프화이트) 글자색도
   같이 뒤집혀야 합니다. #fff 고정이면 다크에서 흰 글자가 밝은 배경에 묻힙니다. */
.comment-badge--ai { color: var(--text-on-accent); background: var(--accent); }
.comment-badge--wait { color: var(--text-secondary); }
.comment-rewrite { float: right; }
.winner-card__note { font-size: 10px; color: var(--text-muted); line-height: 1.5; margin: 4px 0 0; }
/* 썸네일을 못 불러온 자리 — 예전처럼 <img> 를 지워버리면 "이미지가 원래 없는
   소재"와 구분이 안 됩니다. 자리를 유지하고 이유를 적습니다. */
.winner-card__thumb-fail {
  height: 90px; border-radius: 6px; margin-bottom: 8px;
  display: flex; align-items: center; justify-content: center; text-align: center;
  padding: 0 10px; font-size: 10px; line-height: 1.4; color: var(--text-muted);
  background: var(--surface-2);
  border: 1px dashed var(--border);
}
/* ---- 대시보드 베스트 요약 (§6 재설계) ----------------------------------
   "이번 기간에 뭐가 제일 잘 됐나"를 타겟·캠페인·소재 세 덩어리로만 답합니다.
   목적군별 비교는 보고서의 위너 표가 이미 하므로 여기서는 중복을 걷어냈습니다. */
/* 블록이 정확히 셋이라 auto-fit 을 쓰면 넓은 화면에서 네 번째 빈 칸이 생깁니다.
   개수가 고정이므로 명시적으로 접습니다. */
.best-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
@media (max-width: 1100px) { .best-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 720px)  { .best-grid { grid-template-columns: minmax(0, 1fr); } }
.best-block {
  border: 1px solid var(--hairline); border-radius: var(--radius-card);
  background: var(--bg-inset); padding: 13px 14px; min-width: 0;
}
.best-block h3 {
  font-size: 11px; font-weight: 600; color: var(--text-muted);
  margin: 0 0 8px; display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap;
}
/* 무슨 기준으로 1위인지 — 기준을 안 밝히면 숫자를 믿을 수 없습니다. */
.best-block__basis { font-size: 10px; font-weight: 500; color: var(--text-muted); opacity: .8; }
.best-block__foot { font-size: 10px; color: var(--text-muted); line-height: 1.5; margin: 8px 0 0; }
.best-headline {
  font-size: 14px; font-weight: 500; line-height: 1.35; margin-bottom: 8px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.best-metrics { display: flex; flex-wrap: wrap; gap: 8px 16px; }
.best-metric { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.best-metric__label { font-size: 10px; color: var(--text-muted); }
.best-metric__value { font-size: 13px; font-weight: 600; font-variant-numeric: tabular-nums; }
.best-metric__rank { font-size: 10px; color: var(--text-muted); opacity: .85; }
.best-metrics--sm .best-metric__value { font-size: 12px; }

/* 보기 전환(교차·성별·연령). 화면 필터가 아니라 같은 데이터를 다르게 묶는 것이라
   탭 모양으로 두고, 눌린 것만 또렷하게 합니다. */
.best-tabs { display: inline-flex; gap: 2px; padding: 2px; margin: 0 0 10px;
  border: 1px solid var(--hairline); border-radius: 999px; background: var(--bg-inset); }
.best-tab {
  border: 0; background: none; cursor: pointer; padding: 3px 11px; border-radius: 999px;
  font-size: 11px; font-weight: 500; color: var(--text-muted); line-height: 1.6;
}
.best-tab:hover { color: var(--text-primary); }
.best-tab.is-on { background: var(--bg-elevated); color: var(--text-primary); font-weight: 600; }
.best-block__sub { font-size: 11px; color: var(--text-secondary); margin: -4px 0 0; }

/* 성별만 도넛입니다 — 조각이 셋이라 "전체 중 몇 %"가 한눈에 들어옵니다.
   연령(7)·교차(최대 21)를 도넛에 넣으면 조각이 얇아져 못 읽습니다. */
.best-donut { display: flex; align-items: center; gap: 12px; margin-top: 10px; }
.best-donut svg { width: 108px; height: 108px; flex: 0 0 108px; }
.best-donut .donut-legend { margin: 0; display: grid; gap: 5px; font-size: 11px; }
.best-donut .donut-legend b { font-variant-numeric: tabular-nums; }

/* 가로 막대 — 세로 막대는 '여성 25-34세' 같은 라벨을 눕혀야 해서 읽기 나쁩니다. */
.best-bars { list-style: none; margin: 10px 0 0; padding: 0; display: grid; gap: 5px; }
.best-bars li { display: grid; grid-template-columns: 82px 1fr 46px; align-items: center; gap: 8px; }
.best-bars__label { font-size: 11px; color: var(--text-secondary); white-space: nowrap; }
.best-bars__track { height: 7px; border-radius: 999px; background: var(--hairline); overflow: hidden; }
.best-bars__track i { display: block; height: 100%; border-radius: 999px; background: var(--text-muted); }
.best-bars .is-top .best-bars__label { color: var(--text-primary); font-weight: 600; }
.best-bars .is-top .best-bars__track i { background: var(--chart-bar, var(--accent)); }
.best-bars__value { font-size: 11px; text-align: right; font-variant-numeric: tabular-nums; color: var(--text-secondary); }

/* 캠페인 → 광고세트 연결 */
.best-chain { display: flex; gap: 7px; margin-top: 11px; padding-top: 11px; border-top: 1px dashed var(--hairline); }
.best-chain__arrow { color: var(--text-muted); font-size: 12px; line-height: 1.4; }
.best-chain__body { min-width: 0; flex: 1; }
.best-chain__name {
  font-size: 12px; font-weight: 600; margin-bottom: 6px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.best-chain__note { font-size: 10px; color: var(--text-muted); margin: 6px 0 0; }

/* 소재 — 썸네일이 곧 그래프입니다. 옆에 붙여 이름·카피와 같이 읽게 합니다. */
.best-creative { display: flex; gap: 11px; align-items: flex-start; }
.best-creative__thumb {
  width: 84px; height: 84px; flex: 0 0 84px;
  object-fit: cover; border-radius: 6px; display: block;
}
.best-creative__thumb--none {
  display: flex; align-items: center; justify-content: center; text-align: center;
  padding: 0 8px; margin: 0; font-size: 10px; line-height: 1.35; color: var(--text-muted);
  background: var(--surface-2);
  border: 1px dashed var(--border);
}
.best-creative__body { min-width: 0; flex: 1; }
.best-creative__copy {
  font-size: 11px; color: var(--text-secondary); line-height: 1.5; margin: 0 0 8px;
  overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}

.winner-summary-commonality { margin-top: 4px; }
.winner-summary-commonality h3 { font-size: 11px; font-weight: 600; color: var(--text-muted); margin: 0 0 6px; }
.winner-summary-commonality .commonality-list { margin: 0; }

/* 전체 순위표 접기/펼치기 — 네이티브 <details> 라 JS 상태 관리가 필요 없습니다. */
.winner-details summary {
  cursor: pointer; font-size: 12px; font-weight: 600; color: var(--text-secondary);
  padding: 4px 0 10px; list-style: none; display: flex; align-items: center; gap: 5px;
}
.winner-details summary::-webkit-details-marker { display: none; }
.winner-details summary::before { content: '▸'; font-size: 10px; color: var(--text-muted); }
.winner-details[open] summary::before { content: '▾'; }
.winner-details summary:hover { color: var(--text-primary); }

/* 위너 표 */
.winner-group { margin-bottom: 16px; }
.winner-group h3 { font-size: 12px; font-weight: 600; margin-bottom: 8px; }
.winner-table td, .winner-table th { white-space: nowrap; }
.winner-table tbody tr.is-winner td { background: var(--positive-tint); }
.rank-badge {
  font-size: 10px; color: var(--text-muted); margin-left: 4px;
  vertical-align: super; font-variant-numeric: tabular-nums;
}
.rank-badge--top { color: var(--positive); font-weight: 500; }
.score-cell { display: flex; align-items: center; gap: 6px; font-variant-numeric: tabular-nums; }
.score-bar { display: inline-block; width: 46px; height: 5px; border-radius: 3px; background: var(--hairline); overflow: hidden; }
.score-bar__fill { display: block; height: 100%; background: var(--signal); border-radius: 3px; }

.chip {
  display: inline-block; font-size: 10px; font-weight: 600; line-height: 1;
  padding: 3px 6px; border-radius: 999px; margin-left: 4px; vertical-align: middle;
  background: var(--bg-inset); color: var(--text-secondary); border: 1px solid var(--hairline);
}
/* .chip 을 겹쳐 써서(.chip.chip--winner) 특이도를 (0,2,0)으로 올립니다.
   그냥 .chip--winner(0,1,0)는 .creative-meta span 같은 컨테이너의
   범용 span 규칙(0,1,1)에 색이 밀립니다 — 배경만 초록이고 글자는 회색으로
   나오던 원인. */
.chip.chip--winner { background: var(--positive-tint); color: var(--positive); border-color: transparent; }
.chip.chip--tie { background: var(--warning-tint); color: var(--warning); border-color: transparent; }
.chip.chip--uncat { background: var(--hairline); color: var(--text-muted); cursor: help; }
.chip.chip--partial { background: var(--signal-tint); color: var(--signal); border-color: transparent; }

/* 소재 카드 */
.creative-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 14px; }
.creative-card {
  border: 1px solid var(--hairline); border-radius: var(--radius-card); overflow: hidden;
  background: var(--bg-inset); display: flex; flex-direction: column;
}
.creative-thumb {
  aspect-ratio: 1 / 1; background: var(--bg-surface-alt);
  display: flex; align-items: center; justify-content: center; overflow: hidden;
}
.creative-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.creative-thumb--none::before {
  content: '이미지 없음'; font-size: 11px; color: var(--text-muted);
}
.creative-meta { padding: 10px 12px 0; display: flex; flex-direction: column; gap: 2px; }
.creative-meta strong { font-size: 13px; }
.creative-meta span { font-size: 11px; color: var(--text-muted); }
.creative-kpis {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(78px, 1fr));
  gap: 6px; padding: 10px 12px; font-size: 11px;
}
.creative-kpis > div { display: flex; flex-direction: column; gap: 2px; color: var(--text-muted); }
.creative-kpis strong { font-size: 12px; color: var(--text-primary); font-variant-numeric: tabular-nums; }
.creative-facts { padding: 0 12px 12px; font-size: 12px; }
.creative-facts dt { font-weight: 600; color: var(--text-secondary); margin-top: 10px; }
.creative-facts dt:first-child { margin-top: 0; }
.creative-facts dd { margin: 3px 0 0; line-height: 1.6; }
.fact-missing { color: var(--text-muted); font-style: normal; }
.fact-source { display: block; font-size: 10px; color: var(--text-muted); margin-top: 2px; }

/* 운영방안 · 페인포인트 */
.plan-block { margin-bottom: 12px; }
.plan-block h3 { font-size: 12px; font-weight: 600; margin-bottom: 6px; }
.plan-evidence { font-size: 12px; line-height: 1.7; color: var(--text-secondary); margin: 0 0 4px; }
.pain-list { margin: 0; padding-left: 18px; font-size: 12px; line-height: 1.8; color: var(--text-secondary); }

.report-comment pre {
  white-space: pre-wrap; word-break: break-word; font: inherit; line-height: 1.75;
  background: var(--bg-surface); border: 1px solid var(--hairline); border-radius: var(--radius-card);
  padding: 12px; font-size: 12px; color: var(--text-secondary);
}

@media (max-width: 900px) {
  .creative-grid { grid-template-columns: 1fr; }
}

/* ==========================================================================
   UTM 생성 (utm.html)
   위→아래 한 줄 흐름입니다: 입력 폼 → 미리보기 → 생성된 링크 → 웹페이지
   미리보기. 입력 폼 안의 필드도 전부 세로로 하나씩 나열합니다(2열 그리드 없음).
   utm_campaign 은 규칙으로 계산되는 값이라 점선 테두리로 "직접 입력" 필드와
   구조적으로 구분합니다(실선=사용자 입력, 점선=계산됨).
   ========================================================================== */
.utm-form-panel form { display: flex; flex-direction: column; gap: 18px; max-width: 480px; }

.utm-section-label {
  font-size: 12px; font-weight: 500;
  color: var(--text-muted); margin: 2px 0 -6px;
}

.utm-field-hint { font-weight: 400; color: var(--text-muted); margin-left: 4px; }

.field select { height: 40px; padding: 0 12px; width: 100%; font-size: 14px; }
.field select:focus { outline: none; box-shadow: 0 0 0 3px var(--signal-tint); border-color: var(--signal); }

/* select + "추가한 값 관리" 버튼 한 줄, 그 아래 펼쳐지는 관리 패널(칩 목록) */
.utm-select-row { display: flex; gap: 8px; align-items: center; }
.utm-select-row select { flex: 1 1 auto; min-width: 0; }
.utm-select-row button { flex: none; white-space: nowrap; }
.utm-select-row button.is-active { background: var(--signal-tint); color: var(--signal); border-color: var(--signal); }

.utm-option-manager {
  margin-top: 8px; padding: 10px 12px;
  display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
  background: var(--bg-inset); border: 1px solid var(--hairline); border-radius: var(--radius-control);
}
.utm-option-manager__empty { font-size: 12px; color: var(--text-muted); }
.utm-option-chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 4px 4px 11px; border-radius: var(--radius-pill);
  background: var(--bg-surface-alt); border: 1px solid var(--border-strong);
  font-size: 12px; color: var(--text-secondary);
}
.utm-option-chip button {
  width: 17px; height: 17px; border: none; border-radius: 50%;
  background: transparent; color: var(--text-muted); cursor: pointer;
  font-size: 12px; line-height: 1; display: flex; align-items: center; justify-content: center;
}
.utm-option-chip button:hover { background: var(--negative-tint); color: var(--negative); }

/* 요구사항: 입력하지 않은 필드는 빨간 글씨로 바로 알립니다. 높이를 미리
   확보해(min-height) 메시지가 나타났다 사라져도 아래 레이아웃이 흔들리지 않습니다. */
.utm-field-error { min-height: 15px; margin: 1px 0 0; font-size: 11px; color: var(--negative); }
.field .is-invalid { border-color: var(--negative); }
.field input.is-invalid:focus, .field select.is-invalid:focus { box-shadow: 0 0 0 3px var(--negative-tint); border-color: var(--negative); }

.utm-campaign-rule {
  border: 1px dashed var(--border-strong); border-radius: var(--radius-card);
  background: var(--bg-inset); padding: 16px 18px 14px;
  display: flex; flex-direction: column; gap: 16px;
}
.utm-campaign-rule__head { display: flex; align-items: center; gap: 8px; margin-bottom: -8px; }
.utm-campaign-rule__head h3 { font-size: 13px; font-weight: 600; color: var(--text-primary); }
.utm-campaign-rule__head h3 em { margin-left: 6px; font-style: normal; font-weight: 500; font-size: 11px; color: var(--text-muted); }
.utm-campaign-rule__formula {
  font-family: var(--font-mono); font-size: 11px; color: var(--text-muted);
  margin: -6px 0 0;
}
.utm-campaign-rule__preview {
  margin: 0; font-size: 12px; color: var(--text-secondary);
}
.utm-campaign-rule__preview code {
  font-family: var(--font-mono); font-size: 12px; color: var(--signal);
  background: var(--signal-tint); border-radius: var(--radius-control); padding: 2px 7px;
}

.btn-primary.is-disabled { opacity: 0.4; cursor: not-allowed; }
.btn-primary.is-disabled:hover { background: var(--text-primary); }

/* ---------- 미리보기 ---------- */
.utm-preview {
  font-family: var(--font-mono); font-size: 12px; line-height: 1.9;
  background: var(--bg-inset); border: 1px solid var(--hairline); border-radius: var(--radius-control);
  padding: 14px; word-break: break-all;
}
.utm-seg-op { color: var(--text-muted); }
.utm-seg--base { color: var(--text-primary); }
.utm-seg { border-radius: 4px; }
.utm-seg.is-ghost { opacity: 0.7; }
.utm-ghost-val { color: var(--text-muted); font-style: italic; }
/* 파라미터별 색 코드 — 매체 칩 팔레트를 재사용해 앱 전체 톤과 일관성을 유지합니다 */
.utm-seg--source, .utm-dot--source { color: var(--platform-meta); }
.utm-seg--medium, .utm-dot--medium { color: var(--platform-google); }
.utm-seg--campaign, .utm-dot--campaign { color: var(--signal); }
.utm-seg--content, .utm-dot--content { color: var(--platform-kakao); }
.utm-seg--term, .utm-dot--term { color: var(--positive); }
.utm-dot--source, .utm-dot--medium, .utm-dot--campaign, .utm-dot--content, .utm-dot--term {
  display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: currentColor;
}

.utm-legend { list-style: none; display: flex; flex-wrap: wrap; gap: 7px 14px; margin: 12px 0 0; padding: 0; font-size: 11px; color: var(--text-muted); }
.utm-legend li { display: inline-flex; align-items: center; gap: 5px; }

/* ---------- 생성된 링크 ---------- */
.utm-result { display: flex; align-items: center; gap: 10px; background: var(--bg-inset); border: 1px solid var(--hairline); border-radius: var(--radius-control); padding: 10px 12px; }
.utm-result code { flex: 1 1 auto; min-width: 0; overflow-wrap: break-word; font-family: var(--font-mono); font-size: 11px; color: var(--text-primary); }
.utm-result-panel .panel-head span { color: var(--positive); }

/* ---------- 웹페이지 미리보기(iframe) ---------- */
.utm-webpreview-note { font-size: 11px; color: var(--text-muted); margin: -8px 0 12px; }
.utm-webpreview-frame {
  border: 1px solid var(--hairline); border-radius: var(--radius-control);
  overflow: hidden; background: var(--bg-inset); height: 70vh; min-height: 420px; max-height: 720px;
}
.utm-webpreview-frame iframe { width: 100%; height: 100%; border: 0; display: block; background: #fff; }

@media (max-width: 640px) {
  .utm-form-panel form { max-width: none; }
}

/* ==========================================================================
   캠페인 업로드 (campaign-builder.html/.js)
   메타 광고관리자와 같은 3계층 탭(캠페인 / 광고세트 / 광고)입니다.
   ========================================================================== */

.cb-gate-banner { background: var(--negative-tint); margin-bottom: 18px; }
.cb-gate-banner p { font-size: 12px; color: var(--text-primary); margin: 0; }
.cb-gate-banner p + p { margin-top: 6px; color: var(--text-secondary); }

/* 컨텍스트 바 — 거래처·광고계정은 어느 탭에도 속하지 않고 늘 보입니다 */
.cb-context-bar { display: flex; flex-wrap: wrap; gap: 14px; padding: 14px 18px; margin-bottom: 14px; }
.cb-context-bar .field { flex: 1 1 220px; min-width: 0; }
.cb-context-bar .form-error { flex: 1 1 100%; margin: 0; }

/* 탭 — 활성 탭의 아래 테두리를 패널 색으로 지워 패널과 한 몸으로 이어 붙입니다.
   .cb-main 의 20px gap 이 탭과 패널 사이에 끼면 이어붙은 모양이 깨지므로
   탭+패널을 .cb-tabwrap 하나로 감싸 gap 이 그 묶음 바깥에만 걸리게 합니다. */
.cb-tabwrap { display: flex; flex-direction: column; min-width: 0; }
/* dub 식 밑줄 탭 — 예전의 "폴더 탭"(활성 탭 아래 테두리를 패널색으로 덮어
   패널과 이어 붙이던 트릭)은 margin-bottom:-1px 에 의존해 깨지기 쉬웠습니다.
   밑줄만 쓰면 그 트릭 없이도 활성 상태가 분명합니다. */
.cb-tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--hairline); }
.cb-tab {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 11px 14px; font-size: 13px; font-weight: 500; color: var(--text-secondary);
  background: transparent; border: 0; border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  transition: color .12s, border-color .12s;
}
.cb-tab:hover { color: var(--text-primary); }
.cb-tab.is-active {
  color: var(--text-primary); font-weight: 600;
  border-bottom-color: var(--text-primary);
}
/* Bolt: 완료 여부만 알려주던 점(dot) 대신 몇 번째 단계인지도 보여주는 번호 배지 —
   3단계(캠페인/광고세트/광고)가 실제 순서가 있는 흐름이라 번호가 정보를 더합니다.
   채워지면 배지 색만 바뀌고(회색→포지티브) 번호·모양은 그대로 유지됩니다. */
.cb-tab__dot {
  width: 18px; height: 18px; border-radius: 50%; background: var(--border-strong); flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 10px; font-weight: 500; color: var(--text-secondary);
}
.cb-tab.is-filled .cb-tab__dot { background: var(--positive); color: var(--text-on-signal); }

.cb-tabpanel { --panel-pad: 24px; padding: 24px 24px 28px; }
.cb-tabpanel > .panel-head:first-child { margin-bottom: 18px; }
/* 탭 안 인접 블록 간격 — 종류(field / 행 / 체크박스 / 드롭존 / 안내문)를 나열하지 않고
   한 규칙으로 둡니다. 소구분선(.cb-subhead)만 자기 간격을 쓰고, 그 바로 뒤 요소는
   구분선이 이미 여백을 주므로 0 입니다. */
.cb-tabpanel > * + *:not(.cb-subhead) { margin-top: 18px; }
.cb-tabpanel > .cb-subhead + * { margin-top: 0; }

/* 한 탭 안에서 소주제를 가르는 얇은 구분선 — 카드를 더 쪼개지 않고 숨만 틔웁니다 */
.cb-subhead {
  display: block; margin: 26px 0 14px; padding-top: 18px; border-top: 1px solid var(--hairline);
  font-size: 12px; font-weight: 500; color: var(--text-muted);
}
.cb-subhead:first-child { margin-top: 0; padding-top: 0; border-top: 0; }

.cb-field-row { display: flex; gap: 12px; }
.cb-field-row .field { flex: 1 1 0; }

.objective-card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 10px; }
.objective-card {
  text-align: left; border: 1px solid var(--hairline); border-radius: var(--radius-control);
  background: var(--bg-surface); padding: 12px 14px; cursor: pointer; transition: box-shadow .12s, border-color .12s;
}
.objective-card:hover { border-color: var(--signal); }
.objective-card.is-active { border-color: var(--signal); background: var(--signal-tint); box-shadow: 0 0 0 1px var(--signal) inset; }
.objective-card strong { display: block; font-size: 13px; font-weight: 600; color: var(--text-primary); }
.objective-card span { display: block; margin-top: 3px; font-size: 11px; color: var(--text-muted); }
/* 2단계(세부 옵션) 중 아직 못 만드는 조합 — 사라지지 않고 비활성+사유로 남습니다 */
.objective-card:disabled, .objective-card.is-disabled {
  cursor: not-allowed; opacity: 0.5; background: var(--bg-inset);
}
.objective-card:disabled:hover, .objective-card.is-disabled:hover { border-color: var(--hairline); }
.objective-card__note { display: block; margin-top: 3px; font-size: 10px; color: var(--negative); }

.cb-checkbox-group { display: flex; flex-wrap: wrap; gap: 8px 16px; }
.cb-checkbox-group label { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-secondary); }
/* width/height 를 안 주면 브라우저 기본 체크박스 크기(맥 사파리 등에서 12.5px
   텍스트보다 눈에 띄게 큼)로 렌더됩니다 — em 이면 상속받은 라벨 폰트 크기를
   그대로 따라가 "글자 크기만큼"이 항상 맞습니다.
   flex:none 이 필요한 이유: 이 input 은 .field 안의 <label> 의 직계 자식이라
   `.field label > input { flex: 1 0 100% }`(특이도 0,1,2 — 여기보다 높음)에도
   걸립니다. flex-basis:100%+grow:1 이 이 체크박스를 한 줄 전체로 늘려버려서
   width:1em 을 줘도 시각적으로는 그대로 커 보이던 원인입니다 — 이 규칙 하나로
   그 stretch 를 끊어야 위 width/height 가 실제로 적용됩니다. 특이도도
   input[type="checkbox"] 로 (0,2,1) 까지 올려 확실히 이깁니다. */
.cb-checkbox-group input[type="checkbox"] { accent-color: var(--signal-solid); width: 1em; height: 1em; flex: none; }

.cb-flex-group {
  border: 1px solid var(--hairline); border-radius: var(--radius-control); padding: 10px 12px; margin-bottom: 8px;
}
.cb-flex-group__head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.cb-flex-group__head span { font-size: 11px; color: var(--text-muted); }
.cb-flex-group__chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; min-height: 24px; }
.cb-flex-group__add { display: flex; gap: 6px; }
.cb-flex-group__add input { flex: 1 1 auto; }
.chip--removable { display: inline-flex; align-items: center; gap: 6px; }
.chip--removable button {
  border: 0; background: none; color: inherit; cursor: pointer; font-size: 13px; line-height: 1; padding: 0;
}

.cb-dropzone {
  border: 1.5px dashed var(--border-strong); border-radius: var(--radius-control); padding: 22px;
  text-align: center; cursor: pointer; color: var(--text-muted); font-size: 12px; margin-bottom: 12px;
}
.cb-dropzone:hover, .cb-dropzone.is-dragover { border-color: var(--signal); color: var(--text-secondary); background: var(--signal-tint); }
.cb-asset-grid { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 14px; }
.cb-asset-card { position: relative; width: 96px; border: 1px solid var(--hairline); border-radius: var(--radius-control); overflow: hidden; background: var(--bg-inset); }
.cb-asset-card img { width: 100%; height: 72px; object-fit: cover; display: block; }
.cb-asset-card__status { font-size: 10px; text-align: center; padding: 4px; color: var(--text-muted); }
.cb-asset-card.is-ready .cb-asset-card__status { color: var(--positive); }
.cb-asset-card.is-error .cb-asset-card__status { color: var(--negative); }
.cb-asset-card__remove {
  position: absolute; top: 3px; right: 3px; width: 18px; height: 18px; line-height: 16px;
  border-radius: 50%; border: none; background: rgba(0,0,0,.6); color: #fff;
  font-size: 12px; cursor: pointer; padding: 0;
}
.cb-asset-card__remove:hover { background: var(--negative); }

/* 헤드라인/설명/링크/CTA — 한 줄로 정렬(§3) */
.cb-field-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; }
.cb-field-grid .field { min-width: 0; }
.cb-char-count { display: block; margin-top: 4px; text-align: right; }
.cb-char-count.is-over { color: var(--warning); }

/* 계정에 이미 있는 오디언스 — 포함/제외 체크박스 목록 */
.cb-audience-list { max-height: 160px; overflow-y: auto; border: 1px solid var(--hairline); border-radius: var(--radius-control); padding: 6px 10px; }
.cb-audience-list label { display: flex; align-items: baseline; gap: 6px; font-size: 12px; padding: 4px 0; color: var(--text-secondary); }
.cb-audience-list label span.cb-audience-size { margin-left: auto; color: var(--text-muted); font-size: 10px; font-variant-numeric: tabular-nums; }
/* [type="checkbox"] 로 특이도를 올려야 `.field label > input`(0,1,2)의
   flex:1 0 100% 를 확실히 이깁니다 — .cb-checkbox-group 과 같은 이유,
   위 주석 참고. */
.cb-audience-list input[type="checkbox"] { accent-color: var(--signal-solid); width: 1em; height: 1em; flex: none; }
.cb-audience-list input:disabled + span { color: var(--text-muted); text-decoration: line-through; }

/* 검증 상태 신호등 — 상태 하나만 보이고, 코드/원문은 "자세히 보기"를 펼쳐야 */
.cb-signal { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.cb-signal__badge { font-size: 12px; }

.cb-cta-advanced { margin-top: 8px; }
.cb-cta-advanced summary { cursor: pointer; font-size: 12px; color: var(--text-muted); }
.cb-cta-advanced input, .cb-cta-advanced pre { margin-top: 8px; }
.cb-cta-advanced pre {
  background: var(--bg-inset); border: 1px solid var(--hairline); border-radius: var(--radius-control);
  padding: 10px; font-size: 11px; overflow-x: auto; max-height: 320px; overflow-y: auto;
}

.cb-validation { margin-bottom: 14px; }
.cb-validation-row { display: flex; align-items: center; gap: 8px; font-size: 12px; padding: 4px 0; }
.cb-validation-row .cb-validation-icon { font-weight: 500; }
.cb-validation-row.is-ok .cb-validation-icon { color: var(--positive); }
.cb-validation-row.is-error .cb-validation-icon { color: var(--negative); }

/* ==========================================================================
   캠페인 업로드 — 한 화면 생성기 + 고정 사이드바
   (campaign-builder.html — 예전엔 6단계 위저드였고, 검증이 마지막 단계에서만
   보였습니다. 지금은 본문 전체가 한 화면에 있고, 사이드바가 계정 정보·실시간
   미리보기·검증 상태를 항상 띄웁니다.)
   ========================================================================== */
.cb-layout { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: 20px; align-items: start; }
.cb-main { display: flex; flex-direction: column; gap: 20px; min-width: 0; }
.cb-side { position: sticky; top: 20px; display: flex; flex-direction: column; gap: 16px; }
.cb-side-card { --panel-pad: 16px; padding: 16px; }
.cb-side-card .panel-head { margin-bottom: 10px; }

/* 광고 계정 정보 카드 — 실측 API 값(최소 예산 등)을 그대로 보여줍니다 */
.cb-account-info { display: flex; flex-direction: column; gap: 6px; }
.cb-account-info__row { display: flex; justify-content: space-between; gap: 10px; font-size: 12px; }
.cb-account-info__row dt { color: var(--text-muted); }
.cb-account-info__row dd { color: var(--text-primary); font-variant-numeric: tabular-nums; text-align: right; }

/* 예산 최소치 안내/에러 — Meta 왕복 전에 화면에서 막습니다(code 100/2446375 대응) */
.field-hint.is-error { color: var(--negative); font-weight: 500; }
.field-hint.is-ok { color: var(--positive); font-weight: 500; }

/* 마지막 자동 갱신 시각 — 재수집 버튼 옆에 조용히 붙습니다 */
.sync-stamp {
  font-size: 11px;
  color: var(--text-muted);
  white-space: nowrap;
  margin-right: 2px;
}
.sync-stamp.is-running { color: var(--text-secondary); }


/* 입력 + 버튼을 한 줄에 — 사업자번호 중복확인 */
.inline-row { display: flex; gap: 8px; align-items: stretch; }
.inline-row input { flex: 1; min-width: 0; }
.inline-row .btn-sm { flex: 0 0 auto; white-space: nowrap; }

/* 권한 선택 — 라디오를 목록 형태로 */
.grade-list { display: flex; flex-direction: column; gap: 8px; }
.grade-opt {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  padding: 10px 12px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-control);
  cursor: pointer;
  font-size: 13px;
  line-height: 1.45;
}
.grade-opt:hover { border-color: var(--border-strong); }
.grade-opt input { margin-top: 2px; flex: 0 0 auto; }
.grade-opt strong { font-weight: 600; }

input.is-invalid { border-color: var(--negative) !important; box-shadow: 0 0 0 1px var(--negative) inset; }

/* 실시간 광고 미리보기 — Meta 피드형 광고를 흉내낸 목업. 실제 렌더링 결과를
   보장하진 않지만(플랫폼마다 다름), "지금 뭘 만들고 있는지" 감을 주는 용도입니다. */
.cb-ad-mock {
  border: 1px solid var(--hairline); border-radius: var(--radius-card);
  background: var(--bg-inset); overflow: hidden; font-size: 12px;
}
.cb-ad-mock__head { display: flex; align-items: center; gap: 8px; padding: 10px 12px 6px; }
.cb-ad-mock__avatar {
  width: 28px; height: 28px; border-radius: 50%; flex: none;
  background: var(--signal-tint); color: var(--signal); font-weight: 500;
  display: flex; align-items: center; justify-content: center; font-size: 13px;
}
.cb-ad-mock__who { display: flex; flex-direction: column; line-height: 1.3; min-width: 0; }
.cb-ad-mock__who strong { font-size: 12px; color: var(--text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cb-ad-mock__who span { font-size: 10px; color: var(--text-muted); }
.cb-ad-mock__text { padding: 0 12px 10px; color: var(--text-secondary); white-space: pre-wrap; word-break: break-word; }
.cb-ad-mock__media {
  aspect-ratio: 1 / 1; background: var(--bg-surface-alt);
  display: flex; align-items: center; justify-content: center;
  color: var(--text-muted); font-size: 11px; text-align: center; padding: 12px;
}
.cb-ad-mock__media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.cb-ad-mock__link { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 10px 12px; background: var(--bg-surface-alt); }
.cb-ad-mock__link-text { min-width: 0; }
.cb-ad-mock__domain { display: block; font-size: 10px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.02em; }
.cb-ad-mock__link-text strong { display: block; font-size: 12px; color: var(--text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cb-ad-mock__cta {
  flex: none; padding: 6px 10px; border-radius: var(--radius-control);
  background: var(--bg-inset); border: 1px solid var(--hairline);
  font-size: 11px; font-weight: 600; color: var(--text-primary); white-space: nowrap;
}

.cb-upload-card { display: flex; flex-direction: column; gap: 10px; }
.cb-upload-card .btn-primary { width: 100%; }

@media (max-width: 980px) {
  .cb-layout { grid-template-columns: 1fr; }
  .cb-side { position: static; }
}

/* 진행모달 확장 상태 — 기존 is-running/is-done/is-failed 옆에 3개 추가 */
.progress-step.is-rolling-back { color: var(--warning); }
.progress-step.is-rolling-back .progress-step__mark { border-color: var(--warning); }
.progress-step.is-rolled-back { color: var(--text-muted); }
.progress-step.is-rolled-back .progress-step__mark { border-color: var(--text-muted); color: var(--text-muted); }
.progress-step.is-rollback-failed { color: var(--negative); }
.progress-step.is-rollback-failed .progress-step__mark { border-color: var(--negative); color: var(--negative); background: var(--negative-tint); }

.cb-rollback-banner {
  background: var(--negative-tint); border-radius: var(--radius-control);
  padding: 10px 12px; margin-top: 12px; font-size: 12px; color: var(--text-primary);
}
.cb-rollback-banner a { color: var(--signal); }

/* 심사 상태 pill (11_Meta_Campaign_Upload_Spec.md §7) — 심사중=주황+펄스,
   게재중=초록, OFF=회색, 승인거부/문제=빨강(--error 는 위 연동 배지와 공용).
   펄스를 심사중에만 쓰는 이유: "우리가 기다리는 중"인 유일한 상태라서입니다. */
.status-badge--pending { color: var(--warning); background: var(--warning-tint); animation: cb-pulse 1.6s ease-in-out infinite; }
.status-badge--active { color: var(--positive); background: var(--positive-tint); }
.status-badge--idle { color: var(--text-muted); background: var(--bg-inset); }
@keyframes cb-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.55; } }

/* 업로드 내역 — 감사로그를 commit 단위로 묶어 보여주는 목록 */
.cb-history__row { border-top: 1px solid var(--hairline); padding: 10px 0; }
.cb-history__row:first-child { border-top: 0; }
.cb-history__head { display: flex; align-items: center; gap: 8px; font-size: 12px; font-weight: 600; }
/* 계정이름·예산·시각 — 오브젝트 ID 나열 대신 이 세 가지만(§5) */
.cb-history__meta { display: flex; flex-wrap: wrap; gap: 4px 14px; margin-top: 4px; font-size: 11px; color: var(--text-muted); }

.cb-unresolved {
  background: var(--negative-tint); border-radius: var(--radius-control);
  padding: 10px 12px; margin: 10px 0 14px; font-size: 12px; color: var(--text-primary);
}
.cb-unresolved a { color: var(--signal); }
.cb-unresolved__row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 8px; }
.cb-unresolved__obj { font-size: 11px; }
.cb-unresolved__obj code { font-family: var(--font-mono); }

/* ==========================================================================
   타겟 매트릭스 · 시간대별 히트맵 (dashboard.html — app.js renderDemoMatrix/renderHourlyHeatmap)
   ========================================================================== */

.demo-matrix-table th, .demo-matrix-table td { text-align: right; white-space: nowrap; }
/* 표시 지표를 여러 개 켜면 컬럼이 늘어나 가로 스크롤이 길어집니다 — 연령 라벨
   열은 고정해 어느 행인지 스크롤 중에도 계속 보이게 합니다(.data-table 의
   첫 열 sticky 패턴과 동일). */
.demo-matrix-table .demo-row-label { text-align: left; font-weight: 500; color: var(--text-secondary); position: sticky; left: 0; background: var(--bg-surface); z-index: 1; }
.demo-matrix-table thead tr:first-child th { border-bottom: 1px solid var(--hairline); }
.demo-cell-empty { color: var(--text-muted); }
.demo-cell--best { color: var(--positive); font-weight: 600; background: var(--positive-tint); }

.heatmap-table { border-collapse: separate; border-spacing: 2px; }
.heatmap-table th { font-size: 10px; font-weight: 500; color: var(--text-muted); text-align: center; }
.heatmap-table th.heatmap-hour--major { color: var(--text-secondary); font-weight: 500; }
.heatmap-table th.heatmap-hour--minor { opacity: 0.55; }
.heatmap-row-label { font-size: 11px; color: var(--text-secondary); white-space: nowrap; padding-right: 8px !important; }
/* 5단계 이산 색상 등급 — 값이 있는 시간대는 전부 최소 레벨 1부터 시작합니다(0단계는
   존재하지 않음, hourlyHeatLevel() 참고). 히트맵 셀과 범례 스와치가 같은 [data-heat]
   값으로 같은 색을 씁니다. 색은 --positive(앱 전체에서 "최고 성과 강조"에 쓰는 초록
   톤, 예: .demo-cell--best)를 재사용합니다 — "초록=활발함"이라는 의미를 히트맵에도
   그대로 씁니다. 믹스 비율은 20%가 아니라 35%부터 시작해
   레벨 1이 배경(--bg-inset)과 확실히 구분되게 합니다 — 옅은 라이트 테마 배경에서 저강도
   구간이 "빈 칸"처럼 보이던 문제(사용자 확인)를 막기 위함입니다. */
.heatmap-cell, .heatmap-legend__step {
  background: var(--bg-inset);
}
.heatmap-cell[data-heat="1"], .heatmap-legend__step[data-heat="1"] { background: color-mix(in srgb, var(--positive) 35%, var(--bg-inset)); }
.heatmap-cell[data-heat="2"], .heatmap-legend__step[data-heat="2"] { background: color-mix(in srgb, var(--positive) 50%, var(--bg-inset)); }
.heatmap-cell[data-heat="3"], .heatmap-legend__step[data-heat="3"] { background: color-mix(in srgb, var(--positive) 65%, var(--bg-inset)); }
.heatmap-cell[data-heat="4"], .heatmap-legend__step[data-heat="4"] { background: color-mix(in srgb, var(--positive) 82%, var(--bg-inset)); }
.heatmap-cell[data-heat="5"], .heatmap-legend__step[data-heat="5"] { background: var(--positive); }
/* 그 날짜 전체(24시간)에 수집된 데이터가 없는 행 — data-heat 자체를 안 줘서 위 5단계
   중 어디에도 안 걸립니다. 광고가 아직 라이브되지 않은 날을 "실적 0(옅은 초록)"과
   구분하려는 것이라, 기본 .heatmap-cell 배경(--bg-inset)보다도 더 옅게 지웁니다. */
.heatmap-cell--nodata { background: transparent; opacity: 0.5; }
.heatmap-cell {
  width: 14px; height: 18px; padding: 0 !important; border-radius: 2px;
}

/* 시간대별 히트맵 범례 — 지표명 + 최소값 + 5단계 그라데이션 막대 + 최대값 */
.heatmap-legend { display: flex; align-items: center; gap: 8px; margin-top: 10px; font-size: 11px; color: var(--text-muted); }
.heatmap-legend__label { font-weight: 600; color: var(--text-secondary); margin-right: 2px; }
.heatmap-legend__scale { display: flex; gap: 2px; }
.heatmap-legend__step { width: 18px; height: 10px; border-radius: 2px; }
.heatmap-legend__value { font-variant-numeric: tabular-nums; }

/* 시간대별 성과 — 날짜 탭(하루 골라서 막대그래프로 보기) */
.hourly-date-tabs { display: flex; flex-wrap: wrap; gap: 6px; margin: 4px 0 14px; }
.hourly-date-tabs .toggle-btn { font-size: 11px; padding: 5px 10px; }

/* 타겟분석 도넛차트 + 범례 */
.chart-wrap--donut { display: flex; align-items: center; gap: 18px; }
.chart-wrap--donut svg { flex: 0 0 auto; max-width: 180px; }
.donut-legend { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; font-size: 12px; color: var(--text-secondary); }
.donut-legend li { display: flex; align-items: center; gap: 7px; }
.donut-legend .legend-swatch { width: 10px; height: 10px; border-radius: 3px; }

/* 일별 추이 — 일예산 기준선 범례 스와치(대시 선) */
.legend-swatch--budget { border-radius: 0; height: 2px; width: 12px; background: var(--warning); align-self: center; }
