﻿/* ╔══════════════════════════════════════════════════════════════╗
   ║  EYESMAG ANALYTICS — DESIGN SYSTEM  (B&W Edition)          ║
   ╚══════════════════════════════════════════════════════════════╝ */

/* ── FONTS ── */
/* Pretendard Variable — 파일 하나로 웨이트 100~900을 전부 커버한다.
   교체 전에는 'Apple SD Gothic Neo' 원본 TTF 9개가 33.0MB였고, 그중 400·500·600
   3개(11.35MB)는 `font-display: block`이라 폰트가 도착할 때까지 화면의 모든 글자를
   가리는 표시 방식이었다. Apple 기기 번들 폰트라 웹 셀프호스팅이 라이선스상
   허용되지도 않았다. Pretendard(SIL OFL)는 그 웹 대체용으로 설계되어 자형 골격이 가깝다. */
@font-face {
  font-family: 'Pretendard Variable';
  src: url('/fonts/PretendardVariable.woff2') format('woff2');
  font-weight: 45 920;
  font-style: normal;
  font-display: swap;
}

/* ── TOKENS ── */
:root {
  /* Brand / Primary — navy */
  --c-primary:       #1F2A44;
  --c-primary-hov:   #25324F;
  --c-primary-light: #F0F2F7;
  --c-primary-ring:  rgba(31,42,68,0.16);

  /* Surface */
  --c-bg:      #F7F8FA;
  --c-surface: #FFFFFF;
  --c-border:  #E5E7EB;
  --c-divide:  #EEF1F4;

  /* Text */
  --c-text-1: #111827;
  --c-text-2: #374151;
  --c-text-3: #6B7280;
  --c-text-4: #9CA3AF;

  /* Sidebar */
  --c-sb-bg:     #0F1115;
  --c-sb-hov:    rgba(255,255,255,0.055);
  --c-sb-act:    rgba(255,255,255,0.09);
  --c-sb-bdr:    rgba(255,255,255,0.07);
  --c-sb-text:   rgba(255,255,255,0.5);
  --c-sb-text-a: #FFFFFF;

  /* Status */
  --c-success:      #22C55E;
  --c-danger:       #EF4444;
  --c-info:         #3B82F6;
  --c-info-bg:      #EFF6FF;
  --c-info-border:  #BFDBFE;
  --c-info-text:    #1D4ED8;
  --c-danger-bg:    #FEF2F2;
  --c-danger-bdr:   #FECACA;
  --c-danger-text:  #DC2626;

  /* Score card accent colors */
  --c-score-a: #111827;
  --c-score-b: #374151;

  /* Typography */
  --f:      'Pretendard Variable', -apple-system, 'Malgun Gothic', sans-serif;
  /* Pretendard를 맨 앞에 둔다. 예전엔 위 @font-face로 자체 호스팅하던 한글 시스템
     서체가 맨 앞이었는데, 그 서체는 한글뿐 아니라 라틴·숫자까지 다 커버하는
     비율폭(proportional) 서체라 이 토큰을 쓰는 이 파일 전역 ~30곳(.data-table th, .scorecard__value 등)이
     실제로는 한 번도 모노스페이스로 렌더링된 적이 없다 — 숫자 정렬은
     font-variant-numeric:tabular-nums(:1266-1267 등)가 해온 일이다. 순서를 바꿔
     진짜 모노 서체를 앞세우면 그 ~30곳이 조용히 진짜 모노스페이스로 바뀌는 시각적
     변경이 되는데, 이번 폰트 교체 작업 범위에는 그런 디자인 변경 권한이 없다.
     Pretendard를 맨 앞에 두면 예전 렌더링을 그대로 재현하면서 한글 글리프 공백도 메운다. */
  --f-mono: 'Pretendard Variable', 'SF Mono', 'Courier New', monospace;

  /* Spacing */
  --sp-1:4px; --sp-2:8px; --sp-3:12px; --sp-4:16px;
  --sp-5:20px; --sp-6:24px; --sp-8:32px; --sp-10:40px;
  --sp-12:48px; --sp-16:64px;

  /* Radius */
  --r-xs:6px; --r-sm:8px; --r-md:12px; --r-lg:16px; --r-xl:20px; --r-2xl:24px;

  /* Shadows */
  --sh-xs:  0 1px 2px rgba(0,0,0,0.04);
  --sh-sm:  0 1px 4px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04);
  --sh-md:  0 4px 16px rgba(0,0,0,0.08), 0 2px 4px rgba(0,0,0,0.04);
  --sh-lg:  0 12px 36px rgba(0,0,0,0.10), 0 4px 10px rgba(0,0,0,0.05);
  --sh-hov: 0 20px 50px rgba(0,0,0,0.12), 0 8px 18px rgba(0,0,0,0.07);

  /* Easing */
  --ease:   cubic-bezier(0.16,1,0.3,1);
  --ease-s: cubic-bezier(0.34,1.56,0.64,1);
  --t-f:120ms; --t-n:200ms; --t-s:320ms;

  /* Layout */
  --sb-w:         260px;
  --sb-w-col:      60px;
  --topbar-h:      54px;
  --appbar-h:      56px;
  --content-max: 1200px;
}

/* ── RESET ── */
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
/* scrollbar-gutter: 스크롤바 자리를 늘 비워 둔다.
   없으면 내용이 늘어 세로 스크롤바가 «처음 생기는» 순간 뷰포트가 그만큼(윈도우
   크롬 기준 15px) 좁아져 화면 전체가 왼쪽으로 움찔한다. 작성자 지정에서 본문 +를
   누를 때 실측으로 패널 오른쪽이 1388 → 1373으로 밀렸다.

   한 페이지에만 걸면 페이지를 옮길 때 같은 현상이 난다 — 문서 전체에 건다.
   지원하지 않는 브라우저는 이 줄을 무시할 뿐 기존 동작 그대로다(퇴행 없음).
   헤드리스 크롬은 오버레이 스크롤바라 이 문제가 재현되지 않는다 — 실제 창으로 확인했다. */
html{scroll-behavior:smooth;scrollbar-gutter:stable;}

body {
  font-family: var(--f);
  font-size: 14px;
  line-height: 1.6;
  color: var(--c-text-1);
  background: var(--c-bg);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

.sr-only {
  position:absolute;width:1px;height:1px;
  padding:0;margin:-1px;overflow:hidden;
  clip:rect(0,0,0,0);white-space:nowrap;border:0;
}

:focus-visible {
  outline: 2px solid var(--c-primary);
  outline-offset: 2px;
  border-radius: var(--r-xs);
}

@media (prefers-reduced-motion:reduce) {
  *,*::before,*::after {
    animation-duration:0.01ms!important;
    transition-duration:0.01ms!important;
  }
}

/* ══════════════════════════════════════════════════════════════
   LAYOUT
══════════════════════════════════════════════════════════════ */
.layout { display:flex; min-height:100vh; }

/* ── SIDEBAR ── */
.sidebar {
  width: var(--sb-w);
  flex-shrink: 0;
  background: var(--c-sb-bg);
  position: fixed;
  inset: 0 auto 0 0;
  z-index: 300;
  border-right: 1px solid var(--c-sb-bdr);
  transition: width var(--t-n) var(--ease);
  overflow: hidden;
}

.sidebar-inner {
  display: flex;
  flex-direction: column;
  height: 100%;
  /* 위쪽 패딩을 없애 .sidebar-brand가 사이드바 맨 위(y=0)에서 시작하게 한다 — 그래야
     .sidebar-brand와 .appbar가 같은 --appbar-h를 써도(각자 위쪽 여백이 다르면 높이가
     같아도 아래 테두리 y가 어긋난다) 두 테두리가 같은 y에서 한 줄로 이어진다.
     하단 16px은 그대로 둬 footer 위 여백을 유지한다. */
  padding: 0 0 16px;
  width: var(--sb-w);   /* keep inner fixed so content doesn't wrap */
  overflow: hidden;
}

/* Collapse toggle */
.sb-collapse-btn {
  position: absolute;
  top: 16px;
  right: 12px;
  width: 24px;
  height: 24px;
  border-radius: var(--r-sm);
  background: rgba(255,255,255,0.06);
  border: 1px solid rgba(255,255,255,0.08);
  color: rgba(255,255,255,0.4);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background var(--t-f), color var(--t-f);
  z-index: 10;
  flex-shrink: 0;
}
.sb-collapse-btn:hover { background:rgba(255,255,255,0.1); color:rgba(255,255,255,0.8); }
.sb-collapse-btn svg { transition: transform var(--t-n) var(--ease); }

/* Brand / Logo */
.sidebar-brand {
  /* 헤더 단(.appbar)과 같은 변수를 써야 아래 테두리가 한 줄로 이어진다.
     각자 상수를 박으면 로고 크기가 바뀔 때 어긋난다. */
  height: var(--appbar-h);
  display: flex;
  align-items: center;
  padding: 0 14px;
  border-bottom: 1px solid var(--c-sb-bdr);
  margin-bottom: 16px;
}

/* Collapsed state */
.sb-collapsed .sidebar { width: var(--sb-w-col); }
.sb-collapsed .layout-main { margin-left: var(--sb-w-col); }
/* .appbar는 이제 position:fixed로 .layout-main 밖에서 직접 sb-w를 참조한다(:356) —
   .progress-line(:694)과 같은 이유로 여기서도 접힘 값을 따로 맞춰야 한다. */
.sb-collapsed .appbar { left: var(--sb-w-col); }
.sb-collapsed .sb-collapse-btn svg { transform: rotate(180deg); }
.sb-collapsed .sidebar-item__label,
.sb-collapsed .sidebar-badge,
.sb-collapsed .sidebar-soon,
.sb-collapsed .sidebar-section-label,
.sb-collapsed .sidebar-footer-text { opacity: 0; pointer-events: none; }

.sb-collapsed .sidebar-logo-full { display: none; }
.sb-collapsed .sidebar-logo-icon { display: flex; }
.sb-collapsed .sidebar-brand { justify-content: center; }
.sb-collapsed .sidebar-item { justify-content: center; padding: 10px; }
.sb-collapsed .sidebar-nav { padding: 0 8px; }

.sidebar-home-btn {
  display: flex;
  align-items: center;
  background: none;
  border: none;
  cursor: pointer;
  padding: 4px 2px;
  border-radius: var(--r-sm);
  width: 100%;
  transition: opacity var(--t-f);
}
.sidebar-home-btn:hover { opacity: 0.8; }

.sidebar-logo-full {
  height: 16px;
  width: auto;
  max-width: 160px;
  object-fit: contain;
  display: block;
  filter: brightness(0) invert(1);
  transition: opacity var(--t-n);
}
.sidebar-logo-icon {
  display: none;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

/* Nav */
.sidebar-nav { flex: 1; padding: 0 10px; }

.sidebar-section-label {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: rgba(255,255,255,0.22);
  padding: 0 8px;
  margin-bottom: 5px;
  transition: opacity var(--t-n);
}

.sidebar-menu { list-style: none; }
.sidebar-menu li { margin-bottom: 2px; }

.sidebar-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 10px;
  border-radius: var(--r-md);
  text-decoration: none;
  color: var(--c-sb-text);
  font-size: 13.5px;
  font-weight: 500;
  cursor: pointer;
  transition: background var(--t-f), color var(--t-f), padding var(--t-n), justify-content var(--t-n);
  position: relative;
  white-space: nowrap;
}
.sidebar-item:hover:not(.is-disabled) { background:var(--c-sb-hov); color:rgba(255,255,255,0.8); }
.sidebar-item.is-active { background:var(--c-sb-act); color:var(--c-sb-text-a); }
.sidebar-item.is-disabled { cursor:default; opacity:0.4; }
.sidebar-item__icon { flex-shrink:0; display:flex; align-items:center; justify-content:center; width:18px; height:18px; }
.sidebar-item__label { flex:1; transition: opacity var(--t-n); }

/* 관리자 전용 메뉴 — 역할은 nav.js가 <head>에서 <html data-rank>에 실어준다.
   인라인 style을 JS로 껐다 켜면 인증 응답 전까지 감춰져 페이지 이동 때마다 깜빡인다. */
html:not([data-rank="admin"]) .nav-admin-only { display: none; }
/* 반대 방향 — 비관리자에게만 보인다. 관리자에게는 보일 이유가 없는 안내문에 쓴다
   (예: #dashHomePending "이 탭은 준비 중입니다" — 관리자에게는 같은 탭에 이미
   실제로 동작하는 보관 표가 있다. 2026-08-19 리뷰 Fix 5). [hidden] 가드도 같이
   둔다 — 이 클래스가 붙은 요소가 나중에 hidden 속성으로도 여닫히게 되면 위 줄과
   같은 함정(display가 [hidden]{display:none}을 이긴다)에 빠진다. */
html[data-rank="admin"] .nav-non-admin-only { display: none; }
.nav-non-admin-only[hidden] { display:none!important; }

.sidebar-badge {
  font-family: var(--f-mono);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.05em;
  background: rgba(255,255,255,0.1);
  color: rgba(255,255,255,0.6);
  border: 1px solid rgba(255,255,255,0.12);
  border-radius: var(--r-xs);
  padding: 2px 6px;
  transition: opacity var(--t-n);
}
.sidebar-soon {
  font-size: 10px;
  color: rgba(255,255,255,0.25);
  background: rgba(255,255,255,0.05);
  border-radius: 4px;
  padding: 2px 6px;
  transition: opacity var(--t-n);
}

/* Footer */
.sidebar-footer {
  margin-top: auto;
  padding: 14px 18px 0;
  border-top: 1px solid var(--c-sb-bdr);
  display: flex;
  align-items: center;
  gap: 8px;
}
.sidebar-footer-dot {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--c-success);
  flex-shrink: 0;
  box-shadow: 0 0 6px rgba(34,197,94,0.5);
}
.sidebar-footer-text { font-size: 11px; color: rgba(255,255,255,0.22); font-family: var(--f-mono); transition: opacity var(--t-n); }

/* Overlay */
.sidebar-overlay { position:fixed; inset:0; background:rgba(0,0,0,0.45); z-index:250; backdrop-filter:blur(2px); }
.sidebar-overlay[hidden] { display:none; }

/* ── APPBAR (상단 단) ──
   좌측 사이드바 로고와 같은 높이를 공유해(--appbar-h) 아래 테두리가
   화면을 가로지르는 한 줄이 되게 한다. 계정·알림 컨트롤이 여기 산다.

   position:fixed(2026-08-21, GSAP ScrollSmoother 도입) — 예전엔 position:sticky;
   top:0 이었다. ScrollSmoother는 #smooth-content에 transform을 걸어 스크롤을
   흉내내는데, transform이 걸린 조상은 그 후손의 sticky 기준(containing block)이
   돼버려 기존 sticky가 죽는다(스크롤해도 안 붙고 그냥 같이 흘러간다 — 브라우저
   공통 동작, GSAP 버그 아님). 이 헤더는 흐름상 맨 위에서 시작해 끝까지 top:0에
   붙어 있어야 하는 요소라 "펼침→고정" 전환이 필요 없다 — 처음부터 끝까지
   position:fixed로 둬도 sticky와 시각적으로 100% 동일하다. 그래서 ScrollTrigger
   pin 같은 JS 대체 없이 순수 CSS로 끝난다(ScrollSmoother가 꺼져 있어도 — reduced
   motion·터치·GSAP 로드 실패 — fixed는 transform 유무와 무관하게 항상 똑같이
   동작하므로 폴백에서도 안전하다).

   left를 var(--sb-w)로 줘 고정 사이드바(.sidebar, position:fixed, z-index:300)
   옆에서 시작하게 한다 — 안 하면 fixed 요소는 자기 static position을 기준으로
   삼는데, .layout-main이 이제 실질적으로 빈 상자라 그 기준이 어긋난다.
   .progress-line(:756)이 이미 같은 패턴(left:var(--sb-w))을 쓰고 있다. 접힘
   상태(.sb-collapsed)는 아래 :210 부근에서 var(--sb-w-col)로 오버라이드한다. */
.appbar {
  height: var(--appbar-h);
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 0 24px;
  background: var(--c-bg);
  border-bottom: 1px solid var(--c-border);
  position: fixed;
  top: 0;
  left: var(--sb-w);
  right: 0;
  z-index: 200;
}
.appbar-spacer { flex: 1; }

/* 각 트리거를 감싸 드롭다운의 위치 기준이 된다.
   .appbar 기준으로 잡으면 알림·계정 메뉴가 같은 자리에 겹친다. */
.appbar-slot { position: relative; }

.appbar-btn {
  position: relative;
  display: flex; align-items: center; justify-content: center;
  width: 34px; height: 34px;
  background: none; border: none; cursor: pointer;
  color: var(--c-text-3); border-radius: var(--r-sm);
  transition: background var(--t-f), color var(--t-f);
}
.appbar-btn:hover { background: var(--c-primary-light); color: var(--c-text-1); }
/* 헤더 벨 알림 배지·목록 */
.appbar-bell-badge {
  position: absolute; top: 2px; right: 2px; box-sizing: border-box;
  min-width: 15px; height: 15px; padding: 0 4px;
  background: #dc2626; color: #fff; border-radius: 999px;
  font-size: 9px; font-weight: 700; line-height: 15px; text-align: center;
}
.notif-head { display: flex; align-items: center; justify-content: space-between; }
.notif-readall { background: none; border: none; cursor: pointer; font-size: 11px; color: var(--c-primary); padding: 2px 4px; }
.notif-readall:hover { text-decoration: underline; }
.notif-item { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.notif-msg { font-size: 13px; }
.notif-time { font-size: 11px; color: var(--c-text-3); flex-shrink: 0; }
.notif-item--unread { background: rgba(37,99,235,.06); }
.notif-item--unread .notif-msg { font-weight: 600; }

.appbar-user {
  display: flex; align-items: center; gap: 8px;
  padding: 4px 12px 4px 4px;
  background: none; border: none; cursor: pointer;
  border-radius: 999px;
  transition: background var(--t-f);
}
.appbar-user:hover { background: var(--c-primary-light); }
.appbar-avatar {
  width: 28px; height: 28px; border-radius: 50%;
  background: var(--c-primary); color: #fff;
  display: flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 700; flex-shrink: 0;
}
.appbar-uname { font-size: 12.5px; font-weight: 500; color: var(--c-text-1); }

/* 드롭다운 — 알림과 계정이 같은 껍데기를 쓴다 */
.appbar-menu {
  display: none;
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  min-width: 216px;
  background: var(--c-surface);
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  box-shadow: var(--sh-lg);
  z-index: 400;
  overflow: hidden;
}
.appbar-menu.is-open { display: block; }
.appbar-menu-head { padding: 14px 16px; border-bottom: 1px solid var(--c-divide); }
.appbar-menu-name { font-size: 13.5px; font-weight: 600; color: var(--c-text-1); }
.appbar-menu-rank { font-size: 12px; color: var(--c-text-3); margin-top: 2px; }
.appbar-menu-item {
  display: flex; align-items: center; gap: 12px;
  width: 100%; padding: 11px 16px;
  background: none; border: none; cursor: pointer;
  font-size: 13px; color: var(--c-text-1); text-align: left;
  text-decoration: none;
  transition: background var(--t-f);
}
.appbar-menu-item:hover { background: var(--c-primary-light); }
.appbar-menu-item svg { flex-shrink: 0; color: var(--c-text-3); }
.appbar-menu-empty { padding: 22px 16px; font-size: 12.5px; color: var(--c-text-3); text-align: center; }

@media (max-width: 767px) {
  .appbar { padding: 0 14px; }
  .appbar-uname { display: none; }
  /* .appbar를 .topbar 줄로 끌어올려 헤더를 한 줄로 만드는 규칙은 파일 끝
     MOBILE PASS 블록에 있다 — 예전엔 여기서 position:static으로 풀어 두 줄로
     흘려보냈다. 같은 셀렉터를 두 곳에서 다루면 어느 쪽이 이기는지 헷갈리므로
     이 자리에는 남기지 않는다. */
}

/* ── MAIN ── */
/* margin-left는 ScrollSmoother가 꺼져 있을 때(reduced motion·터치·GSAP 로드 실패)의
   폴백 배치다 — 그때는 #smooth-wrapper/#smooth-content가 평범한 in-flow 블록이라
   .layout-main의 margin-left가 그대로 오른쪽으로 밀어준다. ScrollSmoother가 실제로
   켜지면(html.ey-smooth-active, smooth-scroll.js) #smooth-wrapper가 GSAP JS에 의해
   position:fixed;inset:0으로 전체 뷰포트를 뒤덮어 .layout-main의 margin-left는
   더 이상 아무것도 밀어주지 못한다(안에 남는 in-flow 자식이 없다 — .topbar·.appbar는
   이제 fixed) — 그 경우의 오프셋은 아래 #smooth-content 규칙이 padding으로 대신
   맡는다. 두 메커니즘은 동시에 켜지지 않으므로 서로 상쇄되지 않는다. */
.layout-main {
  margin-left: var(--sb-w);
  flex: 1;
  min-width: 0;
  background: var(--c-bg);
  transition: margin-left var(--t-n) var(--ease);
}

/* ── SMOOTH SCROLL (GSAP ScrollSmoother, 2026-08-21) ──
   #smooth-wrapper > #smooth-content가 <main>을 감싼다(마크업은 6개 페이지 동일,
   smooth-scroll.js가 초기화). 여기 규칙은 "GSAP이 아직 안 왔거나 안 켜졌을 때도
   이 두 div가 화면을 깨뜨리지 않는" 폴백이다 — ScrollSmoother.create()가 실제로
   돌면 GSAP가 #smooth-wrapper에 직접 인라인 스타일(position:fixed 등)을 얹어
   이 규칙 대부분을 덮어쓴다(ScrollSmoother.js의 wrapper()/content() — 이 저장소
   node_modules/gsap가 아니라 public/vendor/gsap에 있다). 인라인 스타일이 항상
   스타일시트를 이기므로 순서를 다툴 필요가 없다.

   padding-top은 조건 없이 항상 켠다 — .appbar/.topbar는 ScrollSmoother 활성 여부와
   무관하게 항상 position:fixed라(:356·:513) 흐름에서 제 높이만큼의 공간을 절대
   차지하지 않는다. <main>(#smooth-content의 자식)이 그 밑에 깔리지 않으려면 이
   padding이 스무딩 on/off와 무관하게 항상 있어야 한다.

   처음엔 이 offset을 .main 자신의 padding-top에 더했었는데(calc(var(--appbar-h)+28px))
   틀렸다 — getBoundingClientRect()가 재는 건 .main "자신의" 위치지 자신의 padding이
   아니다. .main의 padding은 그 안의 자식을 밀어낼 뿐, .main 자체를 헤더 밑으로
   내려주지는 않는다. .main이 실제로 헤더 밑에서 시작하려면 .main의 "부모"
   (#smooth-content)가 그만큼 밀어야 한다 — 실측(smoke-mobile-ui.js)에서 본문이
   0px(뷰포트 맨 위)에서 시작해 헤더에 덮이는 것으로 드러난 회귀였다. */
#smooth-content { padding-top: var(--appbar-h); }

/* padding-left는 ScrollSmoother가 "transform으로 실제 스크롤을 대신하는 모드"일
   때만 조건부로 켠다(html.ey-smooth-active, smooth-scroll.js가 붙인다 — 터치는
   smoothTouch:false라 여기 해당 안 됨). 그 모드의 #smooth-wrapper는 GSAP가 강제로
   position:fixed;inset:0(뷰포트 전체)로 만들므로, 사이드바만큼 안으로 들여쓰는
   일을 #smooth-content 자신의 padding이 대신 맡아야 한다(GSAP는 이 요소에
   width:100%·box-sizing:border-box는 얹지만 padding·margin은 건드리지 않는다).
   꺼져 있을 때(터치·reduced-motion·GSAP 로드 실패)는 .layout-main의 margin-left가
   이미 같은 일을 하므로 여기서 또 들여쓰면 두 배로 밀린다 — 그래서 조건부다. */
html.ey-smooth-active #smooth-content {
  padding-left: var(--sb-w);
  min-height: 100vh;
}
html.ey-smooth-active.sb-collapsed #smooth-content { padding-left: var(--sb-w-col); }

/* Mobile topbar — position:fixed는 .appbar와 같은 이유(:356 주석 참고).
   left:0 — 모바일에서는 사이드바가 오프캔버스 서랍이라(:1533 부근) 옆에 비워둘
   폭이 없다. */
.topbar {
  display: none;
  height: var(--topbar-h);
  background: #fff;
  border-bottom: 1px solid var(--c-border);
  padding: 0 16px;
  align-items: center;
  gap: 14px;
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 200;
}
.topbar-toggle { display:flex; flex-direction:column; gap:4.5px; background:none; border:none; cursor:pointer; padding:4px; border-radius:var(--r-xs); }
.topbar-toggle span { display:block; width:18px; height:1.5px; background:var(--c-text-1); border-radius:2px; }
.topbar-brand { display:flex; align-items:center; }
.topbar-logo { height:18px; width:auto; object-fit:contain; display:block; }
.topbar-home-btn { display:flex; align-items:center; }

/* Content */
/* 위쪽 28px — 헤더 단이 생겨 40px이면 본문이 과하게 내려간다. 헤더(--appbar-h)를
   피하는 몫은 더 이상 여기서 더하지 않는다 — #smooth-content의 padding-top이
   맡는다(:503 부근 SMOOTH SCROLL 섹션 주석 참고). 여기서 또 더하면 이중으로
   밀린다. */
.main {
  flex: 1;
  padding: 28px 40px 80px;
  max-width: var(--content-max);
  width: 100%;
  margin: 0 auto;
}

.view { width:100%; }
.view[hidden] { display:none!important; }


/* Anti-flicker: on refresh, the analyzer view is the default markup state
   (no [hidden] attr) and app.js/report.js only hide it after they load and
   run. If the last active section was the report, an inline script in
   <head> (before body paint) adds this class so CSS alone shows the right
   view on first paint, before any JS executes. */
html.ey-init-report #viewUpload,
html.ey-init-report #viewLoading,
html.ey-init-report #viewResults { display:none!important; }
html.ey-init-report #viewReportUpload { display:block!important; }

html.ey-init-dashboard #viewUpload,
html.ey-init-dashboard #viewLoading,
html.ey-init-dashboard #viewResults,
html.ey-init-dashboard #viewReportUpload { display:none!important; }
html.ey-init-dashboard #viewDashboard { display:block!important; }

/* ── 대시보드 세부 탭 — 첫 페인트 짐작(index.html의 인라인 스크립트가 찍는
   ey-init-dashtab-*) ──
   같은 트릭을 한 단계 안쪽(대시보드 섹션 안의 홈/인사이트/메타 광고 탭)에
   다시 쓴다. 정적 마크업은 세 패널이 전부 hidden으로 나가고 #dashTabHome만
   is-active로 나가는 상태라, dash-tabs.js가 실행되기 전까지 그 어긋남이
   그대로 보인다(2026-08-21 팔로업 요건 2) — initDashTabs()가 실제 상태를
   세우는 순간 이 클래스를 지운다(dashboard.js). 그 전까지만 유효하므로
   [hidden] 가드는 필요 없다 — 오히려 여기 목적 자체가 hidden을 잠깐
   이기는 것이다. */
html.ey-init-dashtab-home #viewDashHome,
html.ey-init-dashtab-insight #dashContent,
html.ey-init-dashtab-ads #viewMetaAds { display:block!important; }

html.ey-init-dashtab-home #dashDateRow { display:none!important; }

/* 형제 결함 — #dashTabHome은 마크업에서 항상 is-active로 나간다. 짐작한
   시작 탭이 홈이 아닐 때는 그 모양을 지우고 진짜 시작 탭 버튼에 입힌다. */
html.ey-init-dashtab-insight #dashTabHome.is-active,
html.ey-init-dashtab-ads #dashTabHome.is-active {
  color:var(--c-text-3); border-bottom-color:transparent; font-weight:500;
}
html.ey-init-dashtab-insight #dashTabInsight,
html.ey-init-dashtab-ads #dashTabMetaAds {
  color:var(--c-primary); border-bottom-color:var(--c-primary); font-weight:600;
}

/* ══════════════════════════════════════════════════════════════
   ANIMATIONS
══════════════════════════════════════════════════════════════ */
@keyframes fadeUp {
  from{opacity:0;transform:translateY(10px);}
  to{opacity:1;transform:translateY(0);}
}
@keyframes shimmer {
  0%{background-position:200% 0;}
  100%{background-position:-200% 0;}
}
@keyframes spin { to{transform:rotate(360deg);} }
@keyframes sweepLine {
  0%{transform:translateX(-100%);}
  100%{transform:translateX(280%);}
}
@keyframes scaleIn {
  from{opacity:0;transform:scale(0.96);}
  to{opacity:1;transform:scale(1);}
}
@keyframes slideDown {
  from{opacity:0;transform:translateY(-8px);}
  to{opacity:1;transform:translateY(0);}
}

.card-enter {
  animation: fadeUp var(--t-s) var(--ease) both;
  animation-delay: var(--d,0ms);
}

/* ══════════════════════════════════════════════════════════════
   SURFACE
══════════════════════════════════════════════════════════════ */
.surface {
  background: var(--c-surface);
  border: 1px solid var(--c-border);
  border-radius: var(--r-xl);
  box-shadow: var(--sh-sm);
}

/* ══════════════════════════════════════════════════════════════
   PAGE HEADER
══════════════════════════════════════════════════════════════ */
.page-header { margin-bottom:20px; }
.page-title { font-size:26px; font-weight:700; color:var(--c-text-1); letter-spacing:-0.02em; line-height:1.25; margin-bottom:8px; }
.page-desc { font-size:14px; color:var(--c-text-3); line-height:1.7; max-width:540px; }

/* ══════════════════════════════════════════════════════════════
   INFO BOX
══════════════════════════════════════════════════════════════ */
.infobox {
  display:flex; align-items:flex-start; gap:11px;
  background:var(--c-info-bg); border:1px solid var(--c-info-border);
  border-radius:var(--r-lg); padding:14px 16px; margin-bottom:20px;
  font-size:13.5px; color:var(--c-info-text); line-height:1.6;
}
.infobox__icon { flex-shrink:0; margin-top:1px; color:var(--c-info); }
.infobox strong { font-weight:600; }

/* ══════════════════════════════════════════════════════════════
   FORM
══════════════════════════════════════════════════════════════ */
.surface { padding:28px; }
.form-group { margin-bottom:24px; }
.form-divider { height:1px; background:var(--c-divide); margin:24px 0; }
.form-label { font-size:13px; font-weight:600; color:var(--c-text-2); margin-bottom:10px; letter-spacing:-0.01em; }
.form-required { color:var(--c-danger); margin-left:2px; }
.form-label-row { display:flex; align-items:center; justify-content:space-between; gap:12px; margin-bottom:10px; }
.form-label-row .form-label { margin-bottom:0; }

/* Input */
.input-group { display:flex; flex-direction:column; gap:6px; }
.input-label { font-size:11px; font-weight:600; letter-spacing:0.08em; text-transform:uppercase; color:var(--c-text-4); }
.input {
  height:52px; background:var(--c-surface); border:1.5px solid var(--c-border);
  border-radius:14px; padding:0 16px; font-family:var(--f); font-size:14px;
  color:var(--c-text-1); color-scheme:light; width:100%;
  transition:border-color var(--t-f) ease, box-shadow var(--t-n) ease;
}
.input::placeholder { color:var(--c-text-4); }
.input:hover { border-color:#D1D5DB; }
.input:focus { outline:none; border-color:var(--c-primary); box-shadow:0 0 0 4px var(--c-primary-ring); }

/* Date row */
.date-row { display:grid; grid-template-columns:1fr auto 1fr; align-items:end; gap:12px; }
.date-sep { color:var(--c-text-4); padding-bottom:16px; display:flex; align-items:center; }

/* Preset pills */
.preset-group { display:flex; gap:5px; }
.preset-btn {
  font-family:var(--f); font-size:12px; font-weight:500; color:var(--c-text-3);
  background:var(--c-surface); border:1.5px solid var(--c-border);
  border-radius:var(--r-xl); padding:4px 12px; cursor:pointer; white-space:nowrap;
  transition:color var(--t-f), background var(--t-f), border-color var(--t-f);
}
.preset-btn:hover { border-color:#D1D5DB; color:var(--c-text-1); }
.preset-btn.is-active { color:var(--c-primary); background:var(--c-primary-light); border-color:rgba(31,42,68,0.28); font-weight:600; }

/* File drop */
.filedrop {
  position:relative; border:1.5px dashed var(--c-border); border-radius:var(--r-lg); cursor:pointer;
  transition:border-color var(--t-n) ease, background var(--t-n) ease;
}
.filedrop:focus-within { outline:2px solid var(--c-primary); outline-offset:2px; border-radius:var(--r-lg); }
.filedrop input[type="file"] { position:absolute; inset:0; opacity:0; cursor:pointer; width:100%; height:100%; z-index:1; }
.filedrop input[type="file"]:focus { outline:none; }
.filedrop.is-over { border-color:var(--c-primary); background:var(--c-primary-light); }
.filedrop.is-over .filedrop__icon { color:var(--c-primary); transform:translateY(-4px); }
.filedrop__idle { padding:36px 24px; text-align:center; display:flex; flex-direction:column; align-items:center; gap:10px; }
.filedrop__icon { color:#D1D5DB; transition:transform var(--t-n) var(--ease-s), color var(--t-n); }
.filedrop__primary { font-size:14px; color:var(--c-text-3); font-weight:450; }
.filedrop__cta { color:var(--c-primary); font-weight:600; }
.filedrop__hint { font-family:var(--f-mono); font-size:11.5px; color:var(--c-text-4); letter-spacing:0.03em; }
.filedrop__chosen { padding:12px 14px; }

.file-card { display:flex; align-items:center; gap:12px; background:var(--c-bg); border:1px solid var(--c-border); border-radius:var(--r-md); padding:12px 14px; animation:scaleIn var(--t-n) var(--ease-s) both; }
.file-card__icon { color:var(--c-primary); flex-shrink:0; }
.file-card__info { flex:1; min-width:0; display:flex; flex-direction:column; gap:2px; }
.file-card__name { font-size:13.5px; font-weight:500; color:var(--c-text-1); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.file-card__size { font-family:var(--f-mono); font-size:11.5px; color:var(--c-text-4); }
.file-card__remove { background:none; border:none; cursor:pointer; color:var(--c-text-4); padding:5px; border-radius:var(--r-sm); display:flex; align-items:center; flex-shrink:0; transition:color var(--t-f), background var(--t-f); }
.file-card__remove:hover { color:var(--c-danger); background:var(--c-danger-bg); }

/* Selectable cards */
.select-cards { display:grid; grid-template-columns:repeat(3,1fr); gap:12px; }
.select-card {
  position:relative; display:flex; flex-direction:column; gap:10px; padding:16px;
  background:var(--c-surface); border:1.5px solid var(--c-border); border-radius:var(--r-lg); cursor:pointer;
  color:var(--c-text-1); transition:border-color var(--t-n), box-shadow var(--t-n), transform var(--t-n) var(--ease-s), background var(--t-n), color var(--t-n);
}
.select-card:hover { border-color:var(--c-primary); box-shadow:var(--sh-md); transform:scale(1.015); }
.select-card.is-active { background:var(--c-primary); border-color:var(--c-primary); color:#fff; box-shadow:var(--sh-md); transform:scale(1.015); }
.select-card__check { position:absolute; top:10px; right:10px; width:20px; height:20px; border-radius:50%; background:rgba(255,255,255,0.15); display:flex; align-items:center; justify-content:center; opacity:0; transition:opacity var(--t-n); }
.select-card.is-active .select-card__check { opacity:1; }
.select-card__icon { color:var(--c-primary); transition:color var(--t-n); }
.select-card.is-active .select-card__icon { color:rgba(255,255,255,0.85); }
.select-card__body { display:flex; flex-direction:column; gap:3px; }
.select-card__title { font-size:14px; font-weight:600; letter-spacing:-0.01em; }
.select-card__desc { font-size:12px; color:var(--c-text-3); transition:color var(--t-n); }
.select-card.is-active .select-card__desc { color:rgba(255,255,255,0.6); }

/* Alert */
.alert { display:flex; align-items:flex-start; gap:10px; border-radius:var(--r-md); padding:12px 14px; font-size:13.5px; margin-bottom:20px; animation:slideDown var(--t-n) var(--ease) both; }
.alert[hidden] { display:none!important; }
.alert__icon { flex-shrink:0; margin-top:1px; }
.alert--danger { background:var(--c-danger-bg); border:1px solid var(--c-danger-bdr); color:var(--c-danger-text); }

/* ══════════════════════════════════════════════════════════════
   BUTTONS
══════════════════════════════════════════════════════════════ */
.btn {
  display:inline-flex; align-items:center; justify-content:center; gap:7px;
  border:1.5px solid transparent; border-radius:var(--r-md);
  font-family:var(--f); font-size:14px; font-weight:600;
  cursor:pointer; text-decoration:none; white-space:nowrap;
  transition:background var(--t-f), box-shadow var(--t-n), transform var(--t-f), color var(--t-f), border-color var(--t-f);
}
.btn:disabled { opacity:0.42; cursor:not-allowed; transform:none!important; box-shadow:none!important; }
/* hidden 속성이 display 규칙에 덮이지 않도록 가드 */
.btn[hidden]     { display:none !important; }

.btn--lg { height:52px; padding:0 24px; border-radius:14px; font-size:15px; }
.btn--md { height:40px; padding:0 18px; }
/* 작은 버튼은 거의 늘 필터 칩·입력과 같은 줄에 선다 — 반경도 칩과 같은 --r-sm으로 맞춘다 */
.btn--sm { height:34px; padding:0 12px; font-size:13px; border-radius:var(--r-sm); }
/* 표 안, 촘촘한 행에 박는 버튼 — .btn--sm(34px)은 유가 집행 표 행 높이를 그
   버튼이 정해버릴 만큼 크다(followup-3). line-height:1로 서체가 물려주는 1.6배
   줄높이를 끊어야 버튼 자체가 22px 안에 정확히 담긴다. */
.btn--xs { height:22px; padding:0 9px; font-size:11.5px; line-height:1; border-radius:var(--r-sm); gap:4px; }
.btn--full { width:100%; }

.btn--primary {
  background:var(--c-primary); color:#fff;
  box-shadow:0 2px 6px rgba(31,42,68,0.18);
}
.btn--primary:not(:disabled):hover { background:var(--c-primary-hov); box-shadow:0 6px 20px rgba(31,42,68,0.26); transform:translateY(-1px); }
.btn--primary:not(:disabled):active { transform:translateY(0); }

.btn--ghost {
  background:var(--c-surface); color:var(--c-text-2);
  border-color:var(--c-border); box-shadow:var(--sh-xs);
}
.btn--ghost:not(:disabled):hover { background:var(--c-bg); color:var(--c-text-1); border-color:#D1D5DB; box-shadow:var(--sh-sm); }

.btn__arrow { transition:transform var(--t-n) var(--ease-s); }
.btn:not(:disabled):hover .btn__arrow { transform:translateX(3px); }

/* ══════════════════════════════════════════════════════════════
   LOADING
══════════════════════════════════════════════════════════════ */
.progress-line { position:fixed; top:0; left:var(--sb-w); right:0; height:3px; background:var(--c-divide); z-index:400; overflow:hidden; transition:left var(--t-n); }
.sb-collapsed .progress-line { left:var(--sb-w-col); }
.progress-line__fill { height:100%; width:45%; background:linear-gradient(90deg,transparent,var(--c-primary),transparent); animation:sweepLine 1.6s ease-in-out infinite; }

.loading-header { display:flex; align-items:center; gap:16px; margin-bottom:36px; }
.loading-header[hidden] { display:none!important; }
.loading-spinner svg { animation:spin 0.9s linear infinite; display:block; }
.loading-sub { font-family:var(--f-mono); font-size:12px; color:var(--c-text-3); letter-spacing:0.04em; margin-top:3px; }

/* Skeleton */
.skel-section { margin-bottom:40px; }
.skel-section-hd { display:flex; align-items:center; gap:12px; margin-bottom:14px; }
.skel {
  background:linear-gradient(90deg,var(--c-divide) 25%,#F0F1F4 50%,var(--c-divide) 75%);
  background-size:200% 100%; border-radius:var(--r-xs); animation:shimmer 1.4s ease-in-out infinite;
  /* bar()가 --d에 스태거 값을 넣는데 이 줄이 없어 전부 같은 위상으로 동시에
     맥동하고 있었다. .skel-row에만 있던 규칙을 여기에도 둔다. */
  animation-delay:var(--d,0ms);
}
.skel-pill  { height:24px; border-radius:var(--r-xl); }
.skel-title { height:22px; }
.skel-cell  { height:10px; }
.skel-row   { height:48px; border-radius:var(--r-sm); margin-bottom:2px; animation:shimmer 1.4s ease-in-out infinite; animation-delay:var(--d,0ms); }
.skel-card  { padding:0!important; overflow:hidden; }
.skel-thead { display:flex; align-items:center; gap:24px; padding:12px 20px; border-bottom:1px solid var(--c-divide); }
.skel-rows  { padding:8px 10px; }

/* ── 영역별 스켈레톤 (skeleton.js) ──────────────────────────────
   실제 컴포넌트와 같은 높이·그리드를 차지해야 교체 시 점프가 없다. */
.skel-scorecard        { display:flex; flex-direction:column; }
.skel-scorecard__head  { display:flex; align-items:center; gap:7px; }
.skel-rank-row         { display:flex; align-items:center; gap:10px; padding:7px 0; }

/* ── 홈 스켈레톤 조립(home-skeleton.js) ── 정적 마크업 위에 데이터 두 자리만 채운다.
   .seg__count·.dash-pagination는 실제 요소가 이미 갖고 있는 클래스라 배치(정렬·간격)는
   거기서 상속된다 — 여기서는 데이터 도착 전에도 실제와 같은 자리를 차지하게만 만든다. */
.skel-count { display:inline-flex; align-items:center; min-width:18px; }
/* 실 페이지네이션은 데이터가 1페이지뿐이면 컨트롤이 아예 안 뜨는 조건부 렌더라(다시
   0px로 접힌다) 이 min-height로도 완전히는 못 막지만, 흔한 경우(2페이지 이상)에서
   "행이 없다 생기며 표 아래가 밀리는" 점프는 없앤다. 실 버튼(.dash-pagination__btn)과
   같은 30px을 잡는다. */
.skel-pagination { min-height:30px; }

/* 표 스켈레톤 — 실제 행 높이에 맞춘다 */
.skel-tr td { padding:11px 12px; border-bottom:1px solid var(--c-divide); }

/* ── warm 상태: 기존 수치를 남긴 채 흐리게 ──────────────────────
   읽던 내용이 사라지지 않아 체감 대기가 가장 짧다.
   대신 상호작용은 막아 낡은 값으로 조작하는 일을 방지한다. */
.is-stale {
  opacity:.42; pointer-events:none; user-select:none;
  transition:opacity var(--t-s,.18s) var(--ease,ease);
}

/* ── 스켈레톤 → 콘텐츠 크로스페이드 (skeleton.js swap) ──────────
   스켈레톤을 떠 있는 레이어로 남기고 실제 콘텐츠를 그 밑에 흐름으로 넣어
   둘을 겹친다. 어느 순간에도 빈 화면이 없다.
   지속시간은 JS가 --ey-swap-dur로 넘긴다. */
.is-swapping { position:relative; }
.is-swapping > .skel-ghost {
  position:absolute; left:0; right:0; top:0;
  opacity:1; pointer-events:none;
  transition:opacity var(--ey-swap-dur,200ms) var(--ease,ease);
}
.is-swapping > *:not(.skel-ghost) {
  opacity:0;
  transition:opacity var(--ey-swap-dur,200ms) var(--ease,ease);
}
.is-swapping.is-revealed > .skel-ghost          { opacity:0; }
.is-swapping.is-revealed > *:not(.skel-ghost)   { opacity:1; }

/* ── 스플래시 ────────────────────────────────────────────────
   마크업과 첫 페인트용 스타일(#eySplash의 기본 숨김 + html.ey-splashing에서만
   보이게 하는 규칙)은 각 HTML의 <head>에 인라인으로 있다. 여기 있는 것은
   그 이후의 연출이다 — 이 파일이 도착할 즈음에는 로고가 이미 화면에 떠 있다.

   display는 여기서 다시 선언하지 않는다 — <head>의 인라인 스타일이 기본은
   숨김으로, html.ey-splashing일 때만 grid로 두는 조건부 display를 이미
   정했다. 여기서 무조건 display:grid를 또 선언하면, 이 파일 로드가
   완료되는 순간(같은 명시도끼리는 나중에 온 이 규칙이 이긴다) 스플래시를
   안 띄워야 하는 로드에서도 갑자기 다시 보이게 된다.

   z-index 1200은 토스트(.toast-stack, 1000)보다 위다.

   배경은 --c-bg 토큰을 그대로 못 쓴다 — 토큰은 #F7F8FA 헥스라 알파를 못
   갖는다. 그래서 같은 값을 rgba(247,248,250,.72) 리터럴로 다시 적는다(값이
   갈리면 여기 코멘트로 유지보수 시 같이 확인). .72는 임의값이 아니다 —
   <head> 인라인이 이 오버레이를 style.css 도착 전에 먼저 그리는데, 그 순간
   화면 아래엔 아직 스타일이 안 입은 raw HTML이 있다. blur(20px)와 짝지어야
   그 raw 마크업이 "읽을 수 없는 텍스처"로 뭉개진다 — .72보다 더 투명하게
   "개선"하면 콜드 로드에서 꾸미지 않은 마크업이 도로 비쳐 보인다. 절대
   더 낮추지 말 것. backdrop-filter를 지원하지 않는 브라우저는 블러 없이
   .72만 남아 raw 마크업이 그대로 비치므로, 아래 @supports 분기가 그 경우에만
   불투명에 가깝게(.94) 끌어올린다. */
#eySplash{
  position:fixed; inset:0; width:100%; height:100dvh;
  place-items:center;
  /* 불투명이다. 반투명 글래스로 뒀을 땐 사이드바(거의 검정)가 회색 띠로,
     카드 윤곽이 얼룩으로 비쳐 로고보다 먼저 눈에 들어왔다. 불투명이면
     그 문제가 통째로 사라지고, <head> 인라인이 style.css 도착 전에 먼저
     그리는 콜드 로드에서 raw HTML이 비칠 여지도 없어진다. */
  background:var(--c-bg); z-index:1200;
  transition:opacity 220ms var(--ease,ease);
}
/* 원본 <img>는 그리지 않는다 — 검은 워드마크라 색을 바꿀 수 없다. 같은 PNG의
   알파를 마스크로 써서 ::after에 유리 재질을 입힌다. ::after는 #eySplash의
   유일한 그리드 아이템이라 place-items:center가 그대로 가운데로 놓는다
   (절대배치·translate가 필요 없고, 그래서 transform을 scale 전용으로 남겨둘 수
   있다 — 아래 호흡·퇴장이 이걸 쓴다).

   opacity:1·scale(1)을 쉬는 상태로 명시한다 — 안 하면 <head> 인라인의
   opacity:0·scale(.94)(진입 애니메이션의 시작점)가 남아, .is-breathing이
   animation 단축 속성으로 eySplashIn을 갈아치우는 순간(아래) 그 forwards가
   사라지면서 로고가 진입 전 상태로 되돌아가 버린다. */
#eySplash img{ display:none; }
#eySplash::after{
  content:''; width:clamp(80px,11vw,132px); aspect-ratio:951/165;
  -webkit-mask:url(/logo/eyesmag-logo.png) center/contain no-repeat;
          mask:url(/logo/eyesmag-logo.png) center/contain no-repeat;
  /* 배경과 같은 계열의 반투명 회색 — 레퍼런스의 저대비 톤.
     backdrop-filter는 쓰지 않는다: 배경이 단색이 된 뒤로는 블러할 대상이
     없어 실측 차이가 채널당 최대 2/255였다(눈에 안 보인다). 합성 레이어
     비용만 남으므로 뺀다. */
  background:rgba(90,102,120,.42);
  /* 음영은 글자 윤곽을 따라간다. drop-shadow는 마스크의 알파 모양을 그대로
     따르므로 글자 바깥 트레일에만 붙는다 — 로고 뒤에 덩어리(방사형 그라디언트)를
     까는 방식과 달리 배경에 얼룩이 생기지 않는다.

     오프셋을 주지 않는다. 132px에서 획 두께가 2px 남짓이라, 흰 하드 그림자를
     아래로 1px만 깔아도 획의 절반을 덮어 글자가 얇아지고 위쪽이 잘린 것처럼
     보였다(160px일 땐 안 보이던 문제가 크기를 줄이며 드러났다). 첫 겹은
     오프셋 0으로 윤곽을 고르게 감싸고, 둘째 겹만 아래로 살짝 퍼뜨린다. */
  filter:drop-shadow(0 0 1px rgba(15,17,21,.18))
         drop-shadow(0 1px 4px rgba(15,17,21,.12));
  opacity:1; transform:scale(1);
}

/* 260ms의 등장이 끝난 뒤 한 번만 숨을 쉰다. 루프가 아니다 —
   무한 애니메이션은 reduced-motion에서 별도 차단이 필요해지고
   브랜드 톤에도 과하다.

   #eySplash.is-breathing::after는 #eySplash::after보다 명시도가 높아(1,1,1 vs
   1,0,1) animation 단축 속성으로 <head> 인라인의 eySplashIn을 완전히
   갈아치운다 — 그 forwards가 잡고 있던 종료 상태는 사라지고, 이때부터
   transform의 "쉬는 값"은 위 #eySplash::after가 정한 값이 된다. 0%·100%를
   여기서 직접 고정해 그 값과 같게 맞춘다 — 안 그러면(퍼센트를 안 주면) 브라우저가
   같은 값을 암묵적으로 채우긴 하지만, 위 규칙이 나중에 달라져도 호흡만은
   항상 "쉬는 상태"에서 시작해 그 상태로 돌아오도록 여기서 명시적으로 못박는다. */
#eySplash.is-breathing::after{ animation:eySplashBreathe 340ms var(--ease,ease) 1; }
@keyframes eySplashBreathe{ 0%,100%{ transform:scale(1); } 50%{ transform:scale(1.012); } }

/* 퇴장에 애니메이션을 남긴다 — 이 퇴장은 스켈레톤으로의 전환 그 자체라서
   다른 오버레이들처럼 즉시 사라지면 화면이 번쩍인다. */
#eySplash.is-out{ opacity:0; }
#eySplash.is-out::after{ transform:scale(1.008); transition:transform 220ms var(--ease,ease); }

html.ey-splashing, html.ey-splashing body{ overflow:hidden; }

/* 접근성: 모션 최소화 설정이면 shimmer를 끄고 정적 회색으로 둔다 */
@media (prefers-reduced-motion: reduce) {
  .skel, .skel-row { animation:none; background:var(--c-divide); }
  .is-stale { transition:none; }

  /* 스플래시는 전체 순서를 유지하되 강도만 낮춘다. 전역 차단(:128)이 이미
     duration을 0.01ms로 만들지만, 그러면 페이드가 아예 없어 툭 사라진다.
     짧은 opacity 페이드만 남기고 scale과 호흡은 뺀다. */
  #eySplash            { transition:opacity 140ms linear!important; }
  #eySplash::after     { animation:none!important; transform:none!important; opacity:1!important; }
  #eySplash.is-out::after { transform:none!important; }
}

/* ══════════════════════════════════════════════════════════════
   RESULTS TOP BAR
══════════════════════════════════════════════════════════════ */
.results-bar { display:flex; align-items:flex-start; justify-content:space-between; gap:16px; margin-bottom:28px; }
.meta-chips { display:flex; flex-wrap:wrap; gap:6px; }
.meta-chip {
  display:inline-flex; align-items:center; gap:6px;
  background:var(--c-surface); border:1px solid var(--c-border);
  border-radius:var(--r-xl); padding:5px 13px; font-size:12.5px; color:var(--c-text-2);
  font-weight:500; box-shadow:var(--sh-xs);
}
.meta-chip__dot { width:6px; height:6px; border-radius:50%; flex-shrink:0; }

/* ── Author chips (작성자 지정 테이블) ── */
.author-chip-list { display:flex; flex-wrap:wrap; gap:4px; }
.author-chip {
  display:inline-flex; align-items:center;
  background:hsl(var(--chip-hue, 220) 40% 94%);
  border:1px solid hsl(var(--chip-hue, 220) 30% 83%);
  border-radius:999px; padding:2px 9px; font-size:11px; font-weight:500;
  color:hsl(var(--chip-hue, 220) 40% 32%); white-space:nowrap;
}
/* 접히는 칩은 button이라 브라우저 기본값을 걷어내야 span 칩과 같아 보인다 */
.author-chip--toggle {
  font-family:inherit; cursor:pointer; appearance:none;
  transition:filter .12s;
}
.author-chip--toggle:hover { filter:brightness(.96); }
.author-chip--toggle:focus-visible {
  outline:2px solid var(--c-accent, #4c6ef5); outline-offset:1px;
}

/* ══════════════════════════════════════════════════════════════
   DASHBOARD SCORE GRID
══════════════════════════════════════════════════════════════ */
.dash-score-grid {
  display: flex;
  flex-direction: column;
  gap: 16px;
  margin-bottom: 28px;
}
.dash-score-card {
  padding: 20px 22px;
  border-radius: var(--r-lg);
  overflow: hidden; /* 내부 콘텐츠 카드 밖으로 벗어나지 않도록 */
  transition: box-shadow var(--t-n), transform var(--t-n);
}
.dash-score-card:hover {
  box-shadow: var(--sh-hov);
  transform: translateY(-2px);
}
/* 카드 제목 (아이콘 + 텍스트) */
.dash-score-card__title {
  display: flex; align-items: center; gap: 6px;
  font-size: 11px; font-weight: 600; letter-spacing: 0.05em;
  text-transform: uppercase; color: var(--c-text-4); margin-bottom: 16px;
  white-space: nowrap; overflow: hidden;
}
.dash-score-card__title-icon {
  display: flex; align-items: center; flex-shrink: 0; color: var(--c-text-4);
}
.dash-score-card__title > span:last-child {
  overflow: hidden; text-overflow: ellipsis;
}
.dash-score-card__empty { font-size: 12px; color: var(--c-text-4); padding: 8px 0; }
.dash-score-row {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 0; border-bottom: 1px solid var(--c-divide);
  min-width: 0; /* 부모 flex에서 overflow 방지 */
}
.dash-score-row:last-child { border-bottom: none; padding-bottom: 0; }
.dash-score-row__rank {
  font-size: 11px; font-weight: 700; color: var(--c-text-4);
  font-family: var(--f-mono); width: 14px; flex-shrink: 0; text-align: center;
}
.dash-score-row__body { flex: 1; min-width: 0; }
.dash-score-row__name {
  font-size: 12.5px; color: var(--c-text-2);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  margin-bottom: 4px;
}
.dash-score-row__name--metric {
  flex: 1; min-width: 0;
  font-size: 12.5px; color: var(--c-text-2);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  text-decoration: none;
}
a.dash-score-row__name--metric:hover {
  color: var(--c-primary); text-decoration: underline;
}
/* 성과표 제목 링크 */
.perf-title-link {
  display: block; color: var(--c-text-2); text-decoration: none;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  max-width: 190px; transition: color var(--t-f);
}
.perf-title-link:hover { color: var(--c-primary); text-decoration: underline; }
/* 작성자 칸 — 이름이 칸보다 길면(공동 작성자면 여러 명이 들어온다) 둘째 줄로 넘어가
   행 높이가 두 줄로 벌어진다. 본문 칸과 같은 방식으로 한 줄에 고정하고 넘치면 자른다.
   잘린 전체 이름은 셀의 title 속성으로 확인할 수 있다. */
.perf-author-cell {
  display: block; color: inherit;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  max-width: 88px;
}
/* 본문 확장 버튼 */
.perf-expand-btn {
  display: inline-flex; align-items: center; justify-content: center;
  flex-shrink: 0; width: 16px; height: 16px;
  border: 1px solid var(--c-divide); border-radius: 3px;
  font-size: 12px; font-weight: 700; line-height: 1;
  color: var(--c-text-4); background: none; cursor: pointer;
  transition: color var(--t-f), border-color var(--t-f);
}
.perf-expand-btn:hover { color: var(--c-primary); border-color: var(--c-primary); }
/* 본문 확장 행 */
.perf-expand-row { display: none; }
.perf-expand-row.is-open { display: table-row; }
.perf-expand-body {
  padding: 12px 16px 14px; font-size: 13px; line-height: 1.75;
  color: var(--c-text-2); white-space: pre-wrap; word-break: break-word;
  background: var(--c-bg-2); border-top: 1px solid var(--c-divide);
}
/* 전체 탭 유형 배지 */
.perf-type-badge {
  flex-shrink: 0; font-size: 10px; font-weight: 600; line-height: 1;
  padding: 2px 6px; border-radius: 4px; white-space: nowrap;
}
.perf-type-badge[data-t="게시물"] { background: #e0f2fe; color: #0369a1; }
.perf-type-badge[data-t="릴스"]   { background: #fce7f3; color: #be185d; }
/* 유가 집행 표는 캐러셀·동영상도 배지로 그린다(콘텐츠 성과 표는 아직 안 씀) */
.perf-type-badge[data-t="캐러셀"] { background: #ede9fe; color: #6d28d9; }
.perf-type-badge[data-t="동영상"] { background: #fef3c7; color: #b45309; }
@media (prefers-color-scheme: dark) {
  .perf-type-badge[data-t="게시물"] { background: rgba(3,105,161,.25); color: #7dd3fc; }
  .perf-type-badge[data-t="릴스"]   { background: rgba(190,24,93,.25);  color: #f9a8d4; }
  .perf-type-badge[data-t="캐러셀"] { background: rgba(109,40,217,.25); color: #c4b5fd; }
  .perf-type-badge[data-t="동영상"] { background: rgba(180,83,9,.25);   color: #fcd34d; }
}
.dash-score-row__bar {
  height: 4px; background: var(--c-divide); border-radius: 2px; overflow: hidden;
}
.dash-score-row__bar-fill {
  height: 100%; background: var(--c-primary); border-radius: 2px;
  /* 업무량 트래커 막대(.wl-chartrow__fill, :2798)의 실제 채움 값과 맞춘다 —
     duration은 이미 같았고(0.5s), var(--ease)는 그쪽에 없어 뺐다(사용자 지시:
     실제 값이 다르면 그쪽을 따른다). */
  transition: width 0.5s;
}
.dash-score-row__val {
  font-size: 12px; font-family: var(--f-mono);
  color: var(--c-text-3); flex-shrink: 0; white-space: nowrap; padding-left: 4px;
}
.dash-score-badge {
  background: var(--c-primary); color: #fff;
  border-radius: 999px; padding: 3px 10px;
  font-size: 11px; font-weight: 600; white-space: nowrap; flex-shrink: 0;
}
@media (prefers-reduced-motion:reduce) {
  .dash-score-row__bar-fill { transition:none; }
}

/* 지표 TOP5 변동 배지 (NEW/▲n/▼n, 2026-08-24) — 새 색 토큰 없이 기존 것만 쓴다.
   ▲는 --c-danger(빨강=상승), ▼는 --c-info(파랑=하락) — 포털 실시간 검색어 관행을
   따른다. 값 증감 배지(.scorecard__delta, :2298)의 success=상승/danger=하락과는
   일부러 반대다 — 저건 "값이 늘었다(좋다)"고, 이건 "순위가 올랐다(포털 관행)"라
   같은 화살표라도 뜻이 다르다. NEW는 --c-primary(네이비) 텍스트 배지. */
.dash-score-row__trend {
  font-size: 10.5px; font-weight: 700; font-family: var(--f-mono);
  letter-spacing: 0.02em; white-space: nowrap; flex-shrink: 0; padding-left: 2px;
}
.dash-score-row__trend--up   { color: var(--c-danger); }
.dash-score-row__trend--down { color: var(--c-info); }
.dash-score-row__trend--new  { color: var(--c-primary); }

/* 실검 룰렛식 채움 (지표 TOP5 카드 전용, 2026-08-24) — 카드가 그려질 때(첫 로드·기간
   변경) 행이 위에서 아래로 시차를 두고 부드럽게 굴러 들어온다. .card-enter(:628)와
   같은 성질 — JS 타이머 없이 --row-d(buildMetricCard가 행마다 100ms씩 준다)로 시차를
   준다. 5행 기준 최장 지연(4×100ms)+재생(320ms)=720ms.

   사용자 제공 참고 영상(실검 ui.mp4, 2026-08-24 프레임 분석) 기준:
   · 수직 이동이 없다 — 행 텍스트가 제자리에서 순수 페이드로만 나타난다
     (예전 translateY(8px)는 영상과 달라 제거).
   · 물결이 순차다 — 한 행이 반잯 채워질 때 다음 행이 시작한다. 영상은
     행당 ~300ms·전체 ~1.4초지만 업무 화면이라 전체 0.72초로 조임 —
     순차감(지연 100ms < 재생 320ms의 겹침)은 유지된다. */
.dash-score-row--enter {
  animation: metricRowIn 320ms ease both;
  animation-delay: var(--row-d, 0ms);
}
@keyframes metricRowIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@media (prefers-reduced-motion:reduce) {
  .dash-score-row--enter { animation: none; }
}

/* 상단 3열 카드 */
.dash-score-top-row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
}
/* 지표 섹션 */
.dash-metric-section { }
.dash-metric-viewport {
  overflow: hidden;
}
.dash-metric-track {
  display: flex;
  will-change: transform;
}
.dash-metric-page {
  flex: 0 0 100%;
  min-width: 0;
}
.dash-metric-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 16px;
}
/* 지표 네비게이션 */
.dash-metric-nav {
  display: flex; align-items: center; justify-content: center;
  gap: 10px; margin-top: 14px;
}
.dash-metric-arrow {
  width: 30px; height: 30px; border-radius: 50%;
  background: var(--c-surface); border: 1.5px solid var(--c-border);
  color: var(--c-text-3);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; flex-shrink: 0;
  transition: border-color var(--t-f), color var(--t-f), box-shadow var(--t-f);
}
.dash-metric-arrow:hover:not(:disabled) {
  border-color: var(--c-primary); color: var(--c-primary); box-shadow: var(--sh-sm);
}
.dash-metric-arrow:disabled { opacity: 0.3; cursor: not-allowed; }
.dash-metric-dots { display: flex; align-items: center; gap: 6px; }
.dash-metric-dot {
  width: 7px; height: 7px; border-radius: 4px;
  background: var(--c-divide); border: none; padding: 0;
  cursor: pointer; transition: background var(--t-n), width var(--t-n);
}
.dash-metric-dot.is-active { background: var(--c-primary); width: 22px; }

@media (max-width: 1100px) {
  .dash-score-top-row { grid-template-columns: repeat(2, 1fr); }
  .dash-metric-grid   { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 640px) {
  .dash-score-top-row { grid-template-columns: 1fr; }
  .dash-metric-grid   { grid-template-columns: 1fr; }
}

/* ══════════════════════════════════════════════════════════════
   SCORE CARDS GRID
══════════════════════════════════════════════════════════════ */
.score-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 14px;
  margin-bottom: 28px;
}

/* Metric cards slider */
.metric-slider-outer {
  grid-column: 1 / -1;
  overflow: hidden;
}
.metric-slider {
  display: flex;
  gap: 14px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  padding-bottom: 4px; /* prevent shadow clip */
}
.metric-slider::-webkit-scrollbar { display: none; }
.metric-slider > .score-card {
  flex: 0 0 calc(25% - 10.5px);
  min-width: 220px;
  scroll-snap-align: start;
}
.metric-slider-footer {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  margin-top: 12px;
}
.mslider-arrow {
  width: 28px; height: 28px;
  border-radius: 50%;
  background: var(--c-surface);
  border: 1.5px solid var(--c-border);
  color: var(--c-text-3);
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
  transition: border-color var(--t-f), color var(--t-f), background var(--t-f);
}
.mslider-arrow:hover:not(:disabled) { border-color: var(--c-primary); color: var(--c-primary); background: var(--c-primary-light); }
.mslider-arrow:disabled { opacity: 0.3; cursor: not-allowed; }
.mslider-dots { display: flex; align-items: center; gap: 5px; }
.mslider-dot {
  width: 6px; height: 6px;
  border-radius: 3px;
  background: var(--c-divide);
  border: none; padding: 0; cursor: pointer;
  transition: background var(--t-n), width var(--t-n);
}
.mslider-dot.is-active { background: var(--c-primary); width: 18px; }

.score-card {
  background: var(--c-surface);
  border: 1px solid var(--c-border);
  border-radius: var(--r-xl);
  padding: 20px 22px;
  box-shadow: var(--sh-sm);
  transition: box-shadow var(--t-n) ease, transform var(--t-n) ease;
}
.score-card:hover { box-shadow: var(--sh-hov); transform: translateY(-2px); }

.score-card__label {
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--c-text-4);
  margin-bottom: 14px;
  display: flex;
  align-items: center;
  gap: 6px;
}

.score-card__label-icon { color: var(--c-text-3); }

.score-list { display: flex; flex-direction: column; gap: 8px; }

.score-item {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 13.5px;
  padding: 7px 0;
  border-bottom: 1px solid var(--c-divide);
}
.score-item:last-child { border-bottom: none; padding-bottom: 0; }

.score-item__rank {
  font-family: var(--f-mono);
  font-size: 11.5px;
  font-weight: 700;
  color: var(--c-text-4);
  width: 20px;
  flex-shrink: 0;
  text-align: center;
}
.score-item--1 .score-item__rank { color: var(--c-text-1); }

.score-item__name {
  flex: 1;
  font-weight: 500;
  color: var(--c-text-1);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.score-item__sub {
  font-size: 12px;
  color: var(--c-text-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 130px;
}

.score-item__val {
  font-family: var(--f-mono);
  font-size: 12.5px;
  font-weight: 600;
  color: var(--c-text-1);
  flex-shrink: 0;
  white-space: nowrap;
}
.score-item__badge {
  font-family: var(--f-mono);
  font-size: 11px;
  font-weight: 600;
  background: var(--c-primary);
  color: #fff;
  border-radius: 4px;
  padding: 1px 6px;
  flex-shrink: 0;
}

/* ══════════════════════════════════════════════════════════════
   TABS
══════════════════════════════════════════════════════════════ */
.tab-panel { animation: fadeUp var(--t-s) var(--ease) both; }
.tab-panel[hidden] { display: none!important; }

/* ══════════════════════════════════════════════════════════════
   LIST TOOLBAR — 리스트 화면 공통 (세그먼트 · 필터칩 · 컴팩트 검색)

   리스트+필터 화면(대시보드 성과 순위, 작성자 지정)이 같은 어휘를 쓰도록
   묶어 둔 것. 화면마다 다른 클래스를 만들면 금세 어긋나므로 여기만 고친다.

     .seg      상호배타 뷰 전환 — 전체/게시물/릴스처럼 '무엇을 보는가'
     .fchip    켜고 끄는 필터·정렬 — 반응/좋아요, 이번달/지난달
     .toolbar  칩은 왼쪽, 검색·액션은 오른쪽으로 미는 한 줄
══════════════════════════════════════════════════════════════ */

/* ── 세그먼트 컨트롤 ── */
.seg {
  display:inline-flex; align-items:center; gap:2px;
  padding:3px; background:var(--c-bg);
  border:1px solid var(--c-border); border-radius:var(--r-sm);
}
.seg[hidden] { display:none!important; }
/* justify-content:center(followup-6) — 개수 배지(.seg__count)가 서버 응답
   전엔 빈 span이다. 기본값(flex-start)이면 라벨이 항상 왼쪽 패딩에 붙어
   있고, 그 오른쪽엔 gap(6px)만큼의 빈 자리가 남아 숫자가 뜨기 전엔 라벨이
   왼쪽으로 치우쳐 보인다 — 숫자가 도착하면 그 빈 자리가 실제 텍스트로
   바뀌며 버튼 폭만 늘어날 뿐, 라벨 자체는 계속 왼쪽 그대로였다. center로
   바꾸면 라벨(+빈 배지)이 통째로 가운데 놓이고, 배지에 숫자가 들어오면
   "라벨+숫자" 묶음이 다시 가운데를 잡으며 라벨이 그만큼 옆으로 밀린다 —
   숫자가 뜨기 전엔 가운데, 뜨면 그 자리에서 이동한다는 사용자 요구를
   그대로 만족한다. 배지가 없는 .seg__btn(예: 메타 광고 전체/무가/유가
   퀵칩)은 항목이 하나뿐이라 정렬 기준이 바뀌어도 보이는 차이가 없다. */
.seg__btn {
  display:inline-flex; align-items:center; justify-content:center; gap:6px;
  height:28px; padding:0 12px;
  border:none; background:none; border-radius:6px;
  font-family:var(--f); font-size:12.5px; font-weight:500;
  color:var(--c-text-3); cursor:pointer; white-space:nowrap;
  transition:background var(--t-f), color var(--t-f);
}
.seg__btn:hover { color:var(--c-text-1); }
.seg__btn.is-active {
  background:var(--c-surface); color:var(--c-text-1);
  font-weight:600; box-shadow:var(--sh-xs);
}
.seg__btn:focus-visible { outline:2px solid var(--c-primary); outline-offset:-2px; }
/* .fchip:disabled(:1303)의 짝 — 지금까지 .seg__btn을 쓰는 화면(전체/게시물/릴스,
   부스팅/유가 서비스)은 셀 비활성화가 없어서 이 규칙이 없어도 안 드러났다.
   메타 광고 퀵칩이 .seg로 옮겨오며(Task 2) "그 유형 캠페인이 기간에 없으면
   비활성"을 그대로 가져오는데, 이 규칙 없인 disabled=true인 셀이 활성 셀과
   똑같아 보여 눌러도 아무 일 없는 상태가 된다. */
.seg__btn:disabled { opacity:.45; cursor:default; }
.seg__btn:disabled:hover { color:var(--c-text-3); }
/* 개수는 라벨 다음에 눈에 들어와야 하지만, 위계는 크기(11px)로 이미 잡혔다.
   색까지 --c-text-4로 낮추면 --c-bg 위에서 2.4:1 — WCAG AA(4.5:1) 미달이라 --c-text-3(4.55:1)을 쓴다. */
.seg__count { font-family:var(--f-mono); font-size:11px; font-weight:500; color:var(--c-text-3); }
.seg__btn.is-active .seg__count { color:var(--c-text-2); }
/* 배지가 비어 있는 동안(숫자 도착 전)은 레이아웃에서 완전히 빠진다 — 위
   justify-content:center와 짝이다. 빈 span도 flex 아이템으로 남으면 그
   앞의 gap(6px)이 그대로 남아 "라벨 + 안 보이는 여백"이 가운데를 잡아
   라벨이 살짝 왼쪽으로 밀린다. display:none으로 아예 치우면 라벨 혼자
   정확히 가운데 놓이고, textContent가 채워지는 순간(:empty가 더 이상 안
   걸림) 다시 나타나며 자연스럽게 자리를 만든다. */
.seg__count:empty { display:none; }

/* ── 밑줄 탭 (화면 단위 전환) ──
   세그먼트(.seg)는 "같은 목록을 좁히는" 필터에, 이 밑줄 탭은 "다른 화면으로 옮기는"
   전환에 쓴다 — 오버뷰↔포스팅 관리, 전체↔내가 작성한처럼. 둘을 섞지 않는다. */
.tab-bar { display:flex; gap:0; margin-bottom:16px; border-bottom:2px solid var(--c-border); }
/* [hidden] 가드 — 인증 실패 경로가 #dashTabs를 hidden으로 되돌린다(index.html,
   2026-08-19 리뷰 Fix 6). 위 display:flex가 UA의 [hidden]{display:none}을 이겨버리는
   건 이 파일 전체에서 반복되는 함정이다(.dash-header-controls·.view 등과 같은 이유). */
.tab-bar[hidden] { display:none!important; }
.tab-btn {
  display:inline-flex; align-items:center; gap:7px;
  padding:11px 20px;
  font-family:var(--f); font-size:14px; font-weight:500;
  color:var(--c-text-3); background:none; border:none;
  border-bottom:2px solid transparent; margin-bottom:-2px;
  cursor:pointer; white-space:nowrap;
  transition:color var(--t-f), border-color var(--t-f);
}
.tab-btn:hover { color:var(--c-text-1); }
.tab-btn.is-active { color:var(--c-primary); border-bottom-color:var(--c-primary); font-weight:600; }
.tab-btn:focus-visible { outline:2px solid var(--c-primary); outline-offset:-2px; }
.tab-bar--sm { margin-bottom:20px; gap:4px; }
.tab-bar--sm .tab-btn { height:34px; padding:0 14px; font-size:13px; }

/* ── 필터 칩 (둥근 박스) ── */
.fchip {
  display:inline-flex; align-items:center; gap:5px;
  height:30px; padding:0 11px;
  background:var(--c-surface); border:1px solid var(--c-border);
  border-radius:var(--r-sm);
  font-family:var(--f); font-size:12.5px; font-weight:500;
  color:var(--c-text-2); cursor:pointer; white-space:nowrap;
  transition:background var(--t-f), border-color var(--t-f), color var(--t-f);
}
.fchip:hover { background:var(--c-bg); border-color:#D1D5DB; }
.fchip.is-active {
  background:var(--c-primary); border-color:var(--c-primary);
  color:#fff; font-weight:600;
}
.fchip:focus-visible { outline:2px solid var(--c-primary); outline-offset:2px; }
.fchip:disabled { opacity:.45; cursor:default; }
.fchip:disabled:hover { background:var(--c-surface); border-color:var(--c-border); }
.fchip[hidden] { display:none!important; }
.fchip__ico { flex-shrink:0; opacity:.75; }
.fchip.is-active .fchip__ico { opacity:1; }
/* 선택 개수처럼 라벨 뒤에 붙는 보조 숫자 */
.fchip__count { font-family:var(--f-mono); font-size:11px; opacity:.8; }

/* ── 툴바 한 줄 ── */
.toolbar {
  display:flex; align-items:center; gap:6px; flex-wrap:wrap;
  padding:10px 0 12px;
  border-bottom:1px solid var(--c-divide);
  margin-bottom:12px;
}
.toolbar__end { margin-left:auto; display:flex; align-items:center; gap:6px; }

/* 세그먼트(36px)와 한 줄에 서는 툴바 — 검색·CSV를 탭 높이에 맞춘다.
   30px짜리 칩 기준을 그대로 쓰면 탭보다 6px 낮아 줄이 어긋나 보인다. */
.toolbar--tall .fchip,
.toolbar--tall .search-field--sm .search-field__input { height:36px; }
.toolbar--tall .fchip { padding:0 13px; }

/* 칩과 같은 줄에 서는 버튼은 칩 높이(30px)에 맞춘다.
   .btn--sm 기본값 34px을 그대로 두면 한 줄 안에서 혼자 튀어 보인다. */
.toolbar .btn--sm,
.rsec-actions .btn--sm { height:30px; padding:0 12px; font-size:12.5px; }

/* 분할 버튼(나로 지정 + 대상 선택)은 본체가 비활성이면 화살표도 함께 흐려져야 한다 */
.rsec-actions .btn:disabled + .btn { opacity:.5; pointer-events:none; }
.toolbar__sep { width:1px; height:18px; background:var(--c-border); flex-shrink:0; }

/* ── 컴팩트 검색 — 칩과 같은 높이로 맞춘다 ── */
.search-field--sm .search-field__input {
  height:30px; width:168px;
  font-size:12.5px; border-width:1px; border-radius:var(--r-sm);
  padding:0 10px 0 28px;
}
.search-field--sm .search-field__input:focus { width:208px; }
.search-field--sm .search-field__ico { left:9px; }

@media (max-width:720px) {
  .toolbar__end { margin-left:0; width:100%; }
  .search-field--sm .search-field__input { width:100%; }
  .search-field--sm .search-field__input:focus { width:100%; }
  .search-field--sm { flex:1; }
}

/* ══════════════════════════════════════════════════════════════
   RESULT SECTION HEADER (above each table)
══════════════════════════════════════════════════════════════ */
.rsec-header {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 10px;
  margin-bottom: 12px;
  flex-wrap: wrap;
}

.rsec-actions { display: flex; align-items: center; gap: 8px; }

/* Search field */
.search-field { position:relative; display:flex; align-items:center; }
.search-field__ico { position:absolute; left:11px; color:var(--c-text-4); pointer-events:none; flex-shrink:0; }
.search-field__input {
  height:38px; background:var(--c-surface); border:1.5px solid var(--c-border);
  border-radius:var(--r-lg); padding:0 12px 0 30px; font-family:var(--f); font-size:13px;
  color:var(--c-text-1); width:190px;
  transition:border-color var(--t-f), box-shadow var(--t-n), width var(--t-n) var(--ease);
}
.search-field__input::placeholder { color:var(--c-text-4); }
.search-field__input:hover { border-color:#D1D5DB; }
.search-field__input:focus { outline:none; border-color:var(--c-primary); box-shadow:0 0 0 3px var(--c-primary-ring); width:230px; }

/* ══════════════════════════════════════════════════════════════
   DATA TABLE
══════════════════════════════════════════════════════════════ */
.table-surface {
  padding:0!important; overflow:hidden;
  transition:box-shadow var(--t-n) ease, transform var(--t-n) ease;
}
.table-surface:hover { box-shadow:var(--sh-hov); transform:translateY(-2px); }
.table-wrap { overflow-x:auto; }

/* 메타 광고 · 광고세트 표 전용 스크롤 컨테이너(2026-08-20, Task 4-1).
   .table-wrap은 전역이라 여기 max-height를 주면 저장소의 모든 표가 바뀐다 —
   #metaAdsTopTable을 감싸는 이 표 하나만을 위한 새 클래스를 쓴다.
   overflow-x·overflow-y를 둘 다 auto로 명시한다 — 한 축만 적으면 CSS 명세상
   나머지 visible 축도 auto로 승격되어(:2332 authorTableWrap 주석 참고)
   결과는 같지만, 여기서는 의도(9열까지 늘어나 가로도 필요하다)를 코드에서
   바로 읽히게 둘 다 적는다. .data-table th의 position:sticky; top:0(:1414)이
   이 컨테이너가 생겨야 비로소 자리를 잡는다 — th의 배경(#FAFAFA)이 불투명해
   스크롤되는 행이 그 밑으로 숨는다. */
.meta-ads-table-scroll { max-height:640px; overflow-x:auto; overflow-y:auto; }

/* 캠페인 열(followup-6, 2026-08-21 재수정) — "무가_참여"처럼 공백 없는
   캠페인 이름이 줄바꿈돼 두 줄로 접힌다("무가_참 / 여" 또는 "무가_" / "참여").

   이전 시도(word-break:keep-all)는 안 먹혔다 — 원인을 브라우저 실측(Range API로
   글자별 줄 좌표를 짚어 확인)으로 잡았다: keep-all은 "두 CJK 글자 사이"의
   줄바꿈만 막는 옵션이라, 밑줄(_, CJK가 아니다) 앞뒤의 줄바꿈 기회는 그대로
   남는다 — 실측 결과 keep-all을 켠 채로도 "무가_" / "참여"에서 갈렸다(밑줄
   바로 뒤). 한글 음절 사이가 아니라 밑줄이 진짜 끊기는 자리였다.

   그래서 nowrap으로 바꾼다. 예전엔 "여러 단어로 된 긴 캠페인 이름까지
   강제로 한 줄에 욱여넣는다"는 이유로 nowrap을 피했는데, 그 우려는 이
   열엔 안 맞는다 — 캠페인 이름은 "{유형}_{목적}" 형식의 정해진 아홉 개뿐이고
   전부 7자 안팎이다(작업지시서 결정, 자유 텍스트가 아니다). 옆 광고세트
   열(이 표에서 훨씬 긴 값이 흔하다, :1014 buildAdsetNameCell)은 이 규칙이
   안 닿는다 — nth-child(2)라 두 번째 열(캠페인)에만 걸린다. 그래도 못 접힐
   만큼 긴 값은 이 표가 이미 가진 가로 스크롤(.meta-ads-table-scroll)이 받는다.

   캠페인 열은 columnsFor()가 BASE_COLUMNS 순서를 고정해 두므로
   (meta-ads.js:22-30) 열이 14개로 늘어난 지금도 언제나 두 번째 열이다 —
   실제 14열 렌더에서 nth-child(2)가 캠페인 셀과 같은 엘리먼트를 가리키는지
   다시 확인했다(브라우저 실측) — nth-child(2)로 안전하게 짚을 수 있다. */
#metaAdsTopTable td:nth-child(2) { white-space:nowrap; }

.data-table { width:100%; border-collapse:collapse; font-size:13.5px; }

/* TH */
.data-table th {
  font-family:var(--f-mono); font-size:10.5px; font-weight:600; letter-spacing:0.09em;
  text-transform:uppercase; color:var(--c-text-4); text-align:left;
  padding:12px 16px; white-space:nowrap;
  background:#FAFAFA; border-bottom:1px solid var(--c-divide);
  user-select:none; position:sticky; top:0; z-index:5;
}
.data-table th.is-sortable { cursor:pointer; }
.data-table th.is-sortable:hover { color:var(--c-text-2); }
.data-table th.sort-asc .sort-ico,
.data-table th.sort-desc .sort-ico { opacity:1; color:var(--c-primary); }
/* 기본 아이콘은 아래 화살표다 — 내림차순이 그대로, 오름차순이 뒤집힌 쪽이다.
   반대로 걸면 값이 큰 것부터인데 화살표가 ↑를 가리켜 정반대로 읽힌다. */
.data-table th.sort-asc .sort-ico { transform:rotate(180deg); }
.th-inner { display:inline-flex; align-items:center; gap:5px; }
.sort-ico { opacity:0.3; flex-shrink:0; transition:opacity var(--t-f), transform var(--t-n); }
/* 정렬 헤더는 진짜 <button>이어야 키보드로도 누를 수 있다 — th의 클릭 핸들러엔 Tab이 닿지 않는다.
   th가 이미 정한 글꼴·자간·대문자화를 그대로 물려받아 버튼처럼 보이지 않게 한다. */
button.th-inner {
  border:none; background:none; padding:0; margin:0; cursor:pointer;
  font:inherit; color:inherit; letter-spacing:inherit; text-transform:inherit;
}
button.th-inner:focus-visible { outline:2px solid var(--c-primary); outline-offset:3px; border-radius:2px; }

/* TD */
.data-table td { padding:12px 16px; border-bottom:1px solid var(--c-divide); vertical-align:top; color:var(--c-text-1); }
.data-table tbody tr:last-child td { border-bottom:none; }
.data-table tbody tr { transition:background var(--t-f); animation:fadeUp 260ms var(--ease) both; animation-delay:var(--row-d,0ms); }
.data-table tbody tr:hover td { background:#F9FAFB; }

/* Cell types */
.td-rank { width:52px; text-align:right; padding-right:10px; white-space:nowrap; }
.rank-badge {
  display:inline-flex; align-items:center; justify-content:center;
  min-width:28px; height:22px; border-radius:6px;
  font-family:var(--f-mono); font-size:11.5px; font-weight:700;
}
.rank-badge--1   { background:var(--c-primary); color:#fff; }
.rank-badge--23  { background:var(--c-text-1); color:#fff; opacity:0.7; }
.rank-badge--def { color:var(--c-text-4); font-weight:500; }

.td-title { min-width:180px; max-width:280px; }
.cell-title { font-weight:500; font-size:13.5px; line-height:1.45; white-space:normal; }
.cell-title.is-empty { color:var(--c-text-4); font-style:italic; font-weight:400; }
.cell-author { font-family:var(--f-mono); font-size:10.5px; color:var(--c-text-4); margin-top:3px; }
.cell-category {
  display: inline-flex;
  align-items: center;
  margin-top: 4px;
  font-size: 11px;
  font-weight: 500;
  color: var(--c-text-3);
  background: var(--c-divide);
  border-radius: 4px;
  padding: 1px 7px;
}

.td-desc { min-width:180px; max-width:300px; font-size:13px; color:var(--c-text-3); white-space:normal; line-height:1.5; word-break:keep-all; }
.desc-more-btn {
  display:inline-flex; align-items:center; justify-content:center;
  width:18px; height:18px; border-radius:4px;
  background:var(--c-primary); color:#fff;
  border:none; cursor:pointer; font-size:12px; font-weight:700; line-height:1;
  flex-shrink:0; vertical-align:middle; margin-left:3px;
  transition:background var(--t-f), transform var(--t-f);
}
.desc-more-btn:hover { background:var(--c-primary-hov); transform:scale(1.1); }
.desc-more-btn.is-open { background:var(--c-text-3); }
.td-num { font-family:var(--f-mono); font-size:13px; text-align:right; font-variant-numeric:tabular-nums; white-space:nowrap; color:var(--c-text-3); }
.td-reaction { font-family:var(--f-mono); font-size:13.5px; font-weight:700; text-align:right; font-variant-numeric:tabular-nums; white-space:nowrap; color:var(--c-primary); }

.td-link a {
  display:inline-flex; align-items:center; gap:4px;
  font-family:var(--f-mono); font-size:11.5px; color:var(--c-text-3);
  text-decoration:none; padding:4px 10px; border:1.5px solid var(--c-border);
  border-radius:var(--r-sm); white-space:nowrap;
  transition:color var(--t-f), border-color var(--t-f), background var(--t-f);
}
.td-link a:hover { color:var(--c-primary); border-color:rgba(31,42,68,0.28); background:var(--c-primary-light); }

.td-flag { min-width:160px; max-width:260px; }
.flag-chips { display:flex; flex-wrap:wrap; gap:4px; }
.flag-chip {
  display:inline-flex; align-items:center; gap:5px;
  font-family:var(--f-mono); font-size:11px; font-weight:500;
  background:#FFF8F7; border:1px solid #FECACA; color:#DC2626;
  border-radius:var(--r-xl); padding:3px 9px; white-space:nowrap;
  animation:scaleIn var(--t-n) var(--ease-s) both;
}
.flag-chip__dot { width:4px; height:4px; border-radius:50%; background:currentColor; flex-shrink:0; }
.td-flag.is-empty { font-family:var(--f-mono); font-size:13px; color:var(--c-text-4); }

/* Empty state */
.tbl-empty { padding:64px 24px; text-align:center; }
.tbl-empty__icon { color:#E5E7EB; margin-bottom:16px; }
.tbl-empty__title { font-size:15px; font-weight:600; color:var(--c-text-3); margin-bottom:6px; }
.tbl-empty__desc { font-size:13px; color:var(--c-text-4); }

/* ══════════════════════════════════════════════════════════════
   TOAST
══════════════════════════════════════════════════════════════ */
.toast-stack { position:fixed; top:20px; right:20px; z-index:1000; display:flex; flex-direction:column; gap:8px; max-width:380px; pointer-events:none; }
.toast { display:flex; align-items:flex-start; gap:10px; background:var(--c-surface); border:1px solid var(--c-border); border-radius:var(--r-lg); padding:14px 16px; box-shadow:var(--sh-lg); font-size:13.5px; color:var(--c-text-1); pointer-events:all; animation:slideDown var(--t-n) var(--ease) both; }
.toast--danger { border-color:var(--c-danger-bdr); }
.toast__icon { flex-shrink:0; color:var(--c-danger); margin-top:1px; }
.toast__close { margin-left:auto; background:none; border:none; cursor:pointer; color:var(--c-text-4); flex-shrink:0; padding:2px; border-radius:4px; }
.toast__close:hover { color:var(--c-text-1); }

/* ══════════════════════════════════════════════════════════════
   RESPONSIVE
══════════════════════════════════════════════════════════════ */
@media (max-width:1100px) {
  .score-grid { grid-template-columns: repeat(2, 1fr); }
  .metric-slider > .score-card { flex-basis: calc(50% - 7px); }
}
@media (max-width:860px) {
  .score-grid { grid-template-columns: 1fr 1fr; }
}

@media (max-width:1024px) {
  .main { padding:32px 32px 64px; }
}

@media (max-width:767px) {
  .sidebar { transform:translateX(-100%); transition:transform var(--t-s) var(--ease); box-shadow:none; }
  .sidebar.is-open { transform:translateX(0); box-shadow:8px 0 32px rgba(0,0,0,0.3); }
  .layout-main { margin-left:0!important; }
  /* padding-top은 조건 없이 항상 바꾼다(:503 부근 설명과 같은 이유 — .topbar가
     .appbar보다 낮아 오프셋도 달라져야 한다). padding-left:0은 여전히 필요하다
     — ey-smooth-active(:521)는 "터치가 아니다"만 보고 켜진다. 데스크톱 브라우저
     창을 좁혀 767px 밑으로 내려도(터치가 아니므로) 켜진 채로 남을 수 있는데,
     그 상태에서 desktop용 padding-left:var(--sb-w)가 그대로면 사이드바가
     오프캔버스로 숨은 모바일 레이아웃에 있지도 않은 사이드바 폭만큼 빈 여백이
     생긴다. */
  html.ey-smooth-active #smooth-content { padding-left:0!important; }
  #smooth-content { padding-top:var(--topbar-h); }
  .topbar { display:flex; }
  /* .appbar의 left:0 오버라이드는 여기 두지 않는다 — 아래 MOBILE PASS 블록이
     이미 같은 셀렉터를 다루므로(z-index·height·pointer-events 등) 두 곳에 나눠
     두면 어느 쪽이 이기는지 헷갈린다(:456 부근 .topbar 주석과 같은 원칙). */
  .progress-line { left:0!important; }
  .main { padding:24px 16px 60px; }
  .select-cards { grid-template-columns:1fr; }
  .date-row { grid-template-columns:1fr; }
  .date-sep { display:none; }
  .score-grid { grid-template-columns:1fr; }
  .metric-slider > .score-card { flex-basis: 100%; }
  .results-bar { flex-direction:column; }
  .rsec-header { justify-content:flex-start; }
  .rsec-actions { flex-wrap:wrap; }
  .search-field__input, .search-field__input:focus { width:100%; }
}

@media (max-width:480px) {
  .seg__btn { padding:0 10px; font-size:12px; }
}

/* ══════════════════════════════════════════════════════════════
   REPORT VIEW — 팔로워 성장 리포트
   Dark panel intentionally embedded in the light page:
   chart surface is pure #000 (client requirement).
══════════════════════════════════════════════════════════════ */
:root {
  --rp-surface: #000000;
  --rp-border:  rgba(255,255,255,0.12);
  --rp-text-1:  #FFFFFF;
  --rp-text-2:  #C3C2B7;
  --rp-text-3:  #898781;
}

/* ── Upload view boxes — same rhythm/hover as insight (.ins-card) ── */
#viewReportUpload .surface { transition: box-shadow var(--t-n) ease; }
#viewReportUpload .surface:hover { box-shadow: 0 4px 12px rgba(0,0,0,.06); }
#savedReportsWrap { margin-top: 16px; }

/* ── Staged file list (multi-file, below the dropzone) ── */
.report-filelist {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 12px;
}
.report-filelist[hidden] { display: none !important; }

/* ── Dark chart panel ── */
.report-panel {
  background: var(--rp-surface);
  border: 1px solid var(--rp-border);
  border-radius: var(--r-xl);
  padding: 24px;
  box-shadow: var(--sh-md);
  margin-bottom: 14px;
}
.report-panel__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.report-panel__label {
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--rp-text-3);
  margin-bottom: 4px;
}
.report-panel__period {
  font-family: var(--f-mono);
  font-size: 12px;
  color: var(--rp-text-2);
}

/* ── Legend: @handle (+N), colored to match its line ── */
.report-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 16px;
  margin: 16px 0 8px;
}
.report-legend__item {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-family: var(--f-mono);
  font-size: 12.5px;
  font-weight: 600;
  cursor: default;
  white-space: nowrap;
}
.report-legend__key {
  width: 14px;
  height: 2px;
  border-radius: 1px;
  flex-shrink: 0;
}

/* ── Chart wrap + shared tooltip ── */
.report-chart { position: relative; margin-top: 6px; }
.report-chart svg { display: block; width: 100%; height: auto; }
.report-chart svg:focus-visible { outline: 2px solid rgba(255,255,255,0.5); outline-offset: 2px; border-radius: var(--r-sm); }

.report-tooltip {
  position: absolute;
  z-index: 5;
  pointer-events: none;
  min-width: 190px;
  background: rgba(13,13,13,0.96);
  border: 1px solid var(--rp-border);
  border-radius: var(--r-md);
  padding: 10px 12px;
  box-shadow: var(--sh-lg);
}
.report-tooltip[hidden] { display: none !important; }
.report-tooltip__date {
  font-size: 11px;
  font-weight: 600;
  color: var(--rp-text-3);
  margin-bottom: 7px;
}
.report-tooltip__row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 2.5px 0;
  font-size: 12px;
}
.report-tooltip__key { width: 12px; height: 2px; border-radius: 1px; flex-shrink: 0; }
.report-tooltip__name {
  flex: 1;
  font-family: var(--f-mono);
  color: var(--rp-text-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.report-tooltip__val {
  font-family: var(--f-mono);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--rp-text-1);
  white-space: nowrap;
}

/* ── Per-account stat cards (dark) — horizontal slider ── */
.report-stat-grid {
  display: flex;
  flex-wrap: nowrap;
  gap: 14px;
  overflow-x: auto;
  overflow-y: hidden;
  padding-bottom: 8px;
  scrollbar-width: thin;
  scrollbar-color: rgba(128,128,128,.4) transparent;
}
.report-stat-grid::-webkit-scrollbar { height: 4px; }
.report-stat-grid::-webkit-scrollbar-track { background: transparent; }
.report-stat-grid::-webkit-scrollbar-thumb { background: rgba(128,128,128,.4); border-radius: 2px; }
.report-stat-grid::-webkit-scrollbar-thumb:hover { background: rgba(128,128,128,.65); }
.rstat-card {
  flex: 0 0 260px;
  background: var(--rp-surface);
  border: 1px solid var(--rp-border);
  border-radius: var(--r-xl);
  padding: 18px 20px;
  box-shadow: var(--sh-sm);
  transition: box-shadow var(--t-n) ease, transform var(--t-n) ease;
}
.rstat-card:hover { box-shadow: var(--sh-hov); transform: translateY(-2px); }
.rstat-card__handle {
  display: flex;
  align-items: center;
  gap: 7px;
  font-family: var(--f-mono);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--rp-text-3);
  margin-bottom: 14px;
}
.rstat-card__dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.rstat-row { display: flex; gap: 20px; }
.rstat-block { flex: 1; min-width: 0; }
.rstat-label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--rp-text-3);
  margin-bottom: 5px;
}
.rstat-value {
  font-size: 24px;
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.2;
  white-space: nowrap;
}
.rstat-pct {
  font-family: var(--f-mono);
  font-size: 11.5px;
  color: var(--rp-text-3);
  margin-top: 3px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

@media (max-width:767px) {
  .report-panel { padding: 16px; }
  .rstat-row { gap: 14px; }
  .rstat-value { font-size: 20px; }
}
/* 반응 TOP 20 콘텐츠 섹션 */
.report-top-content {
  margin-top: 18px;
}
.report-top-content__head {
  font-size: 13px; font-weight: 600; color: var(--c-text-1);
  padding: 14px 16px 10px;
  border-bottom: 1px solid var(--c-divide);
}

/* ── Dashboard quick-assign button (empty author cell) ── */
.dash-quick-assign-btn {
  background: none;
  border: 1px dashed transparent;
  color: var(--c-text-4);
  cursor: pointer;
  padding: 2px 6px;
  border-radius: 5px;
  font-size: inherit;
  font-family: inherit;
  line-height: inherit;
  transition: color .15s, border-color .15s, background .15s;
}
.dash-quick-assign-btn:hover {
  color: var(--c-text-2);
  border-color: var(--c-border);
  background: var(--c-bg);
}

/* ── Report dark mode ── */
#main:has(#viewReport:not([hidden])[data-rp-dark]) {
  background: #000000;
}
#viewReport[data-rp-dark] {
  --c-text-1: #f0f0f0;
  --c-text-2: #c0c0c0;
  --c-text-3: #878787;
  --c-border:  rgba(255,255,255,0.12);
}
#viewReport[data-rp-dark] .rstat-card {
  background: #11110B;
  border-color: rgba(255,255,255,0.08);
}

/* ══════════════════════════════════════════════════════════════
   DASHBOARD VIEW — 기사 대시보드
══════════════════════════════════════════════════════════════ */

/* Categorical palette (dataviz skill, light-mode column, fixed order) */
:root {
  --dv-1: #2a78d6; /* blue */
  --dv-2: #1baf7a; /* aqua */
  --dv-3: #eda100; /* yellow */
  --dv-4: #008300; /* green */
  --dv-5: #4a3aa7; /* violet */
  --dv-6: #e34948; /* red */
  --dv-7: #e87ba4; /* magenta */
  --dv-8: #eb6834; /* orange */
  --dv-other: #9CA3AF;
}

/* Filters — one row above the content it scopes.
   position+z-index here matters: .card-enter leaves a lingering
   transform (fadeUp's "to" keyframe) which creates a stacking context,
   trapping the popover panel below later card-enter siblings unless
   this row is explicitly promoted above them. */
.dash-filter-bar { position:relative; z-index:30; display:flex; flex-wrap:wrap; align-items:center; gap:8px; margin-bottom:24px; }
.dash-filter { position:relative; }
.dash-filter__btn {
  display:inline-flex; align-items:center; gap:7px;
  height:38px; padding:0 12px; background:var(--c-surface);
  border:1.5px solid var(--c-border); border-radius:var(--r-lg);
  font-family:var(--f); font-size:13px; color:var(--c-text-1); cursor:pointer;
  transition:border-color var(--t-f), box-shadow var(--t-n);
}
.dash-filter__btn:hover { border-color:#D1D5DB; }
.dash-filter__btn.is-open { border-color:var(--c-primary); box-shadow:0 0 0 3px var(--c-primary-ring); }
.dash-filter__label { color:var(--c-text-3); }
.dash-filter__value { font-weight:600; max-width:160px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.dash-filter__chev { color:var(--c-text-4); flex-shrink:0; transition:transform var(--t-f); }
.dash-filter__btn.is-open .dash-filter__chev { transform:rotate(180deg); }

.dash-filter__panel {
  position:absolute; top:calc(100% + 6px); left:0; z-index:40;
  min-width:220px; max-height:320px; overflow-y:auto;
  background:var(--c-surface); border:1px solid var(--c-border); border-radius:var(--r-lg);
  box-shadow:var(--sh-lg); padding:8px; animation:scaleIn 140ms var(--ease) both;
}
.dash-filter__panel[hidden] { display:none!important; }
.dash-filter__panel-actions { display:flex; gap:6px; padding:2px 4px 8px; border-bottom:1px solid var(--c-divide); margin-bottom:6px; }
.dash-filter__panel-btn { background:none; border:none; color:var(--c-primary); font-size:12px; font-weight:600; cursor:pointer; padding:2px 4px; }
.dash-filter__panel-btn:hover { text-decoration:underline; }
.dash-filter__opt {
  display:flex; align-items:center; gap:8px; padding:7px 6px; border-radius:var(--r-sm);
  font-size:13px; color:var(--c-text-1); cursor:pointer; user-select:none;
}
.dash-filter__opt:hover { background:var(--c-bg); }
.dash-filter__opt input { accent-color:var(--c-primary); width:14px; height:14px; flex-shrink:0; }
.dash-filter__opt span { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
/* 유령 선택 안내(최종 리뷰 Fix 9) — 선택은 남아 있는데 이 패널엔 하나도 안
   보일 때(reconcile의 "안전망 없음" 분기) 왜 아무 줄도 안 켜져 있는지
   설명한다. display를 따로 선언하지 않으므로(p 기본값 block) [hidden] 가드가
   필요 없다 — meta-ads.js가 필요할 때만 이 노드를 만들어 붙이지, hidden
   속성으로 껐다 켰다 하지 않는다. */
.dash-filter__ghost-note {
  margin-top:4px; padding:8px 6px 2px; border-top:1px solid var(--c-divide);
  font-size:11.5px; line-height:1.4; color:var(--c-text-4);
}

/* Stat tiles — figure contract: label + value, proportional figures */
.stat-tile-grid { display:grid; grid-template-columns:repeat(4, 1fr); gap:14px; margin-bottom:28px; }
.stat-tile {
  background:var(--c-surface); border:1px solid var(--c-border); border-radius:var(--r-xl);
  padding:20px 22px; box-shadow:var(--sh-sm); transition:box-shadow var(--t-n) ease, transform var(--t-n) ease;
}
.stat-tile:hover { box-shadow:var(--sh-hov); transform:translateY(-2px); }
.stat-tile__label {
  font-size:11.5px; font-weight:600; letter-spacing:0.06em; text-transform:uppercase;
  color:var(--c-text-4); margin-bottom:10px;
}
.stat-tile__value { font-size:28px; font-weight:700; letter-spacing:-0.02em; color:var(--c-text-1); line-height:1.2; }

/* Panels — surface wrapper shared by chart/table sections */
.dash-grid-2 { display:grid; grid-template-columns:1fr 1fr; gap:16px; margin-bottom:16px; }
.dash-panel { padding:22px; margin-bottom:16px; }
.dash-grid-2 .dash-panel { margin-bottom:0; }
.dash-panel__head { margin-bottom:16px; }
.dash-panel__head--row { display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:12px; }
/* 제목 아래로 컨트롤이 내려오는 헤드 — --row를 빠뜨린 게 아니라 의도된 적층이다(레퍼런스 배치) */
.dash-panel__head--stack { display:flex; flex-direction:column; align-items:flex-start; gap:12px; }
.dash-panel__title { font-size:15px; font-weight:600; color:var(--c-text-1); }

/* 대시보드 탭이 아직 비어 있는 동안의 안내 (서브프로젝트 B에서 제거) */
.dash-home-pending { padding:32px 0; color:var(--c-text-4); font-size:13.5px; }

/* Pie chart + legend */
.dash-pie-wrap { display:flex; align-items:center; gap:24px; flex-wrap:wrap; }
.dash-pie-chart { flex-shrink:0; width:180px; height:180px; position:relative; }
.dash-pie-chart svg { display:block; width:100%; height:100%; }
.dash-pie-legend { flex:1; min-width:160px; display:flex; flex-direction:column; gap:9px; }
.dash-pie-legend__item { display:flex; align-items:center; gap:8px; font-size:13px; color:var(--c-text-2); }
.dash-pie-legend__key { width:10px; height:10px; border-radius:3px; flex-shrink:0; }
.dash-pie-legend__name { flex:1; color:var(--c-text-1); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.dash-pie-legend__val { font-family:var(--f-mono); font-size:12px; color:var(--c-text-3); flex-shrink:0; }
.dash-pie-slice { cursor:pointer; transition:opacity 140ms ease; }
.dash-pie-tooltip {
  position:absolute; pointer-events:none; z-index:20; background:var(--c-surface);
  border:1px solid var(--c-border); border-radius:var(--r-md); box-shadow:var(--sh-lg);
  padding:8px 11px; font-size:12.5px; color:var(--c-text-1); white-space:nowrap;
  transform:translate(-50%, -110%); transition:opacity 100ms ease;
}
.dash-pie-tooltip[hidden] { display:none!important; }

/* Line chart */
.dash-line-chart { position:relative; }
.dash-line-chart svg { display:block; width:100%; height:auto; }
.dash-line-tooltip {
  position:absolute; pointer-events:none; z-index:20; background:var(--c-surface);
  border:1px solid var(--c-border); border-radius:var(--r-md); box-shadow:var(--sh-lg);
  padding:9px 12px; font-size:12.5px; color:var(--c-text-1);
}
.dash-line-tooltip[hidden] { display:none!important; }
.dash-line-tooltip__date { font-weight:600; margin-bottom:4px; }
.dash-line-tooltip__val { font-family:var(--f-mono); font-weight:700; color:var(--c-primary); }

/* Pivot tables — reuse .data-table, add a subtle grand-total row/col */
.data-table td.dt-total, .data-table th.dt-total { font-weight:700; background:var(--c-bg); }
.data-table tr.dt-total-row td { font-weight:700; background:var(--c-bg); }

/* Sticky first column (에디터별 표) — stays in view while scrolling
   horizontally through category columns. */
.data-table .dt-sticky-col {
  position:sticky; left:0; background:var(--c-surface);
  box-shadow:1px 0 0 var(--c-border);
}
.data-table th.dt-sticky-col { z-index:6; background:#FAFAFA; }
.data-table td.dt-sticky-col { z-index:2; }
.data-table tr.dt-total-row td.dt-sticky-col { background:var(--c-bg); }

/* Heatmap cells (팀별 카테고리 분포) — background set inline per-value */
.data-table td.dt-heat { font-weight:600; }

/* Category chip control (top posts) */
.dash-chip-bar { display:flex; flex-wrap:wrap; gap:7px; margin-bottom:16px; }
.dash-chip {
  height:32px; padding:0 14px; border-radius:var(--r-xl);
  background:var(--c-surface); border:1.5px solid var(--c-border);
  font-family:var(--f); font-size:12.5px; font-weight:500; color:var(--c-text-3);
  cursor:pointer; transition:all var(--t-f);
}
.dash-chip:hover { border-color:#D1D5DB; color:var(--c-text-1); }
.dash-chip.is-active { background:var(--c-primary); border-color:var(--c-primary); color:#fff; font-weight:600; }

/* Pagination */
.dash-pagination { display:flex; align-items:center; justify-content:flex-end; gap:12px; margin-top:14px; }
.dash-pagination__label { font-family:var(--f-mono); font-size:12.5px; color:var(--c-text-3); }
.dash-pagination__btn {
  width:30px; height:30px; border-radius:50%; background:var(--c-surface);
  border:1.5px solid var(--c-border); color:var(--c-text-3); cursor:pointer;
  display:flex; align-items:center; justify-content:center; transition:all var(--t-f);
}
.dash-pagination__btn:hover:not(:disabled) { border-color:var(--c-primary); color:var(--c-primary); background:var(--c-primary-light); }
.dash-pagination__btn:disabled { opacity:0.35; cursor:not-allowed; }

@media (max-width:900px) {
  .stat-tile-grid { grid-template-columns:repeat(2, 1fr); }
  .dash-grid-2 { grid-template-columns:1fr; }
}
@media (max-width:600px) {
  .stat-tile-grid { grid-template-columns:1fr; }
  .dash-pie-wrap { flex-direction:column; align-items:stretch; }
  .dash-pie-chart { width:160px; height:160px; margin:0 auto; }
}

/* ── Dashboard header controls ─────────────────────────────────── */
.page-header__top {
  display:flex; align-items:flex-start; justify-content:space-between;
  gap:12px; flex-wrap:wrap;
}
.dash-header-actions { display:flex; align-items:center; gap:8px; flex-shrink:0; }
.dash-date-row {
  display:flex; align-items:center; justify-content:space-between;
  gap:16px; flex-wrap:wrap; margin-top:14px;
}
.dash-date-inputs { display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
.date-sep-line { color:var(--c-text-3); font-size:13px; }
/* 날짜 입력은 늘 필터 칩과 같은 줄에 선다 — .input의 14px 반경·1.5px 테두리를 칩 기준으로 덮는다 */
.input--sm {
  height:30px; padding:0 10px; font-size:12.5px;
  border-width:1px; border-radius:var(--r-sm);
}
.dash-cache-status {
  font-size:11.5px; color:var(--c-text-3); white-space:nowrap;
  font-variant-numeric:tabular-nums;
}
.dash-cache-status.is-refreshing { color:var(--c-primary); }
.dash-cache-status.is-error { color:var(--c-danger); }

/* ── Tab bar (small variant) ───────────────────────────────────── */

/* ── CSV Modal overlay ─────────────────────────────────────────── */
.csv-modal-overlay {
  position:fixed; inset:0; z-index:200;
  background:rgba(0,0,0,0.55); backdrop-filter:blur(3px);
  display:flex; align-items:center; justify-content:center;
  padding:20px;
}
.csv-modal-overlay[hidden] { display:none!important; }
.csv-modal-inner {
  position:relative;
  background:var(--c-bg);
  border-radius:var(--r-xl);
  box-shadow:var(--sh-lg);
  width:100%; max-width:820px;
  max-height:90vh;
  overflow-y:auto;
  padding:28px 28px 24px;
}
.csv-modal-close {
  position:absolute; top:14px; right:14px;
  width:32px; height:32px; border-radius:50%;
  background:none; border:1.5px solid var(--c-border);
  color:var(--c-text-3); cursor:pointer;
  display:flex; align-items:center; justify-content:center;
  transition:all var(--t-f);
}
.csv-modal-close:hover { background:var(--c-surface); color:var(--c-text-1); border-color:var(--c-text-3); }
/* Inside the modal the .view elements should all display:block normally */
.csv-modal-inner .view { display:block!important; }
.csv-modal-inner .view[hidden] { display:none!important; }

@media (max-width:600px) {
  .dash-date-row { flex-direction:column; align-items:flex-start; }
  .csv-modal-inner { padding:20px 16px; }
}

/* ── Dashboard unified header title row ───────────────────────── */
.page-header__title-row {
  display:flex; align-items:center; justify-content:space-between;
  gap:12px; flex-wrap:wrap; margin-top:6px;
}
.dash-header-controls {
  display:flex; align-items:center; gap:8px; flex-shrink:0; flex-wrap:wrap;
}
/* [hidden] 가드 — dash-tabs.js가 이 줄을 dateRowEl.hidden = !def.dateFilter로
   여닫는다. 위 display:flex가 특이도 0-1-0인 UA의 [hidden]{display:none}을 이겨버려
   가드가 없으면 hidden이 서있는 채로 화면엔 계속 flex로 보인다 — 대시보드·메타 광고
   탭에서 아무 일도 안 하는 날짜 필터가 그대로 남는다(2026-08-19 리뷰 Fix 1). */
.dash-header-controls[hidden] { display:none!important; }
.dash-date-compact {
  display:flex; align-items:center; gap:6px;
}
.dash-date-compact input[type="date"] {
  width:130px;
}
/* 날짜 줄의 '적용' 버튼도 칩 높이(30px)에 맞춘다 — 입력 자체는 .input--sm이 이미 맞춰 놓았다 */
.dash-date-compact .btn--sm { height:30px; padding:0 12px; font-size:12.5px; }
.dash-header-sep {
  width:1px; height:20px; background:var(--c-border);
  flex-shrink:0; align-self:center;
}

/* 지금 보고 있는 수치가 언제 것인지. 캐시를 쓰는 이상 필수다 —
   최대 6시간 된 값이 잠깐 보일 수 있기 때문이다. */
.dash-cache-status {
  font-family:var(--f-mono); font-size:11px; color:var(--c-text-3);
  letter-spacing:0.02em; margin-left:12px; white-space:nowrap;
}
.dash-cache-status.is-refreshing { opacity:.6; }
.dash-cache-status.is-error      { color:var(--c-danger,#D33); }

/* ── Performance metric sort chips ────────────────────────────── */
/* 툴바 안에 들어가므로 자체 여백·구분선을 갖지 않는다 (.toolbar가 담당) */

/* ── Top authors panel ─────────────────────────────────────────── */
.dash-top-authors-panel {
  display:grid; grid-template-columns:1fr 1fr; gap:6px;
  margin-top:6px;
}
.dash-top-authors-panel td,
.dash-top-authors-panel th {
  padding:7px 10px;
}
.dash-top-authors-panel .author-rank {
  font-size:11px; color:var(--c-text-4); font-family:var(--f-mono);
  width:28px; text-align:center;
}
@media (max-width:760px) {
  .dash-top-authors-panel { grid-template-columns:1fr; }
  .page-header__title-row { flex-direction:column; align-items:flex-start; }
  .dash-header-controls { width:100%; }
  .dash-date-compact input[type="date"] { width:110px; }
}
/* 날짜 프리셋·필터 버튼은 .fchip으로 통일됐다 — LIST TOOLBAR 섹션 참고 */

/* ── Author table ──────────────────────────────────────────────── */
.td-author-title { max-width:280px; }
/* 본문 줄 — 대시보드 성과 순위(.perf-title-link)와 같은 방식이다.
   본문·펼치기 버튼·유형/유가/보관 칩을 flex 한 줄에 담고, 줄어드는 것은 본문뿐이다.
   예전엔 전부 inline이라 본문이 길면 둘째 줄로 접히고 그 끝에 +와 배지가 매달려,
   행 높이가 두 줄로 벌어지고 +의 가로 위치가 행마다 달랐다. */
.author-title-row { display:flex; align-items:center; gap:5px; min-width:0; }
.author-title-text {
  display:block; min-width:0; flex:0 1 auto;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
  color:var(--c-text-1);
}
/* 칩과 버튼은 줄어들지 않는다 — 줄어드는 쪽은 본문이어야 한다.
   margin-left는 flex gap과 겹쳐 두 번 벌어지므로 이 줄 안에서만 없앤다. */
.author-title-row > .author-type-tag,
.author-title-row > .status-chip,
.author-title-row > .perf-expand-btn { flex-shrink:0; margin-left:0; }
.td-author-title a { color:var(--c-text-1); }
.td-author-title a:hover { color:var(--c-primary); text-decoration:underline; }
.author-type-tag {
  display:inline-block; margin-left:6px;
  padding:0 6px; height:18px; line-height:18px;
  border-radius:var(--r-full); font-size:10px; font-weight:600;
  background:var(--c-border); color:var(--c-text-2);
  vertical-align:middle;
}
.author-type-tag--reel { background:rgba(var(--c-primary-rgb,42,120,214),0.22); color:var(--c-primary); }
.row--assigned { opacity:0.65; }
/* 작성자 지정 썸네일 */
.author-thumb {
  width: 44px; height: 44px; object-fit: cover;
  border-radius: 6px; display: block;
  background: var(--c-bg-3);
}

/* ── Report page navigation ─────────────────────────────────────── */
.report-legend-bar {
  padding:10px 0 4px;
  border-bottom:1px solid var(--c-divide);
  margin-bottom:16px;
}
.report-legend-bar .report-legend {
  flex-wrap:wrap; gap:8px 16px;
}

@media (max-width:600px) {
  .fchip { height:28px; padding:0 9px; font-size:12px; }
  .dash-date-compact { flex-wrap:wrap; }
}

/* 계정 스코어 카드 */
.scorecards { display:grid; grid-template-columns:repeat(4,1fr); gap:12px; margin-bottom:20px; }
@media (max-width:720px){ .scorecards{ grid-template-columns:repeat(2,1fr);} }
.scorecard { padding:16px 18px; border-radius:14px; }
.scorecard__head { display:flex; align-items:center; gap:7px; color:var(--c-text-3); font-size:12px; font-weight:600; }
.scorecard__icon { display:inline-flex; }
.scorecard__value { margin-top:8px; font-family:var(--f-mono); font-size:26px; font-weight:700; color:var(--c-text-1); letter-spacing:-.5px; }
.scorecard__hint {
  margin-left:auto; font-size:10px; font-weight:600; letter-spacing:0;
  padding:1px 6px; border-radius:999px; cursor:help;
  background:var(--c-bg); border:1px solid var(--c-border); color:var(--c-text-4);
}
/* 직전 기간 대비 증감 — 증가 초록 / 감소 빨강 */
/* 증감 줄 = [화살표][숫자][회색 기준 라벨].
   색은 화살표와 숫자에만 준다 — '전월 대비'까지 초록/빨강이면 무엇이 오르내렸는지가
   아니라 줄 전체가 경고처럼 읽힌다. */
.scorecard__delta {
  display:flex; align-items:center; gap:4px;
  margin-top:5px; font-size:11.5px; font-weight:600;
  color:var(--c-text-4);
}
.scorecard__delta-ico { flex-shrink:0; }
.scorecard__delta-val { font-variant-numeric:tabular-nums; }
.scorecard__delta-label { color:var(--c-text-4); font-weight:500; }
.scorecard__delta.is-up   .scorecard__delta-ico,
.scorecard__delta.is-up   .scorecard__delta-val { color:var(--c-success); }
.scorecard__delta.is-down .scorecard__delta-ico,
.scorecard__delta.is-down .scorecard__delta-val { color:var(--c-danger); }
/* 총계를 못 구했을 때의 '—' — 본문 색 그대로면 실제 수치처럼 읽힌다 */
.scorecard__value.is-empty { color:var(--c-text-4); }

/* ── 부스팅 집행 후보 ─────────────────────────────────────────────
   캠페인 두 종류를 색으로 가른다. 참여는 반응(사람의 행동), 인지도는 도달(노출) 쪽이라
   서로 다른 계열을 쓴다 — 같은 색조의 농담 차이로 두면 표에서 구분이 안 된다. */
.ad-badge {
  display:inline-flex; align-items:center; height:20px; padding:0 8px;
  border-radius:var(--r-sm); font-size:11.5px; font-weight:700;
  letter-spacing:-.01em; white-space:nowrap;
}
.ad-badge--engagement { background:#DCFCE7; color:#15803D; }
.ad-badge--awareness  { background:#DBEAFE; color:#1D4ED8; }
/* 칩과 배수는 한 줄에 붙어 있어야 한다. 줄바꿈을 허용해 두면 웹폰트가 늦게 도착하는
   첫 프레임에 폭이 모자라 배수가 아래로 떨어졌다가 다시 붙는 게 눈에 보인다.
   flex + nowrap으로 애초에 접힐 수 없게 만든다. */
.ad-campaign {
  display:flex; align-items:center; gap:6px;
  white-space:nowrap; flex-wrap:nowrap;
}
/* 배수 — 겨우 넘겼는지 압도적인지. 캠페인 이름 다음에 읽혀야 하므로 한 단계 낮춘다 */
.ad-ratio {
  font-family:var(--f-mono); font-size:11.5px;
  font-weight:600; color:var(--c-text-3); flex-shrink:0;
}
/* 남은 시간 — 창이 닫히기 전에만 뜬다. 지금 움직여야 한다는 신호라 강조색을 쓴다 */
.ad-left {
  font-size:11px; font-weight:600; color:var(--c-warning, #B45309);
  white-space:nowrap;
}
/* 경과 칸도 접히면 안 된다 — '11시간 남음'이 두 줄로 갈라진다 */
.data-table td.ad-elapsed { white-space:nowrap; }
/* 판정 근거 — 48시간 기록이 없어 더 긴 창으로 대신 본 건. 근사라는 걸 숨기지 않되,
   경고는 아니므로 조용한 회색 칩으로 둔다. 강조색을 쓰면 '남은 시간'과 헷갈린다. */
.ad-basis {
  display:inline-flex; align-items:center; height:18px; padding:0 6px;
  border-radius:var(--r-sm); background:var(--c-bg-3, #F1F5F9);
  font-family:var(--f-mono); font-size:10.5px; font-weight:600;
  color:var(--c-text-4); white-space:nowrap; cursor:help;
}
/* 집행 완료 토글 — 체크 '원'.
   네모 체크박스는 "여러 개 골라 일괄 처리"로 읽힌다(작성자 지정 표가 실제로 그렇다).
   여기서 필요한 건 선택이 아니라 상태라서 할 일 목록처럼 둥근 체크로 둔다.
   미집행은 조용히(빈 원), 완료만 눈에 띄게(초록 채움) — 표의 대부분은 미집행이다. */
.ad-done {
  position:relative;
  display:inline-flex; align-items:center; justify-content:center;
  width:16px; height:16px; padding:0;
  border:1.25px solid var(--c-border); border-radius:50%;
  background:transparent; color:transparent;   /* 체크는 완료 전엔 안 보인다 */
  cursor:pointer; transition:background var(--t-f), border-color var(--t-f), color var(--t-f);
}
/* 보이는 원은 작게 두되 누를 수 있는 범위는 넓힌다 — 16px은 손가락에도 마우스에도 좁다 */
.ad-done::before { content:''; position:absolute; inset:-7px; }
/* 누를 수 있다는 신호 — 체크를 옅게 미리 보여준다 */
.ad-done:hover { border-color:#54C47B; color:#A7E0BC; }
.ad-done:focus-visible { outline:2px solid var(--c-primary); outline-offset:2px; }
.ad-done:disabled { cursor:default; opacity:.5; }
.ad-done.is-done {
  background:#54C47B; border-color:#54C47B; color:#fff;
}
.ad-done.is-done:hover { background:#43B26A; border-color:#43B26A; color:#fff; }

/* 집행한 행은 목록에 남기되 한눈에 끝난 건으로 보이게. 지우지 않는 이유는
   "안 돌린 것"과 구분이 안 되면 같은 글을 두 번 집행하기 때문이다.
   첫 칸(토글)은 흐리게 하지 않는다 — 해제하려면 눌러야 하니까. */
.data-table tr.ad-row--done > td { opacity:.45; }
.data-table tr.ad-row--done > td:first-child { opacity:1; }
/* 섹션 구분 라벨 */
.section-eyebrow {
  font-size:11px; font-weight:700; letter-spacing:.04em; text-transform:uppercase;
  color:var(--c-text-4); margin:0 0 8px;
}

/* 관리자 포스팅 관리 */
.alert--warn { background:#fff7ed; color:#9a3412; border:1px solid #fed7aa; padding:10px 14px; border-radius:10px; font-size:13px; }
.dash-panel__hint { font-size:12px; color:var(--c-text-3); }

/* 유가(#광고) / 보관 칩 */
.paid-chip             { background:#fee2e2 !important; color:#b91c1c !important; border-color:#fecaca !important; }
/* ── 보관 상태 (게시물 관리) — 상태가 콘텐츠 타입 배지보다 먼저 인지되도록 ── */
.status-chip {
  box-sizing:border-box;
  display:inline-flex; align-items:center; justify-content:center; gap:4px;
  height:18px; padding:0 8px; margin-left:6px;             /* 타입 칩(.author-type-tag)과 동일 높이·간격 */
  border-radius:var(--r-full);
  font-size:10.5px; font-weight:700; line-height:1; letter-spacing:-0.1px;
  white-space:nowrap; vertical-align:middle;
}
.status-chip svg { width:10px; height:10px; flex-shrink:0; display:block; }
.status-chip--pending { background:#FEF3C7; color:#92400E; }                              /* 보관 필요 · warning filled */
.status-chip--done    { background:transparent; color:#15803D; border:1px solid var(--c-success); font-weight:600; } /* 보관 완료 · success outlined */
.status-chip--purge   { background:#FEE2E2; color:#991B1B; }                              /* 영구 삭제 예정 · danger filled */

/* ── 작성자 지정: 툴바·헤더열 고정 ──────────────────────────────
   901건짜리 목록에서 아래로 스크롤해 고른 뒤 「작성자 지정」을 누르려고 맨 위로
   되돌아가는 왕복을 없앤다.

   헤더열(.data-table th)은 원래도 position:sticky; top:0 이었지만, .appbar가
   같은 자리(top:0, z-index:200, 높이 --appbar-h)를 먼저 차지해 그 밑으로 숨어
   보이지 않았다. 안 붙은 게 아니라 안 보였던 것이다.

   층은 앱바(200) > 툴바(20) > 헤더열(5) 순이다 — 스크롤 중 툴바가 헤더열을
   덮어야지 반대면 안 된다.

   전역 .data-table th는 건드리지 않는다. 대시보드 등 다른 표의 모습이 바뀐다. */
#authorTableWrap .dash-panel__head {
  position: sticky;
  top: var(--appbar-h);
  z-index: 20;
  background: var(--c-surface);
  /* 패널 안쪽 여백(.dash-panel의 padding:22px)을 뚫고 좌우로 꽉 차게 —
     안 그러면 스크롤되는 행의 모서리가 툴바 옆으로 비어져 나온다. */
  margin: -22px -22px 16px;
  padding: 22px 22px 12px;
  border-radius: var(--r-xl) var(--r-xl) 0 0;
}

/* 헤더열이 sticky로 붙으려면 '스크롤되는 조상'이 페이지여야 한다.
   .table-wrap의 overflow-x:auto가 그걸 가로챈다 — CSS 명세상 한 축이 auto면
   다른 축의 visible이 auto로 승격돼, 이 래퍼가 세로로도 스크롤 컨테이너가 된다.
   래퍼는 세로로 스크롤되지 않으니 헤더열은 붙을 자리가 없어 그냥 흘러간다.
   원래 있던 top:0 sticky가 한 번도 동작하지 않았던 진짜 이유다.

   데스크톱에서는 표가 가로로 넘치지 않으므로 가로 스크롤을 포기해도 잃는 게
   없다. 좁은 화면에서는 아래 미디어쿼리가 도로 켠다. */
#authorTableArea { overflow: visible; }

/* 툴바 높이는 JS(ResizeObserver)가 --ey-author-toolbar-h에 넣는다.
   좁은 창에서 .dash-panel__head--row가 flex-wrap으로 두 줄이 되므로 고정값을
   쓸 수 없다. 변수가 비면 0px로 흘러가 살짝 겹칠 뿐 화면이 깨지지는 않는다. */
#authorTable th {
  top: calc(var(--appbar-h) + var(--ey-author-toolbar-h, 0px));
}

@media (max-width: 767px) {
  /* 모바일 헤더는 한 줄(.topbar 높이)이다 — 툴바는 그 바로 밑에 붙는다. */
  #authorTableWrap .dash-panel__head { top: var(--topbar-h); }

  /* 좁은 화면에서는 표가 가로로 넘쳐 가로 스크롤을 되살린다. */
  #authorTableArea { overflow-x: auto; }

  /* 헤더열의 sticky는 여기서 반드시 꺼야 한다.
     overflow-x:auto를 주면 CSS가 overflow-y도 auto로 올려 이 래퍼가 스크롤
     컨테이너가 된다. 그러면 th의 sticky가 페이지가 아니라 이 래퍼를 기준으로
     걸린다 — 세로로는 스크롤되지 않는 컨테이너라, 헤더열이 위에 붙지도 않고
     같이 흘러가지도 않은 채 목록 한가운데 떠 있게 된다(실측: 스크롤 0에서
     top 508px, 900까지 내리면 -392px).

     예전 주석은 "여기서 sticky가 동작하지 않는다"고만 적고 규칙은 그대로 뒀다.
     동작하지 않는 정도가 아니라 잘못된 자리에 박힌다. 꺼서 표의 첫 줄로
     되돌린다 — 모바일에서 긴 목록을 훑는 일이 드물어 이 거래를 택했다. */
  #authorTable th { position: static; }
}

/* ScrollSmoother 활성 상태(html.ey-smooth-active, 2026-08-21) 전용 오버라이드.
   위 두 sticky(.dash-panel__head :2320·#authorTable th :2345)는 페이지(문서) 스크롤을
   기준으로 붙는다. ScrollSmoother는 #smooth-content에 transform을 걸어 스크롤을
   흉내내는데, transform이 걸린 조상은 그 후손의 sticky containing block이 돼버려서
   — 이 둘의 진짜 스크롤 기준이 이제 "페이지"가 아니라 "#smooth-content"가 되고,
   그 상자 자신은 스크롤되지 않으므로(스크롤은 그 바깥 뷰포트에서 일어난다) 붙을
   자리를 잃는다(:356 .appbar 주석과 같은 현상 — 여긴 fixed로 못 바꾼다, 흐름 중간에서
   "펼침→고정"으로 전환돼야 하는 진짜 sticky라서).

   .meta-ads-table-scroll(:1395)이 이미 쓰는 해법을 그대로 가져온다 — sticky는
   "가장 가까운 스크롤 컨테이너"를 기준으로 삼으므로, #authorTableWrap 자신을
   overflow-y:auto인 로컬 스크롤 컨테이너로 만들면 그 컨테이너가 #smooth-content의
   transform보다 먼저 발견돼 sticky 계산이 거기서 끝난다 — transform과 완전히
   무관해진다. #authorTableArea는 여전히 overflow:visible(:2340)이라 더 안쪽에
   또 다른 스크롤 컨테이너가 끼어들지 않는다.

   top 값도 이제 --appbar-h를 빼야 한다 — 기준이 페이지가 아니라 이 박스 자신의
   스크롤 뷰포트이므로, 앱바 높이는 이미 박스의 화면상 위치에 반영돼 있어 또
   더하면 그만큼 안쪽으로 밀려 들어간다.

   max-height는 meta-ads-table-scroll(640px)과 같은 방식의 손으로 정한 고정값이다
   — 이 박스 위 페이지 제목·탭·필터 줄의 실측 높이를 반응형으로 좇지는 않지만,
   sticky가 동작하려면 "유한한(스크롤되는) 컨테이너"라는 성질만 있으면 되고 그
   경계의 정확한 px 값은 중요하지 않다. 모바일(max-width:767px)에는 안 켠다 —
   smoothTouch:false(smooth-scroll.js)라 터치에서는 애초에 transform이 안 걸리고,
   #authorTable th는 위 블록에서 이미 position:static으로 꺼 뒀다. */
html.ey-smooth-active #authorTableWrap {
  overflow-y: auto;
  max-height: calc(100vh - var(--appbar-h) - 220px);
  min-height: 320px;
}
html.ey-smooth-active #authorTableWrap .dash-panel__head { top: 0; }
html.ey-smooth-active #authorTable th { top: var(--ey-author-toolbar-h, 0px); }

/* 헤더 겹침 버그(2026-08-21, 실사용 서버에서 발견) — 위 세 규칙만으로는 부족했다.
   .dash-panel__head의 margin-top:-22px(:2348)는 .dash-panel의 padding-top(22px,:1946)을
   "상쇄"해 툴바가 패널 위 테두리까지 꽉 차 보이게 하는 트릭이다. 이게 성립하려면
   음수 마진이 자유롭게 패딩 영역으로 파고들 수 있어야 하는데, CSS position:sticky
   스펙은 top 오프셋이 있는 스티키 요소를 "가장 가까운 스크롤 컨테이너의 패딩 상자"
   밖으로 못 나가게 강제로 clamp한다(overflow 클리핑이 아니라 스펙 자체의 제약 —
   실측으로 확인: 가로(-22px, left/right)는 top 오프셋이 없어 clamp 대상이 아니므로
   그대로 패딩을 뚫고 나가는데, 세로(top:0)만 정확히 패딩 경계에서 멈춘다).
   #authorTableWrap이 페이지가 아니라 자기 자신(overflow-y:auto)이 되면서 바로 그
   "가장 가까운 스크롤 컨테이너"가 됐다 — 툴바의 실제 고정 위치가 의도보다 22px+테두리만큼
   아래로 밀려나고, 그 자리에 아무것도 덮지 않는 22px짜리 틈이 스크롤뷰 맨 위에 항상
   남는다. 표가 스크롤되다 이 틈과 겹치는 행이 오면 그 행이 툴바 위로 삐져나와 보인다
   — 사용자가 실서버에서 본 스크린샷이 정확히 이 상태였고, elementFromPoint 기반
   진단(scripts/smoke-sticky-scroll-browser.js의 새 assertion 참고)으로 재현·확인했다.

   같은 저장소의 .wl-panel__head(:2553)는 이 함정에 안 걸린다 — 그쪽은 패딩을 가진
   요소(.wl-panel__body)와 스크롤 컨테이너(#wlPanel, 패딩 없음)가 애초에 다른 요소라
   clamp 경계(스크롤 컨테이너의 패딩 상자)에 패딩이 아예 없다. #authorTableWrap은
   그 둘(.dash-panel의 패딩 소유자 + 로컬 스크롤 컨테이너)이 한 요소로 합쳐져 있어
   똑같은 트릭이 깨진다.

   해법은 패딩·음수 마진을 "상쇄"하는 짝 자체를 없애는 것이다 — 좌우·아래는 clamp
   대상이 아니라(top 오프셋만 지정돼 있으므로) 그대로 둬도 안전하니 :2348의
   margin:-22px -22px 16px는 손대지 않고, 위쪽만 패딩을 0으로 만들어 상쇄할 대상
   자체를 지운다. z-index 스케일(:2337 앱바 200 > 툴바 20 > 헤더열 5)은 그대로 —
   이건 스택 순서 문제가 아니라 sticky clamp 문제라 새 z-index가 필요 없다. */
html.ey-smooth-active #authorTableWrap { padding-top: 0; }
html.ey-smooth-active #authorTableWrap .dash-panel__head { margin-top: 0; }

/* Row 상태 강조 — 작성자 지정 목록 + 관리자 워크리스트 (좌측 4px accent bar, 레이아웃 시프트 없음) */
#authorTable tbody td:first-child, #authorTable thead th:first-child,
#worklistTable tbody td:first-child, #worklistTable thead th:first-child { border-left:4px solid transparent; }
#authorTable tbody tr.row--archive-pending,
#worklistTable tbody tr.row--archive-pending { background:rgba(217,119,6,0.05); }         /* 아주 옅은 warning 배경 */
#authorTable tbody tr.row--archive-pending > td:first-child,
#worklistTable tbody tr.row--archive-pending > td:first-child { border-left-color:#D97706; }
#authorTable tbody tr.row--archive-done > td:first-child { border-left-color:#16A34A; }   /* 완료 · success accent, 배경은 기본 */

/* ── 업무량 트래커 ─────────────────────────────────────────────────────── */
/* 항목 2 — 상단 요약 줄(#wlHeadline 팀 평균·기준선·과부하·조회 월)을 지우면서
   .wl-headline* 규칙 전체를 함께 정리했다. 쓰는 곳이 없다. */

/* 항목 3 — 컨트롤 줄 높이 정렬. .field/.field__label에는 원래 규칙이 없어(이 화면에서만
   쓰는 이름) <label class="field"><span class="field__label">…</span><input class="input">
   가 세로로 쌓였다(라벨 줄바꿈 + .input의 기본 height:52px). 그 결과 옆의 팀 탭(~40px)·
   .btn--sm(34px)보다 훨씬 커 보였다. 라벨을 입력 왼쪽에 작게 붙이고 입력 자체를
   .btn--sm과 같은 높이(34px)로 낮춰 네 컨트롤의 높이·세로 중심을 맞춘다
   (.dash-header-controls는 이미 align-items:center라 나머지는 저절로 정렬된다). */
.dash-header-controls .field {
  display:flex; align-items:center; gap:8px;
}
.dash-header-controls .field__label {
  font-size:12px; font-weight:600; color:var(--c-text-3); white-space:nowrap;
}
/* 헤더 필터 줄의 입력·버튼도 칩(30px)에 맞춘다 — 한 줄 안에서 높이가 갈리면 울퉁불퉁해 보인다 */
.dash-header-controls .field .input {
  width:auto; min-width:140px; height:30px; padding:0 10px;
  font-size:12.5px; border-width:1px; border-radius:var(--r-sm);
}
.dash-header-controls .btn--sm { height:30px; padding:0 12px; font-size:12.5px; }

/* 팀 필터는 LIST TOOLBAR의 .seg를 그대로 쓴다 — 예전 .wl-teamtabs/.tab은 생김새가
   .seg와 사실상 같은 중복이라 걷어냈다(workload.js renderTeamTabs가 .seg__btn을 그린다). */

.wl-kpi { display:grid; grid-template-columns:repeat(auto-fit,minmax(150px,1fr)); gap:12px; margin-bottom:20px; }
.wl-kpi__card { border:1px solid var(--c-border); border-radius:14px; padding:14px 16px; background:var(--c-surface); }
.wl-kpi__label { font-size:11px; font-weight:600; letter-spacing:.04em; color:var(--c-text-3); margin-bottom:4px; }
.wl-kpi__value { font-size:22px; font-weight:700; line-height:1; }
.wl-kpi__sub { font-size:11px; color:var(--c-text-3); margin-top:4px; }

.wl-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(300px,1fr)); gap:12px; }
.wl-card { border:1px solid var(--c-border); border-radius:16px; padding:16px; background:var(--c-surface); text-align:left; cursor:pointer; transition:box-shadow .15s, transform .15s; width:100%; }
.wl-card:hover { box-shadow:0 6px 20px rgba(0,0,0,.08); transform:translateY(-1px); }
.wl-card__top { display:flex; align-items:flex-start; justify-content:space-between; gap:10px; margin-bottom:10px; }
.wl-card__rank { display:inline-flex; align-items:center; justify-content:center; width:24px; height:24px; border-radius:999px; background:var(--c-text-1); color:var(--c-surface); font-size:11px; font-weight:700; flex-shrink:0; }
.wl-card__name { font-size:15px; font-weight:600; }
.wl-card__score { font-size:22px; font-weight:700; line-height:1; text-align:right; }
.wl-card__scorelabel { font-size:11px; color:var(--c-text-3); text-align:right; }
.wl-card__meta { display:flex; flex-wrap:wrap; gap:8px; font-size:11px; color:var(--c-text-3); margin-bottom:10px; }
/* 세그먼트 색은 workload.js SOURCE_META[*].bar가 정한다(불투명한 짙은 색이라 윤곽선이
   필요 없다 — 회색 테두리는 오히려 색을 흐린다).
   트랙은 데이터가 없을 때만 보이므로 옅게 낮춰 빈 카드가 시끄럽지 않게 한다. */
.wl-bar { display:flex; height:6px; border-radius:999px; overflow:hidden; margin-bottom:8px;
          background:color-mix(in srgb, var(--c-border) 45%, var(--c-surface)); }
.wl-bar span { display:block; height:100%; }
.wl-chip { display:inline-flex; align-items:center; gap:4px; padding:2px 8px; border-radius:999px; font-size:11px; font-weight:600; }
/* 소스 글자색 — 값은 이 :root 한 곳에만 둔다. 면(面) 색(#2dd4bf/#fbbf24/#f87171 —
   workload.js SOURCE_META[*].color·workload-panel.js SOURCE_COLOR, 칩·패널 카드 배경용
   — 막대·도넛·범례는 별개 축인 SOURCE_META[*].bar를 쓴다)과 달리
   글자로 쓸 때는 이 토큰을 쓴다: 원색 그대로는 실효 배경(원색 18 알파 ▷ 흰 표면 =
   #ebfbf9/#fff9ea/#fef2f2) 위 대비가 1.6~2.5:1로 묻힌다. 값은 각 원색의 hue·채도를
   유지한 채 그 실효 배경 위 WCAG 대비 4.5:1을 처음 넘는 지점까지만 어둡게 한 것
   (calendar 4.55:1 · ad 4.51:1 — scripts/test-workload.js가 상대 휘도로 계산해
   단언한다). article만 사용자 지정으로 원색을 그대로 쓰는 예외다(아래 참고).
   카드 그리드 소스 칩(아래 .wl-chip--*)과 상세 패널 소스
   카드 퍼센트(workload-panel.js .wl-srcbtn__pct 인라인 var())가 함께 참조한다. */
:root {
  --wl-src-text-calendar:#1a7f73;
  /* 기사만 예외 — 사용자 지정 값. 실효 배경(#fff9ea) 대비 1.67:1로 WCAG 4.5:1에
     못 미치므로 아래 대비 테스트에서 예외 처리했다 — 칩·패널 글자가 옅어 보이는 건
     의도된 것이지 회귀가 아니다. 이 토큰 하나가 카드 그리드의 소스 칩(.wl-chip--article)과
     상세 패널의 소스 카드(.wl-srcbtn__pct) 양쪽을 동시에 결정한다. */
  --wl-src-text-article:#ffb600;
  --wl-src-text-ad:#e10b0b;
}
/* 소스 칩 — 배경은 소스 면 색+8자리 hex 알파(REF와 이 파일의 다른 소스색 배경
   (.wl-srcbtn 등)이 이미 쓰는 표기), 글자는 위 --wl-src-text-* 토큰. */
.wl-chip--calendar { background:#2dd4bf18; color:var(--wl-src-text-calendar); }
.wl-chip--article  { background:#fbbf2418; color:var(--wl-src-text-article); }
.wl-chip--ad       { background:#f8717118; color:var(--wl-src-text-ad); }
.wl-chip--badge    { background:rgba(236,72,153,.12); color:#db2777; }
/* 항목 9 — 팀 칩 색 구분. .wl-chip--team은 공통 지오메트리만 남기고, 색은 팀별
   변형(--team1/--team2)이 담당한다. 값은 services/workload/aggregator.js가 응답에
   실어 보내는 team id('team1'|'team2')와 services/workload/config.js TEAMS의 색
   (team1 #6366f1 · team2 #0ea5e9)을 그대로 재사용한다 — 서버가 색 자체를 응답에
   내려주지 않아 같은 값을 클라이언트 상수로 복제했다(TEAM_LABEL과 같은 이유·같은 방식).
   원색은 흰 배경 위 칩 텍스트로 쓰기엔 대비가 약해, 칩 배경은 12% 알파의 원색 +
   글자는 각 색 계열의 진한 톤(4.5:1 이상)을 쓴다(team1 대비 5.41:1, team2 5.26:1). */
.wl-chip--team1 { background:rgba(99,102,241,.12); color:#4f46e5; }
.wl-chip--team2 { background:rgba(14,165,233,.12); color:#0369a1; }
/* 예외 캘린더 항목(출장·종일 촬영 분배) 근거 칩 — REF CalendarItemCard의 amber-100/amber-700.
   호박색 계열은 :root에 대응 토큰이 없어 리터럴 hex를 쓴다. */
.wl-chip--exception { background:#fef3c7; color:#b45309; }
/* 미매핑 사용자 뱃지 — 미매핑 목록(노란 계열)과 색 체계를 맞춘다.
   #FEF9C3 / #854D0E에 대응하는 :root 토큰이 없어 리터럴 hex를 쓴다. */
.wl-chip--unmapped { background:#fef9c3; color:#854d0e; }

/* 백드롭 페이드 + 패널 슬라이드인. hidden(display:none) 토글은 workload-panel.js
   open()/close()에서 그대로 즉시 처리한다 — transition이 아니라 animation을 쓰는 이유가
   이것이다: animation은 요소가 display:none에서 다시 렌더되는 순간 처음부터 자동 재생되므로
   (.toast/.tab-panel/.card-enter와 같은 이 파일의 기존 관례), open()/close()의 hidden
   동기 계약(테스트 다수가 close() 직후 hidden===true를 바로 확인한다)을 전혀 건드리지
   않고도 REF EditorDetailPanel의 translate-x-full → translate-x-0 슬라이드를 재현한다.
   닫힘 쪽은 REF처럼 별도로 애니메이션하지 않는다(열림만 재생) — 이 파일의 다른 모달·
   토스트류도 전부 같은 비대칭(입장만 애니메이션)이다. */
/* z-index:255/260 — 헤더 단(.appbar, z-index:200)보다 위에 있어야 상세 패널의 닫기(X)
   버튼이 헤더에 가려 계정 드롭다운 버튼이 클릭되는 사고가 나지 않는다. 다만 사이드바
   (.sidebar, z-index:300)보다는 아래로 둬 사이드바가 계속 패널을 덮게 한다. */
.wl-panel-backdrop { position:fixed; inset:0; background:rgba(0,0,0,.4); z-index:255; animation:wl-panel-fade .2s ease both; }
.wl-panel {
  position:fixed; top:0; right:0; height:100%; width:100%; max-width:560px;
  background:var(--c-surface); box-shadow:-8px 0 32px rgba(0,0,0,.18); z-index:260; overflow-y:auto;
  animation:wl-panel-slide-in var(--t-s) var(--ease) both;
}
.wl-panel__body { padding:24px; }

@keyframes wl-panel-slide-in { from { transform:translateX(100%); } to { transform:translateX(0); } }
@keyframes wl-panel-fade { from { opacity:0; } to { opacity:1; } }
@media (prefers-reduced-motion:reduce) {
  .wl-panel, .wl-panel-backdrop { animation:none; }
}

/* 상세 패널 sticky 헤더 (REF EditorDetailPanel L240) — 항목이 수십 개인 에디터도 스크롤
   중에 이름·팀 칩·닫기 버튼을 잃지 않는다. 스크롤 컨테이너는 #wlPanel(overflow-y:auto)
   이므로 top:0이면 패널 상단에 붙는다. 본문(.wl-panel__body)의 24px 패딩을 음수 마진으로
   상쇄해 패널 전체 폭을 쓴다 — 안 하면 헤더 좌우·위로 24px 틈이 생겨 스크롤된 내용이
   그 틈으로 비쳐 보인다. 반투명 배경+블러는 REF(bg-white/95 + backdrop-blur-sm)와 같고,
   대응하는 :root 토큰이 없어 리터럴 rgba를 쓴다. */
.wl-panel__head {
  position:sticky; top:0; z-index:10;
  display:flex; align-items:center; justify-content:space-between; gap:12px;
  margin:-24px -24px 0; padding:16px 24px;
  background:rgba(255,255,255,.95); backdrop-filter:blur(4px); -webkit-backdrop-filter:blur(4px);
  border-bottom:1px solid var(--c-border);
}
.wl-panel__headmain { min-width:0; }
.wl-panel__nameline { display:flex; align-items:center; flex-wrap:wrap; gap:8px; }
.wl-panel__name  { font-size:20px; font-weight:700; margin:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.wl-panel__email { font-size:12px; margin:2px 0 0; }
/* 닫기(X) 버튼 — REF의 h-8 w-8 rounded-full text-gray-400 hover:bg-gray-100. 회색 계열은
   토큰과 값이 일치하는 것만 토큰을 쓴다(#9ca3af=--c-text-4). hover 배경 #f3f4f6은 대응
   토큰이 없어 리터럴 hex. */
.wl-panel__close {
  flex-shrink:0; display:inline-flex; align-items:center; justify-content:center;
  width:32px; height:32px; border-radius:999px; border:none; background:none;
  color:var(--c-text-4); cursor:pointer;
  transition:background-color var(--t-f) var(--ease), color var(--t-f) var(--ease);
}
.wl-panel__close:hover { background:#f3f4f6; color:var(--c-text-2); }
/* 일평균·총 업무량 블록 — 헤더에서 분리돼 본문 첫 블록이 됐다(REF L277-282 text-right). */
.wl-panel__scores { display:flex; justify-content:flex-end; text-align:right; margin-top:20px; }

/* 예외·배지·일반 캘린더 항목 카드 — REF CalendarItemCard의 amber/pink/gray-50 계열.
   .wl-kpi__card(var(--c-border)·var(--c-surface))보다 뒤에 선언돼 같은 특이도에서 이긴다.
   색은 REF와 동일한 Tailwind 팔레트 값이고 대응 토큰이 없어 리터럴 hex를 쓴다. */
.wl-item--exception { border-color:#fef3c7; background:#fffbeb; } /* amber-100 / amber-50 */
.wl-item--badge     { border-color:#fce7f3; background:#fdf2f8; } /* pink-100 / pink-50 */
/* 일반(비예외) 캘린더 항목 — REF는 테두리 없이 bg-gray-50만 준다. border-color만
   투명 처리해 예외/배지 카드와 상자 크기(1px 테두리 두께)는 그대로 맞춘다. */
.wl-item--plain      { border-color:transparent; background:#f9fafb; }
/* 휴가 항목 카드 — REF LeaveItemCard의 violet-100/violet-50. */
.wl-item--leave      { border-color:#ede9fe; background:#f5f3ff; }

/* 근무일 & 휴가 지표 그룹 카드(REF WorkingDaysSection) — 근무 가능일·휴가 차감·실제
   근무일 3칸과 휴가 내역을 옅은 보라 배경 하나로 묶어, "이 지표들은 한 세트"임을
   낱개 카드보다 먼저 보여준다. 내부 3칸(wl-workdays__box)은 그 배경 위에서도 카드로
   도드라지도록 흰 배경 + 같은 보라 테두리를 쓴다. */
.wl-workdays { margin-top:18px; border:1px solid #ede9fe; background:rgba(245,243,255,.4); border-radius:16px; padding:20px; }
.wl-workdays__title { margin:0 0 14px; font-size:11px; font-weight:600; letter-spacing:.06em; color:var(--c-text-4); }
.wl-workdays__grid { display:grid; grid-template-columns:repeat(3,1fr); gap:12px; }
.wl-workdays__box { border:1px solid #ede9fe; background:#fff; border-radius:12px; padding:16px; }
.wl-workdays__leaves { margin-top:12px; display:grid; gap:6px; }

/* 유가 섹션 요약 박스 (REF EditorDetailPanel L330-335). REF는 보라 계열이지만 이 플랫폼은
   유가 소스 색 #f87171 계열(red-200/red-50/red-700)로 맞춘다 — SOURCE_COLOR.ad와 같은 축이어야
   섹션 머리 점·요약 버튼과 한 색으로 읽힌다. #b91c1c on #fef2f2 대비 5.9:1. */
.wl-adsummary {
  display:flex; flex-wrap:wrap; gap:4px 16px; margin-bottom:8px;
  border:1px solid #fecaca; background:#fef2f2; color:#b91c1c;
  border-radius:12px; padding:10px 14px; font-size:12px;
}

.wl-banner { border-radius:12px; padding:12px 14px; margin-bottom:16px; font-size:13px; }
.wl-banner--warn { border:1px solid rgba(245,158,11,.35); background:rgba(245,158,11,.08); color:#b45309; }
.wl-banner--error { border:1px solid rgba(229,72,77,.35); background:rgba(229,72,77,.08); color:#b91c1c; }

/* 경고 아이콘을 앞세운 배너(소스 상태) — 아이콘은 첫 줄에 맞춰 살짝 내려 붙인다 */
.wl-banner--icon { display:flex; align-items:flex-start; gap:8px; }
.wl-banner--icon > svg { flex-shrink:0; margin-top:2px; }
.wl-banner__body ul { margin:4px 0 0 16px; list-style:disc; list-style-position:outside; font-size:12px; }

/* stale 배너 — 최신 조회가 실패해 이전 정상 데이터를 붙들고 있을 때.
   네 색 모두 :root 토큰과 정확히 일치한다(#BFDBFE / #EFF6FF / #1D4ED8 / #3B82F6). */
.wl-banner--info {
  display:flex; align-items:center; justify-content:space-between; gap:12px;
  border:1px solid var(--c-info-border); background:var(--c-info-bg);
  color:var(--c-info-text); font-size:14px;
}
.wl-banner__msg { display:flex; align-items:center; gap:8px; }
.wl-banner__msg > svg { flex-shrink:0; }
.wl-banner__at { margin-left:8px; font-size:12px; color:var(--c-info); }
.wl-banner__retry {
  flex-shrink:0; border-radius:var(--r-sm); border:1px solid #93c5fd;
  background:var(--c-surface); padding:4px 12px; font-size:12px; font-weight:500;
  color:var(--c-info-text); cursor:pointer;
  transition:background var(--t-f) var(--ease);
}
.wl-banner__retry:hover { background:var(--c-info-bg); }

/* 재조회 중 인라인 인디케이터 — 전체 스피너 대신 컨트롤 줄에만 작게 둔다 */
.wl-loading { display:inline-flex; align-items:center; gap:6px; font-size:12px; color:#9ca3af; }
.wl-loading[hidden] { display:none; }
.wl-loading__spin {
  width:12px; height:12px; border-radius:999px;
  border:2px solid #d1d5db; border-top-color:#4b5563;
  animation:wl-spin .7s linear infinite;
}
@keyframes wl-spin { to { transform:rotate(360deg); } }

/* 재조회 중 데이터 영역 — 내용을 지우지 않고 흐리게 + 조작 차단 */
.wl-dim { pointer-events:none; opacity:.6; transition:opacity var(--t-n) var(--ease); }

@media (prefers-reduced-motion: reduce) {
  .wl-loading__spin { animation:none; }
  .wl-dim { transition:none; }
}

/* workload.js/html이 이미 폭넓게 쓰는 보조 텍스트 클래스인데 정의가 누락돼 있었다
   (이전 태스크에서 도입만 되고 스타일은 빠짐) — 이번 차트·패널이 더 많이 끌어쓰므로
   여기서 함께 채운다. */
.muted { color:var(--c-text-3); }

/* 업무량 바 차트 — 기준선은 컨테이너가 아니라 각 막대 트랙 내부에 그린다.
   그리드 열 폭(이름 56px + 일평균 48px + 총점 56px + 상위% 64px)에 영향받지 않고 막대
   스케일과 정확히 같은 좌표계를 쓰기 위해서다.
   --baseline 기본값 0% — render()가 다음 프레임까지 최종 위치 세팅을 미루므로(REF
   BaselineLine), 마운트 첫 페인트는 이 기본값에서 시작해 "0% → 최종값"으로 실제 변화가
   생겨야 .wl-chartrow__track::after의 left 트랜지션이 재생된다. */
.wl-chart { position:relative; display:grid; gap:6px; --baseline:0%; }
/* 행은 <button>이다(workload-chart.js buildRow) — hover 확장 패널에 키보드·터치로도
   닿아야 하기 때문이다. 기본 버튼 크롬(테두리·배경·글꼴·가운데 정렬)을 지워 이전
   <div> 배치와 픽셀이 같도록 맞춘다. */
.wl-chartrow {
  display:grid; grid-template-columns:56px 1fr 48px 56px 64px; align-items:center; gap:10px;
  cursor:pointer; width:100%; margin:0; padding:0; border:0; background:none; border-radius:8px;
  font:inherit; color:inherit; text-align:left; appearance:none; -webkit-appearance:none;
  transition:background-color .15s;
}
.wl-chartrow:hover { background:#f9fafb; }
.wl-chartrow:hover .wl-chartrow__track { outline:1px solid var(--c-border); }
/* 키보드 포커스는 반드시 보여야 한다 — 확장 패널이 열리는 유일한 키보드 경로다.
   :focus-visible이라 마우스 클릭 때는 링이 뜨지 않는다. */
.wl-chartrow:focus-visible { outline:2px solid var(--c-info); outline-offset:3px; border-radius:6px; }
.wl-chartrow__name { font-size:13px; font-weight:600; color:var(--c-text-2); text-align:right; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.wl-chartrow__track { position:relative; height:20px; border-radius:999px; background:#f3f4f6; }
/* 기준선(::after)은 트랙 위아래로 3px씩 튀어나오게 그린다(top/bottom:-3px). overflow:hidden이
   트랙에 있으면 이 오버행이 그대로 잘려 렌더링되지 않는다(Finding 11) — 클리핑이 실제로
   필요한 막대 채움 쪽으로 옮긴다. fill은 자식이 없어 지금은 잘릴 게 없지만, 트랙 폭을
   넘는 값이 들어와도 항상 안전하게 갇히도록 방어적으로 유지한다.
   left 트랜지션(REF BaselineLine)은 마운트 직후 0%에서 실제 위치로 슬라이드한다. */
.wl-chartrow__track::after {
  content:''; position:absolute; top:-3px; bottom:-3px; left:var(--baseline, 50%);
  /* 막대(z-index:1)와 예상 구간(0)보다 위 — 기준선이 막대에 가리면 안 된다 */
  z-index:2;
  border-left:2px dashed #6366f1; transition:left .7s cubic-bezier(0.33,1,0.68,1);
}
/* z-index:1 — 예상 구간(z-index:0)보다 위에 얹혀야 한다. 예상 구간이 이음새 틈을
   메우려고 실측 막대 끝보다 왼쪽으로 조금 더 들어와 있는데, 그 겹친 부분을 실측 막대가
   가려야 현재 점수의 경계가 또렷하게 남는다. */
.wl-chartrow__fill { display:block; position:relative; z-index:1; height:100%; border-radius:999px; overflow:hidden; transition:width .5s; }
/* 예상 구간 — 실측 막대 끝에서 예상치 지점까지. 실측 막대보다 옅은 색으로 시작해
   흐려지므로 둘의 경계가 색으로 읽힌다. 왼쪽은 각지고 오른쪽만 둥글린다 — 왼쪽 끝은
   어차피 실측 막대 뒤에 깔려 보이지 않는다(buildRow의 margin-left 참고).
   pointer-events:none — 트랙 위 어디를 눌러도 행 버튼이 받아야 한다. */
.wl-chartrow__proj {
  position:absolute; top:0; height:100%; z-index:0;
  border-radius:0 999px 999px 0;
  pointer-events:none;
  transition:width .5s, left .5s;
}
.wl-chartrow__value { font-size:12px; font-weight:700; text-align:right; font-variant-numeric:tabular-nums; }
/* 총점·상위 n% — REF의 뒤 두 열. 둘 다 보조 정보라 작고 옅은 회색이다. */
.wl-chartrow__total { font-size:11px; color:var(--c-text-4); text-align:right; white-space:nowrap; font-variant-numeric:tabular-nums; }
.wl-chartrow__pct { font-size:11px; color:var(--c-text-4); white-space:nowrap; }
/* --baseline은 .wl-chart에 한 번만 설정하고(workload-chart.js render()) 각 트랙이 상속받는다. */

@media (prefers-reduced-motion:reduce) {
  .wl-chartrow { transition:none; }
  .wl-chartrow__track::after { transition:none; }
  .wl-chartrow__fill { transition:none; }
  .wl-chartrow__proj { transition:none; }
}

/* 차트 행 hover 확장 패널 — 행 바로 아래(같은 .wl-chart 그리드의 다음 행)에 끼워 넣는다.
   transform·scale을 쓰지 않으므로 hover된 행 자체의 위치·크기는 변하지 않는다.
   회색 계열은 :root에 대응 토큰이 없어(#f3f4f6 / #f9fafb / #1f2937 / #4b5563 / #6b7280)
   리터럴 hex를 쓴다 — 없는 토큰명을 쓰면 값이 비어 패널이 통째로 깨진다. */
.wl-chartpanel { margin:0 8px 8px; overflow:hidden; border-radius:16px; border:1px solid #f3f4f6; background:#f9fafb; padding:20px; }
.wl-chartpanel__head { display:flex; flex-wrap:wrap; align-items:center; column-gap:12px; row-gap:4px; margin-bottom:16px; }
.wl-chartpanel__name { font-size:14px; font-weight:700; color:#1f2937; }
.wl-chartpanel__meta { font-size:12px; color:#9ca3af; }
.wl-chartpanel__grid { display:grid; grid-template-columns:368px 180px 1fr; gap:20px; align-items:start; }
.wl-chartpanel__donutcol { display:flex; align-items:center; gap:12px; }
.wl-chartpanel__donutsvg { flex-shrink:0; }
.wl-chartpanel__col { min-width:0; }
.wl-chartpanel__col--trend { flex-shrink:0; }
.wl-chartpanel__label { margin:0 0 8px; font-size:12px; font-weight:500; color:#9ca3af; }
.wl-chartpanel__sentences { display:grid; gap:6px; }
.wl-chartpanel__sentences p { margin:0; font-size:12px; line-height:1.625; color:#4b5563; overflow-wrap:break-word; }
/* 도넛 범례 — 폭 96px 고정. 긴 라벨은 말줄임으로 잘라 도넛을 밀어내지 않게 한다. */
.wl-donutlegend { width:96px; flex-shrink:0; display:flex; flex-direction:column; gap:8px; font-size:12px; }
.wl-donutlegend__row { display:flex; align-items:center; gap:6px; }
.wl-donutlegend__dot { width:10px; height:10px; border-radius:999px; flex-shrink:0; }
.wl-donutlegend__label { flex:1; color:#6b7280; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.wl-donutlegend__nums { text-align:right; font-variant-numeric:tabular-nums; }
.wl-donutlegend__pct { font-weight:600; color:#374151; }
.wl-donutlegend__pts { font-size:10px; color:#9ca3af; }
.wl-trend__empty { margin:4px 0 0; font-size:12px; color:#9ca3af; }
/* 368+180+1fr는 사이드바가 있는 좁은 화면에서 넘친다. .wl-chartpanel은 overflow:hidden이라
   넘친 내용이 잘리므로 세 컬럼을 위아래로 쌓는다(데스크톱 배치는 그대로).
   (표기 통일: 이 파일의 미디어 쿼리 대다수(RESPONSIVE 절 이하 12곳)가 쓰는
   `max-width:` 무공백 표기를 따른다 — 업무량 작업이 추가한 이 쿼리만 달랐다.) */
@media (max-width:1100px) {
  .wl-chartpanel__grid { grid-template-columns:1fr; }
}

/* 항목 7 — 퍼센타일 바·z-score 바(옛 .wl-meter__head/__title/__value/__foot/__track/
   __fill, .wl-zbar__*)를 지우고 종류별 건수 요약표로 교체했다. .wl-meter 자신과
   --excluded/__note/__notesub는 statsExcludedNote()가 여전히 쓰므로 남긴다(통계 모수
   제외 안내 — "값이 있어야 할 곳이지만 산출되지 않았다"를 점선 테두리로 표시). */
.wl-meter { margin-top:18px; }
.wl-meter--excluded { border:1px dashed var(--c-border); border-radius:var(--r-sm); padding:10px 12px; }
.wl-meter__note    { margin:0; font-size:12px; font-weight:600; color:var(--c-text-2); }
.wl-meter__notesub { margin:4px 0 0; font-size:11px; line-height:1.5; color:var(--c-text-4); }

/* 항목 7 — 종류별 건수 요약표. th는 소스명(캘린더/기사/유가), td는 그 소스의 칩들
   (wl-chip--calendar/--article/--ad/--badge 재사용 — summaryChip() 참고). 표 자체는
   시각적 틀이 아니라 의미 구조라 border는 행 구분선 정도로만 옅게 준다. */
.wl-itemsummary { margin-top:18px; }
.wl-itemsummary__title {
  margin:0 0 8px; font-size:11px; font-weight:600; letter-spacing:.04em; color:var(--c-text-4);
}
.wl-itemsummary__table { width:100%; border-collapse:collapse; }
.wl-itemsummary__row th {
  text-align:left; vertical-align:top; white-space:nowrap;
  padding:10px 14px 10px 0; font-size:12px; font-weight:600; color:var(--c-text-3);
}
.wl-itemsummary__row td { padding:10px 0; }
.wl-itemsummary__row + .wl-itemsummary__row th,
.wl-itemsummary__row + .wl-itemsummary__row td { border-top:1px solid var(--c-border); }
.wl-itemsummary__row .wl-chip { margin:0 6px 6px 0; }

/* 상세 패널 소스 비율 3칸 — 클릭하면 같은 소스의 상세 섹션으로 패널 내부만 스크롤한다.
   배경색은 소스색 + 알파(hex 8자리 `18`)를 인라인으로 넣는다. */
.wl-srcgrid { margin-top:18px; display:grid; grid-template-columns:repeat(3,1fr); gap:12px; }
.wl-srcbtn {
  display:block; width:100%; border:none; border-radius:12px; padding:12px 14px;
  text-align:center; cursor:pointer; transition:opacity var(--t-n) var(--ease);
}
.wl-srcbtn:hover { opacity:.8; }
.wl-srcbtn__pct { display:block; font-size:24px; font-weight:700; line-height:1; }
.wl-srcbtn__label { display:block; margin-top:4px; font-size:12px; font-weight:500; color:var(--c-text-3); }
.wl-srcbtn__score { display:block; margin-top:2px; font-size:11px; color:var(--c-text-4); }

/* 미매핑 사용자 목록 행 — 노란 계열에 대응하는 :root 토큰이 없어 리터럴 hex를 쓴다. */
.wl-unmapped__row {
  width:100%; display:flex; align-items:center; justify-content:space-between; gap:10px;
  text-align:left; border-radius:12px; border:1px solid #fef08a; background:#fefce8;
  padding:16px; cursor:pointer; transition:background-color var(--t-n) var(--ease);
}
.wl-unmapped__row:hover { background:#fef9c3; }
.wl-unmapped__name { font-size:14px; font-weight:500; color:#1f2937; }
.wl-unmapped__score { font-size:14px; font-weight:600; color:var(--c-text-2); white-space:nowrap; }

/* 항목 6 — 미매핑 토글 버튼의 ▾ 글리프는 aria-hidden이라 펼침 상태를 스스로 알리지
   못한다(실제 상태는 aria-expanded가 맡는다 — workload.js syncUnmappedToggle). 여기서는
   그 속성값을 그대로 눈에 보이는 신호로 옮겨, 열렸을 때 화살표가 180도 돌아 위를
   향하게 한다. */
.wl-unmapped__caret { display:inline-block; transition:transform var(--t-n) var(--ease); }
#wlUnmappedBtn[aria-expanded="true"] .wl-unmapped__caret { transform:rotate(180deg); }
@media (prefers-reduced-motion:reduce) {
  .wl-unmapped__caret { transition:none; }
}

/* 미매핑 목록 펼침 — hidden(display:none) 토글은 workload.js의 클릭 핸들러가 여전히
   동기로 처리한다(여러 테스트가 클릭 직후 .hidden 값을 바로 확인한다) — 이 규칙은 그
   토글이 즉시 끊기지 않고 부드럽게 보이도록 시각만 덧입힌다. transition이 아니라
   animation을 쓴다: display:none에서 다시 렌더되는 순간 자동 재생되므로(.toast/
   .tab-panel/.card-enter와 같은 이 파일의 기존 관례), hidden 동기 계약을 건드리지 않고도
   "즉시 전환"이 "여닫기 애니메이션"이 된다(닫힘은 REF의 다른 모달류처럼 애니메이션하지
   않는다 — 열릴 때만 재생). 2000px는 실제 콘텐츠 높이를 재지 않는 실용적 상한이다 —
   이 조직 규모에서 미매핑 사용자가 그보다 많이 쌓일 일은 없다. */
#wlUnmappedList { display:grid; gap:8px; margin-top:8px; overflow:hidden;
                  animation:wl-unmapped-expand .25s ease both; }
/* [hidden] 가드 — display를 지정하는 순간 UA의 [hidden]{display:none}(특이도 0-1-0)을
   이겨버려 hidden 속성이 무시된다. 이 파일의 다른 컴포넌트(.view/.alert/.tab-panel 등)가
   모두 같은 가드를 두는 이유다. 이게 없으면 목록이 기본 닫힘으로 그려지지 않고, 토글을
   눌러도 aria-expanded와 캐럿만 바뀔 뿐 목록이 그대로 남는다. */
#wlUnmappedList[hidden] { display:none!important; }
@keyframes wl-unmapped-expand {
  from { max-height:0; opacity:0; }
  to   { max-height:2000px; opacity:1; }
}
@media (prefers-reduced-motion:reduce) {
  #wlUnmappedList { animation:none; }
}

/* 조회 결과가 비었을 때(전체 0명 / 팀 필터로 0명) 그리드 자리에 놓는 안내 문구 */
.wl-empty { padding:80px 0; text-align:center; color:var(--c-text-4); font-size:14px; }

/* 보관 토글 버튼 활성(선택 항목이 이미 보관 상태) */
.btn.is-active { background:#fef9c3; color:#854d0e; border-color:#fde68a; }

/* 유가 집행 확인 모달 — 이 저장소에 범용 모달이 없어 이 화면 전용으로 둔다 */
.pad-modal {
  position: fixed; inset: 0; z-index: 60;
  display: flex; align-items: center; justify-content: center;
  background: rgba(0, 0, 0, .45); padding: 16px;
}
.pad-modal[hidden] { display: none; }
.pad-modal__panel { width: 100%; max-width: 380px; padding: 18px; }
.pad-modal__title { margin: 0 0 3px; font-size: 14px; font-weight: 600; }
/* 대상 게시물 한 줄 요약 — 캡션이 길어도 두 줄에서 끊는다 */
.pad-modal__target {
  margin: 0 0 14px; font-size: 11.5px; line-height: 1.5; color: var(--c-text-3);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.pad-modal__row { display: flex; flex-direction: column; gap: 4px; margin-bottom: 11px; font-size: 11.5px; }
.pad-modal__row--check { flex-direction: row; align-items: center; gap: 7px; cursor: pointer; }
.pad-modal__label { color: var(--c-text-3); }
.pad-modal__input {
  width: 100%; padding: 7px 9px; font-size: 12.5px;
  border: 1px solid var(--c-border, #ddd); border-radius: 6px; background: var(--c-bg, #fff);
  color: inherit;
}
/* 경고 문구. .alert 를 쓰지 않는 이유는 index.html의 주석 참고 — 그쪽은 flex라
   <strong>이 flex 항목이 되어 문장이 세로로 쪼개진다. 여기선 평범한 블록이다. */
.pad-modal__warn {
  display: block; margin: 12px 0 0; padding: 9px 11px; border-radius: 8px;
  background: #fff7ed; color: #9a3412; border: 1px solid #fed7aa;
  font-size: 11.5px; line-height: 1.6;
}
.pad-modal__warn strong { font-weight: 600; }
.pad-modal__actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 14px; }

/* 집행 상태 뱃지.
   white-space:nowrap + flex-shrink:0 이 반드시 있어야 한다 — .pad-status-wrap(아래)
   안에서 옆에 긴 사유 문구가 붙으면 flex 기본 shrink가 배지를 짓눌러, 한글은
   공백 없이도 아무 글자 사이에서 줄바꿈되는 성질 때문에 "집행불가" 같은 4글자
   배지 안에서 줄바꿈이 일어나 배지 혼자 두 줄로 벌어지고 행 높이를 밀어 올린다
   (followup-3 실측: 39px까지 벌어짐 — 정상 21.6px의 거의 2배, 안에서 줄바꿈된 것). */
.pad-badge { display: inline-block; padding: 2px 8px; border-radius: 999px; font-size: 11px; font-weight: 600; white-space: nowrap; flex-shrink: 0; }
.pad-badge--ok   { background: rgba(84, 196, 123, .15); color: #2f8b52; }   /* 집행됨 — 부스팅 완료 표시(#54C47B)와 같은 계열 */
.pad-badge--wait { background: rgba(234, 179, 8, .18);  color: #92400e; }   /* 대기 — status-chip--pending과 같은 amber 계열 */
.pad-badge--warn { background: rgba(230, 162, 60, .16);  color: #9a6410; }
.pad-badge--mute { background: rgba(128, 128, 128, .14); color: var(--c-text-3); }
.pad-badge--bad  { background: rgba(239, 68, 68, .16);   color: #dc2626; }

/* 유가 집행 표의 이력 삭제 버튼 — btn--ghost 위에 위험 색만 얹는다(별도 danger
   버튼 클래스가 이 저장소에 없어 여기서 스코프를 좁혀 정의한다) */
.pad-del-btn { color: var(--c-danger); }
.pad-del-btn:not(:disabled):hover { color: var(--c-danger-text, var(--c-danger)); border-color: var(--c-danger); }

/* 유가 집행 표 — 행 높이 축소 (followup-3).
   기본 .data-table td 패딩(12px)에 34px짜리 버튼·두 줄로 쌓인 상태 배지(배지+사유)가
   얹혀 행 높이가 58~65px까지 벌어져 있었다. 콘텐츠 성과 순위 표(기본 패딩,
   텍스트 한 줄 기준 약 46px)보다도 촘촘하게 줄인다 — 버튼은 .btn--xs(22px)로,
   사유는 배지 옆(아래 .pad-status-wrap)으로 옮겨 그 무엇도 텍스트 한 줄보다
   커지지 않게 한다. */
.pad-table td, .pad-table th { padding-top: 7px; padding-bottom: 7px; }
.pad-table td.pad-col-check, .pad-table th.pad-col-check { width: 32px; padding-right: 0; }
/* 2번(이름)·3번(유형) 열 사이 간격을 좁힌다(followup-3) — 기본 좌우 16px씩 더하면
   둘 사이가 32px로 벌어져 다른 열보다 유독 헐렁해 보였다. nth-child 대신 클래스를
   쓰는 이유는 체크박스 열이 관리자에게만 추가돼 열 순번이 사람마다 달라지기
   때문이다(dashboard.js가 이 두 클래스를 이름·유형 셀에 직접 붙인다). */
/* white-space:nowrap이 꼭 있어야 한다 — 이름은 한글이고(YYMMDD_키워드(자동)) 이
   열은 폭을 따로 박아두지 않아서, 표 auto-layout이 열 폭을 좁게 잡으면 한글은
   공백 없이도 글자 사이에서 줄바꿈되는 성질 때문에(라틴 문자처럼 공백에서만
   끊기지 않는다) 이름이 통째로 두 줄로 접혀 행 높이가 벌어진다(followup-3 실측:
   58px까지 — 다른 열의 nowrap 처리와 같은 이유로 여기도 필요하다). */
.pad-table td.pad-col-name, .pad-table th.pad-col-name { padding-right: 6px; white-space: nowrap; }
.pad-table td.pad-col-kind, .pad-table th.pad-col-kind { padding-left: 6px; }

/* 상태 배지 + 집행불가 사유를 한 줄에 옆으로 붙인다(followup-3) — 예전엔 사유가
   배지 아래 <small>로 쌓여 행 높이를 밀어 올렸다. 사유가 없으면(blockedMsg가
   null, 음원 차단이 그렇다) .pad-status-reason 자체를 안 만든다 — 빈 자리도
   남기지 않는다(dashboard.js). */
.pad-status-wrap { display: flex; align-items: center; gap: 6px; min-width: 0; }
.pad-status-reason {
  font-size: 11px; color: var(--c-text-4); white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; max-width: 180px;
}

/* 유가 집행 표의 일괄 삭제 버튼 — followup-3-2 §10부터 .dash-panel__head--row
   안(표 위 자체 헤더 줄, 우측 상단)에 있다. 그 전엔 표 바로 위에 홀로 떠 있어
   margin-bottom으로 스스로 간격을 책임졌지만, 지금은 .dash-panel__head의
   margin-bottom(16px)이 그 역할을 한다 — 여기 남겨두면 같은 줄의 제목과 세로로
   어긋나 보인다. hidden 처리는 .fchip[hidden](:1312)이 이미 담당한다. */

/* 캠페인 칩 — 작성자 칩(hsl 해시 색)과 구분되는 고정 팔레트를 쓴다. 지금은
   '참여' 하나뿐이지만(CAMPAIGN_LABEL, routes/adExec.js) 인지도 캠페인이 붙을
   때를 대비해 perf-type-badge와 같은 data-c 스위치 방식으로 둔다. */
.campaign-chip {
  display: inline-flex; align-items: center; padding: 2px 8px; border-radius: 999px;
  font-size: 11px; font-weight: 600; white-space: nowrap;
}
.campaign-chip[data-c="참여"] { background: #e0e7ff; color: #4338ca; }
@media (prefers-color-scheme: dark) {
  .campaign-chip[data-c="참여"] { background: rgba(67, 56, 202, .28); color: #c7d2fe; }
}

/* ═══════════════════════════════════════════════════════════════════
   MOBILE PASS — 2026-08-12
   docs/superpowers/specs/2026-08-12-mobile-ui-pass-design.md

   모바일 정리분을 한 블록에 모았다. 파일 곳곳에 480·600·640·720·760·
   1100px가 흩어져 있어, 컴포넌트 옆에 끼워 넣으면 어느 규칙이 이기는지
   읽는 사람이 매번 되짚어야 한다. 맨 끝에 두면 순서로 확실히 이긴다.
   이번 작업분은 전부 767px 하나로 맞춘다.
   ═══════════════════════════════════════════════════════════════════ */

/* ── C4. 지정 버튼 — pick 모드에서만 한 덩어리로 ──────────────────────
   #assignBtn과 #assignDropTrigger는 둘 다 .btn--primary라 :hover가 각각
   걸린다. 한쪽에 올리면 그쪽만 밝아져 두 버튼으로 읽힌다.

   묶는 건 pick 모드뿐이다. assign 모드에서 왼쪽은 '바로 지정', ▾는
   '메뉴 열기'로 하는 일이 다르다 — 거기서까지 한 덩어리로 보이면
   메뉴를 열려다 지정해 버린다. 되돌리려면 해제를 또 눌러야 한다.
   data-mode는 author.js updateActionBtns()가 버튼과 래퍼에 함께 쓴다. */
   .btn--primary:hover는 배경·그림자에 translateY(-1px)까지 준다. 한쪽만
   들리는 게 "따로 논다"의 정체다 — 그래서 들림과 그림자는 래퍼가 통째로
   맡고, 자식은 배경만 바꾼다. */
#assignBtnWrap[data-mode="pick"] {
  border-radius: 6px;
  transition: transform var(--t-f), box-shadow var(--t-f);
}
#assignBtnWrap[data-mode="pick"]:hover {
  transform: translateY(-1px);
  box-shadow: 0 6px 20px rgba(31,42,68,0.26);
}
#assignBtnWrap[data-mode="pick"]:hover > .btn--primary:not(:disabled) {
  background: var(--c-primary-hov);
  box-shadow: none;
  transform: none;
}
/* 같은 일을 하는 두 짝이니 가운데 금도 지운다 */
#assignBtnWrap[data-mode="pick"] > #assignDropTrigger {
  border-left-color: transparent;
}

/* ── C1. 기간 접기 (작성자 지정 필터 바) ─────────────────────────────
   데스크톱에선 아무것도 바뀌면 안 된다. 래퍼를 display:contents로 두면
   .dash-date-compact가 여전히 .dash-filter-bar의 직계 flex 아이템이라
   margin-left:auto가 그대로 먹는다 — 지금 배치가 픽셀 그대로 남는다. */
.period-picker { display: contents; }
.period-picker__trigger { display: none; }
.period-picker__caret { transition: transform var(--t-n) var(--ease); }
.period-picker[data-open="true"] .period-picker__caret { transform: rotate(180deg); }

@media (max-width: 767px) {

  /* 모바일에서만 진짜 접기가 된다. 시트는 트리거 아래 오른쪽에 붙는다 —
     필터 바 오른쪽 끝에 있는 버튼에서 열리므로 왼쪽으로 펼쳐야 화면 밖으로
     안 나간다. */
  .period-picker {
    display: inline-flex;
    position: relative;
    margin-left: auto;
  }
  .period-picker__trigger {
    display: inline-flex; align-items: center; gap: 5px;
    font-variant-numeric: tabular-nums;
  }
  .period-picker__sheet {
    position: absolute; top: calc(100% + 6px); right: 0; z-index: 40;
    display: none;
    flex-wrap: wrap; justify-content: flex-end; gap: 6px;
    width: max-content; max-width: min(88vw, 320px);
    margin-left: 0 !important;   /* HTML 인라인 margin-left:auto를 끈다 */
    padding: 10px;
    background: var(--c-surface); border: 1px solid var(--c-border);
    border-radius: 10px; box-shadow: 0 8px 24px rgba(0,0,0,.18);
  }
  .period-picker[data-open="true"] .period-picker__sheet { display: flex; }
  .period-picker__sheet .toolbar__sep { display: none; }
  .period-picker__sheet input[type="date"] { width: 118px; }
  .period-picker__sheet #authorApplyBtn { margin-left: auto; }

  /* ── 헤더 한 줄로 ─────────────────────────────────────────────────
     모바일에서는 헤더가 두 줄이었다. .topbar(☰+로고)가 고정되고, 그 아래
     .appbar(알림+계정)가 static으로 흘러 회색 띠 하나를 더 차지했다. 화면이
     좁을수록 본문에 쓸 세로 공간이 아까운데 56px을 아이콘 두 개에 쓰고 있었다.

     .appbar를 .topbar 줄과 겹친다 — 둘 다 position:fixed;top:0(:356·:456, 2026-08-21
     ScrollSmoother 도입 전엔 sticky + margin-top 음수로 같은 자리에 끌어올렸는데,
     이제 둘 다 애초에 fixed;top:0이라 저절로 겹친다). .appbar는 배경·테두리를
     지워 투명하게 둔다. 결과는 [☰ 로고 ......... 🔔 계정] 한 줄.

     DOM을 옮기지 않는 이유: 인사이트는 브랜드 자리가 두 모드(#topbarNormal 외)라
     6개 페이지의 마크업이 같지 않다. 옮기면 그 차이를 페이지마다 다시 맞춰야 한다.

     pointer-events: .appbar가 투명하게 덮으므로 그대로 두면 ☰·로고가 안 눌린다.
     덮개는 클릭을 통과시키고 실제 버튼이 있는 슬롯만 되받는다. */
  .appbar {
    left: 0;                                        /* 모바일은 사이드바가 오프캔버스라 옆에 비울 폭이 없다 */
    z-index: 201;                                  /* .topbar(200) 위 */
    height: var(--topbar-h);
    padding: 0 16px;
    background: transparent;
    border-bottom: none;
    pointer-events: none;
  }
  .appbar-slot { pointer-events: auto; }

  /* ── A1. 대시보드 헤더 — 건수 표시를 적용 아래 우측으로 ──────
     .dash-date-compact에 flex-basis:100%를 줘 첫 줄을 통째로 차지하게
     하면, 남은 형제(#dashCacheStatus)가 다음 줄로 밀려나며
     justify-content:flex-end로 우측에 붙는다. HTML은 그대로다 —
     감싸는 <div>를 새로 넣으면 6개 페이지의 헤더 동일성을 강제하는
     테스트가 걸린다. */
  .dash-header-controls { width: 100%; justify-content: flex-end; }
  .dash-date-compact    { flex-basis: 100%; flex-wrap: wrap; }
  .dash-cache-status    { margin-left: 0; }

  /* 계정 스코어카드가 375px에서 27px 밖으로 나가 문서 전체에 가로 스크롤이
     생기고 있었다. grid의 1fr은 minmax(auto,1fr)이라 트랙이 내용의 최소폭
     아래로 못 줄어든다 — 카드 하나의 min-content가 188px이라 2열이 화면을
     넘겼다. minmax(0,1fr)로 바닥을 열어 준다.
     (이 작업 전부터 있던 문제다. 대시보드 모바일 정리에 딸려 나왔다.) */
  .scorecards { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  /* ── A2. TOP 100 카드 3장 — 위아래 스택 대신 가로 스크롤 ────────────
     .report-stat-grid(팔로워 성장 리포트)와 같은 구조다. JS는 쓰지 않는다.
     78vw는 다음 카드의 왼쪽 끝이 화면에 걸치는 폭이다 — 옆으로 더 있다는
     걸 스크롤바보다 확실하게 알려준다. */
  .dash-score-top-row {
    display: flex;
    flex-wrap: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
    gap: 12px;
    padding-bottom: 8px;
    scroll-snap-type: x mandatory;
    scrollbar-width: thin;
    scrollbar-color: rgba(128,128,128,.4) transparent;
    -webkit-overflow-scrolling: touch;
  }
  .dash-score-top-row > * {
    flex: 0 0 78vw;
    min-width: 0;
    scroll-snap-align: start;
  }
  .dash-score-top-row::-webkit-scrollbar { height: 4px; }
  .dash-score-top-row::-webkit-scrollbar-track { background: transparent; }
  .dash-score-top-row::-webkit-scrollbar-thumb {
    background: rgba(128,128,128,.4); border-radius: 2px;
  }

  /* ── A3. 주요 카드 캐러셀 — 한 페이지에 한 장 ───────────────────────
     페이지당 장수는 dashboard.js가 화면폭을 보고 정한다(CARDS_PER_PAGE).
     여기서는 그 한 장이 페이지를 꽉 채우게만 한다. touch-action:pan-y로
     세로 스크롤은 브라우저에 남겨 둔다 — 가로로 확정되기 전에
     preventDefault()를 부르면 페이지가 세로로 안 움직인다. */
  .dash-metric-viewport { touch-action: pan-y; }
  .dash-metric-grid     { grid-template-columns: 1fr; }

  /* ── D3. 업무량 상단 KPI 카드 — 가로 스크롤 ─────────────────────────
     5장이 2열로 깔려 세 줄이 된다. TOP 100 카드(A2)와 같은 방식으로 한 줄에
     넣는다 — 두 화면에서 손동작이 같아야 한다. 폭은 A2(78vw)보다 좁게 잡는다.
     이 카드는 라벨·값·보조설명 세 줄뿐이라 더 좁아도 다 읽힌다. */
  .wl-kpi {
    display: flex;
    flex-wrap: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
    gap: 10px;
    padding-bottom: 8px;
    scroll-snap-type: x mandatory;
    scrollbar-width: thin;
    scrollbar-color: rgba(128,128,128,.4) transparent;
    -webkit-overflow-scrolling: touch;
  }
  .wl-kpi__card {
    /* 한 화면에 세 장. vw로 잡으면 본문 좌우 여백(.main의 padding 16px)이 빠지지
       않아 셋째 장이 잘린다 — 컨테이너 폭(100%)에서 gap 두 개를 빼고 나눈다.
       위 gap을 바꾸면 이 20px(10px × 2)도 같이 바꿔야 한다. */
    flex: 0 0 calc((100% - 20px) / 3);
    min-width: 0;
    scroll-snap-align: start;
    /* 108px 안에 라벨·값·보조설명 세 줄이 들어가야 한다. 기본 패딩(14/16)이면
       «기준 초과 (≥N)» 같은 라벨이 감겨 카드 높이가 들쭉날쭉해진다. */
    padding: 12px 10px;
  }
  .wl-kpi__label { font-size: 10.5px; letter-spacing: 0; }
  .wl-kpi__value { font-size: 19px; }
  .wl-kpi__sub   { font-size: 10px; }
  .wl-kpi::-webkit-scrollbar { height: 4px; }
  .wl-kpi::-webkit-scrollbar-track { background: transparent; }
  .wl-kpi::-webkit-scrollbar-thumb { background: rgba(128,128,128,.4); border-radius: 2px; }

  /* ── D1. 업무량 바 차트 — 점수·백분위를 빼고 트랙을 늘린다 ──────────
     56+64px 두 열과 gap 20px, 합쳐서 약 140px이 트랙으로 간다.
     일평균(__value)은 남긴다 — 이 차트가 무엇을 재는지 말하는 유일한
     숫자다. 점수·백분위는 행을 눌러 여는 패널에 그대로 있다. */
  .wl-chartrow { grid-template-columns: 56px 1fr 48px; }
  .wl-chartrow__total,
  .wl-chartrow__pct { display: none; }

  /* ── D2. 확장 패널 — 3개월 추이·업무량 요약을 숨긴다 ────────────────
     1100px 이하에서 3열이 1열로 쌓여 패널이 화면 몇 배 길이가 된다.
     도넛만 남긴다 — 한 화면에 들어오는 건 그것뿐이다. */
  .wl-chartpanel__col--trend,
  .wl-chartpanel__col--summary { display: none; }
}

/* ── 대시보드 홈 — 데이트라인 + 나의 일정(3일 레일) + 이번 주 이슈(D-day 목록) ──
   2026-08-21 리디자인(docs/superpowers/specs/2026-08-21-dashboard-tab-redesign.md),
   이어서 같은 날 자리 교체(리뷰 후속 — 사용자가 "오늘 이슈를 보여주는 방식과
   캘린더를 바꾸고, 위치도 스위치"를 요청했다). 나의 일정이 이슈가 쓰던 4열
   날짜 레일 자리와 시그니처(오늘 열 네이비 채움)를 넘겨받고(일정이 정확히
   오늘·내일·모레 3일치라 3열로 줄었다), 이번 주 이슈는 D-day로 묶은 목록이
   되어 반폭 카드(.home-two-col)로 내려왔다. 같은 크기 카드를 격자로 늘어놓던
   원래 판은 12건짜리 이슈 목록이 폭의 25%만 쓰고 나머지가 비었다 — 날짜가
   붙은 일일 브리핑이라는 실제 콘텐츠 모양을 그대로 구조로 옮긴다는 원칙은
   그대로다. 대담함은 '오늘' 열 하나에만 쓴다(네이비 채움) — 나머지는 전부
   조용하게. 두 개의 채워진 블록은 하나가 과하다는 뜻이라, 이번 주 이슈의
   '오늘' 그룹은 채우지 않고 이 앱이 이미 쓰는 옅은 active 톤만 빌린다(아래
   .home-issues__group--today 참고).
   [hidden] 가드를 반드시 함께 둔다 — display를 지정하는 순간 UA의
   [hidden]{display:none}을 이겨서 hidden 속성이 화면에 안 먹는다.
   이 저장소에서 이미 두 번 난 사고다(#wlUnmappedList, #dashDateRow). */

/* 데이트라인 — 날짜(헤드라인)와 당번(판권면 스태프 박스)을 한 줄에 묶는다.
   당번은 "오늘 누가 맡는가"이므로 날짜 옆이 맥락이 선다. 44px/700/타이트한
   자간은 이 화면에서 가장 큰 글자다(다음으로 큰 건 스코어카드 값 26px) —
   새 리소스 없이 "이 탭은 분석 화면이 아니다"를 크기·굵기 대비로 말한다. */
.home-dateline {
  display: flex; align-items: flex-end; flex-wrap: wrap;
  gap: 10px 16px;
  padding-bottom: 14px; margin-bottom: 18px;
  border-bottom: 1px solid var(--c-divide);
}
.home-dateline__date {
  margin: 0; font-size: 44px; font-weight: 700;
  letter-spacing: -0.02em; line-height: 1;
  color: var(--c-text-1);
}

/* 당번 — 매거진 판권면이 날짜 옆에 담당자를 적는 그 자리다. 셋뿐이고 매일
   같은 자리에 뜨므로 알약 칩 대신 가운뎃점으로 잇는 조용한 텍스트 줄로
   둔다(대담함은 '오늘' 열에만 쓰기로 했으니 여기는 최대한 절제한다).
   시각 다듬기(2026-08-21) — 예전엔 margin-left:auto·justify-content:flex-end로
   이 줄을 데이트라인 오른쪽 끝에 박아 뒀다. 1500px에서는 그게 날짜와 당번
   사이에 큰 빈 공백을 만들어 한 줄(마스트헤드)이 아니라 서로 무관한 두
   덩어리로 읽혔다 — 날짜 바로 옆(.home-dateline의 gap)에 붙여 "오늘,
   그리고 그 당번"이라는 관계가 보이게 한다. */
.home-duty-bar {
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: 4px 0; max-width: 420px; padding-bottom: 6px;
}
.home-duty-bar[hidden] { display:none!important; }
/* 셋뿐인 항목이 "당번"이라는 하나의 조로 읽히도록 붙이는 조용한 그룹
   라벨(home-cards.js가 항목들 앞에 그린다) — 알약 칩만큼 세우지 않고
   당번 항목보다 한 톤 더 죽인 색으로 "이건 표제, 본문은 뒤에 온다"만 말한다. */
.home-duty-bar__group-label { color:var(--c-text-4); font-size:11px; font-weight:600; letter-spacing:0.02em; margin-right:8px; }
.home-duty-bar__item { display:inline-flex; align-items:baseline; gap:5px; white-space:nowrap; }
.home-duty-bar__item[hidden] { display:none!important; }
/* :not(:first-child) 대신 인접 형제 선택자를 쓴다 — 그룹 라벨이 항목들보다
   앞서는 첫 자식이 되면 :not(:first-child)는 원래 첫 항목까지 "첫 자식이
   아니다"로 잘못 걸려 점(·)이 그 항목 앞에도 붙는다. 이 선택자는 항목이
   항목 바로 뒤에 올 때만 걸려 그 함정이 없다. */
.home-duty-bar__item + .home-duty-bar__item::before { content:'·'; margin:0 8px; color:var(--c-text-4); }
.home-duty-bar__label { color:var(--c-text-3); font-size:11.5px; }
.home-duty-bar__name  { color:var(--c-text-1); font-size:12.5px; font-weight:600; }
.home-duty-bar__error { color:var(--c-text-3); font-size:12.5px; }

/* 나의 일정 — 3일 레일(오늘·내일·모레), 전체 폭. 이번 주 이슈가 쓰던 4열
   날짜 레일 골격(칸 구조·오늘 채움·같은 높이)을 그대로 옮겨왔다 — 하루를
   한 열로 두고 실제 날짜를 머리에 달아 빈 폭을 여백이 아니라 구조로 채운다는
   원칙도 그대로다. 일정 데이터가 정확히 오늘·내일·모레 3일치라 3열이 폭에
   그대로 맞는다. 이 화면의 시그니처(오늘 열 네이비 채움)는 이제 여기
   하나뿐이다. */
.home-rail { display:flex; flex-direction:column; padding:0; margin-bottom:16px; overflow:hidden; }
.home-rail[hidden] { display:none!important; }
.home-rail .home-card__title { margin:0; padding:16px 18px 10px; }
.home-rail .home-card__empty { padding:0 18px 16px; margin:0; }

/* align-items:stretch는 그리드의 기본값이라 안 적어도 이미 걸려 있지만,
   명시적으로 적는다 — 오늘 열의 네이비 채움이 "세 칼럼 중 하나"로 보이려면
   세 칼럼이 같은 높이인 게 우연이 아니라 의도라는 걸 코드에서도 읽혀야
   한다(원래 이슈 레일에 걸었던 결정을 이 레일이 그대로 물려받았다 —
   scripts/smoke-home-rail-browser.js가 실브라우저에서 이 성질을 고정한다). */
.home-rail__cols { display:grid; grid-template-columns:repeat(3, 1fr); align-items:stretch; }
.home-rail__col {
  min-width:0; padding:12px 14px 14px;
  border-top:1px solid var(--c-divide); border-left:1px solid var(--c-divide);
  display:flex; flex-direction:column; gap:10px;
}
.home-rail__col:first-child { border-left:none; }
/* 오늘 열 — 화면에서 유일하게 색을 채우는 자리. 테두리도 배경과 같은 색으로
   맞춰 눈에 띄는 줄이 겹쳐 보이지 않게 한다. */
.home-rail__col--today {
  background: var(--c-primary); color: #fff;
  border-top-color: var(--c-primary);
}
.home-rail__col-head { display:flex; flex-direction:column; gap:2px; min-height:32px; }
.home-rail__col-day { font-size:13px; font-weight:700; }
.home-rail__col-date { font-size:11px; color:var(--c-text-3); font-family:var(--f-mono); }
.home-rail__col--today .home-rail__col-date { color:rgba(255,255,255,0.7); }
/* 칸 안의 행은 .home-card__row/__when/__link를 그대로 쓴다(아래 카드 공통
   규칙 참고) — 나의 일정이 원래 쓰던 모양 그대로라 word-break:keep-all 같은
   상속 규칙도 같이 딸려온다. 단 .home-card__when은 자기 색(--c-text-3)을
   직접 지정하는 규칙이라 오늘 열의 흰 글자 상속을 끊는다 — 실측(2배 확대
   스크린샷)에서 "10:00–11:00"이 네이비 위에서 거의 안 읽히는 걸 확인해
   여기서 따로 밝힌다. */
.home-rail__col-list { display:flex; flex-direction:column; gap:8px; min-width:0; }
.home-rail__col--today .home-card__when { color: rgba(255,255,255,0.7); }
/* 업로드 행의 유형 칸(.home-card__media, 아래 카드 공통 규칙 참고)도 __when과
   같은 이유로 오늘 열의 흰 글자 상속을 끊는다 — 자기 색(--c-text-3)을 직접
   지정하는 규칙이라 그대로 두면 네이비 위에서 거의 안 읽힌다. */
.home-rail__col--today .home-card__media { color: rgba(255,255,255,0.7); }
.home-rail__col-empty { color:var(--c-text-4); font-size:12.5px; }
.home-rail__col--today .home-rail__col-empty { color:rgba(255,255,255,0.55); }

@media (max-width:767px) {
  /* 이슈 레일(4열)은 2×2로 접었지만 이 레일은 3열이라 2×2가 안 맞는다(칸
     하나가 남는다). 게다가 "11:00–12:30 회의"처럼 한 행이 시간+제목 두
     조각인 콘텐츠라 좁은 칸에 우겨넣기보다 세로 1열로 펴서 각 칸이 폭
     전체를 쓰게 한다 — 오늘 칸은 항상 맨 위라 접힌 뒤에도 시그니처(네이비
     채움)가 그대로 첫눈에 들어온다. */
  .home-rail__cols { grid-template-columns:1fr; }
  .home-rail__col { border-left:none; }
}

/* 두 카드는 항상 같은 높이 — 긴 쪽에 맞춘다(사용자 결정, 2026-08-21).
   한때 align-items:start로 각자 제 높이만 쓰게 했었다. 픽스처로 볼 땐 메일이
   두세 건뿐이라 아래가 수백 px 비어 그게 나아 보였는데, 실데이터에서는 메일이
   네댓 건씩 차고 나란한 두 카드의 밑변이 어긋나는 쪽이 더 거슬렸다.
   그리드 기본값(stretch)이 곧 '긴 쪽에 맞춤'이라 따로 지정하지 않는다. */
.home-two-col { display:grid; grid-template-columns:1fr 1fr; gap:16px; margin-bottom:16px; }
.home-two-col[hidden] { display:none!important; }
@media (max-width: 900px) { .home-two-col { grid-template-columns:1fr; } }

/* 이번 주 이슈 — D-day(오늘·D-1~D-3)로 묶은 세로 목록, 반폭 카드
   (.home-two-col 왼쪽). 레일 자리를 나의 일정에 넘긴 뒤로는 칸이 아니라
   구간(그룹)이다 — 그룹 머리가 날짜를 말하므로 행에는 D- 배지를 다시
   안 붙인다(더보기 펼침 목록은 예외, 아래 참고). */
/* 카드 높이 — 완화만(followup-6). 두 반폭 카드는 stretch로 밑변을 맞춘다
   (사용자 결정, :3161 — 뒤집지 않는다). 대가는: 메일은 서버에서 최대 5건으로
   잘리는데(services/home/mailThreads.js:19) 이슈는 상한이 없어, stretch가
   늘린 여분이 전부 메일 카드 하단 공백이 되고, "더보기"를 펼치면 그 여분이
   더 늘어나 메일 카드까지 같이 자란다.
   max-height + overflow-y:auto로 이슈 카드 자신의 높이 상한을 정한다 — 그리드
   트랙 높이(따라서 두 카드가 stretch로 맞추는 밑변)에 이 카드가 기여하는 값도
   이 상한 안으로 잡히므로, 메일 카드가 여기 맞춰 무한정 늘어나는 일이 없어진다
   (더보기를 펼쳐도 마찬가지 — 펼침은 이제 카드 밖 그리드 트랙이 아니라 카드
   안쪽 스크롤에서만 자란다). 밑변 정렬 자체는 그대로 유지된다.
   overflow:hidden을 그대로 두면 안 된다 — 상한을 넘는 내용이 스크롤이 아니라
   그냥 안 보이게 잘린다(펼친 이슈가 사라진 것처럼 보인다). overflow-x는
   hidden 그대로 둔다 — 이 카드가 원래 overflow:hidden이던 이유(surface의
   둥근 모서리 밖으로 배경 틴트가 번지는 것을 막는 가로 클리핑)는 세로 스크롤과
   무관하다. */
.home-issues { display:flex; flex-direction:column; padding:0;
               overflow-x:hidden; overflow-y:auto; max-height:460px; }
.home-issues[hidden] { display:none!important; }
.home-issues .home-card__empty { padding:16px 18px; margin:0; }

.home-issues__head {
  display:flex; align-items:baseline; justify-content:space-between;
  gap:8px; padding:16px 18px 12px;
}
.home-issues__heading { margin:0; font-size:14px; font-weight:700; color:var(--c-text-1); }
.home-issues__count { font-family:var(--f-mono); font-size:12px; color:var(--c-text-3); flex-shrink:0; }

.home-issues__groups { display:flex; flex-direction:column; }
.home-issues__group { padding:10px 18px; }
.home-issues__group:not(:first-child) { border-top:1px solid var(--c-divide); }
/* 오늘 그룹 — 눈이 먼저 가야 하지만 이 화면의 채움(네이비)은 이제 나의
   일정 레일 하나뿐이어야 한다. 그래서 채우는 대신 이 앱이 이미 쓰는 "옅은
   배경 + 진한 글자" active 표현(.preset-btn.is-active와 같은
   --c-primary-light/--c-primary 조합 — 새 토큰이 아니다)만 빌려 조용히
   표시한다. */
.home-issues__group--today { background:var(--c-primary-light); border-radius:var(--r-sm); }
.home-issues__group-head { display:flex; align-items:baseline; gap:8px; margin-bottom:8px; }
.home-issues__group-day { font-size:13px; font-weight:700; color:var(--c-text-1); }
.home-issues__group--today .home-issues__group-day { color:var(--c-primary); }
.home-issues__group-date { font-size:11px; color:var(--c-text-3); font-family:var(--f-mono); }
.home-issues__group-list { display:flex; flex-direction:column; gap:8px; }
.home-issues__group-empty { color:var(--c-text-4); font-size:12.5px; }

/* 이슈 행 — D-day 그룹 안(세로 쌓임, 날짜는 그룹 머리가 이미 말하므로 D- 배지
   없이 제목만)과 더보기 펼침 목록(가로 배치, D- 배지 유지) 둘 다에서 쓴다. */
.home-issues__row { font-size:13.5px; }
.home-issues__group-list .home-issues__row { display:flex; flex-direction:column; align-items:flex-start; gap:2px; }
.home-issues__title { overflow-wrap:anywhere; }
.home-issues__meta { color:var(--c-text-3); font-size:12px; }

/* D-3를 넘는 이슈를 담는 펼침 영역 — height:auto는 트랜지션이 안 걸리고,
   max-height 방식은 콘텐츠보다 넉넉한 임의의 상한값이 필요하다. grid-template-rows를
   0fr↔1fr로 옮기면 매직 넘버 없이 실제 콘텐츠 높이까지 정확히 늘어난다.
   자식(__more-inner)의 overflow:hidden·min-height:0이 짝이다 — 없으면 grid 아이템의
   기본 min-height(auto=콘텐츠 높이)가 0fr에서도 트랙을 안 줄인다.
   reduced-motion은 이 파일 상단(:136)의 전역 규칙이 모든 transition-duration을
   0.01ms로 낮춰 이미 처리한다 — 트랜지션이라 애니메이션 전용 규칙을 또 둘 필요가 없다.
   display:grid를 쓰므로 [hidden] 가드도 같이 둔다 — 이 저장소에서 두 번 난 사고다.
   목록이 카드 가장자리까지 패딩을 옮겨 가졌으므로(.home-issues 자체는 padding:0)
   더보기·토글도 좌우 18px을 스스로 챙긴다 — 그룹(.home-issues__group) 안쪽
   여백과 맞춘 값이다. */
.home-issues__more {
  display: grid;
  grid-template-rows: 0fr;
  padding: 0 18px;
  transition: grid-template-rows var(--t-n) var(--ease);
}
.home-issues__more[hidden] { display:none!important; }
.home-issues__more.is-open { grid-template-rows: 1fr; }
.home-issues__more-inner {
  overflow: hidden;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding-top: 10px;
}
.home-issues__more-inner .home-issues__row { display:flex; gap:8px; align-items:baseline; }
.home-issues__dday { flex-shrink:0; min-width:38px; font-size:11.5px; color:var(--c-text-3);
                     font-family:var(--f-mono); }
/* position:sticky(followup-6) — .home-issues가 max-height + overflow-y:auto로
   내부 스크롤을 갖게 되면서(위 :3210 근방) 실측(스모크 스크린샷)해 보니
   버튼이 스크롤 폴드 아래에 있어 접힌 기본 상태에서 아예 안 보였다 — 카드를
   먼저 스크롤해야만 "더보기"를 찾을 수 있는 회귀였다. 스크롤 컨테이너 바닥에
   붙여 두면 아래로 스크롤하는 동안에도 계속 눌를 수 있는 자리에 남는다.
   배경(--c-surface)이 이미 있어 뒤에서 스크롤되는 목록 위에 불투명하게
   얹힌다. */
.home-issues__toggle {
  position: sticky;
  bottom: 0;
  align-self: flex-start;
  margin: 8px 18px 16px;
  padding: 3px 10px;
  border: 1px solid var(--c-border);
  border-radius: var(--r-sm);
  background: var(--c-surface);
  color: var(--c-text-2);
  font-family: var(--f);
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
}
.home-issues__toggle:hover { background: var(--c-bg); border-color: #D1D5DB; }

.home-card { padding:16px 18px; min-height:120px; }
.home-card[hidden] { display:none!important; }
.home-card__title { margin:0 0 10px; font-size:14px; font-weight:700; }
.home-card__empty { color:var(--c-text-4); font-size:13px; padding:8px 0; line-height:1.6; }
.home-card__list { display:flex; flex-direction:column; gap:8px; }
/* word-break:keep-all — 한글은 공백 없이도 줄바꿈이 가능해서(음절 블록 단위)
   기본값이면 "확인" 같은 한 단어도 "확"/"인"으로 쪼개진다(시각 다듬기 2026-08-21,
   메일 카드에서 실측). keep-all은 공백에서만 줄바꿈해 단어를 지킨다. 그래도
   공백 없는 토큰(긴 URL 등)이 칸보다 넓으면 넘칠 수 있어 overflow-wrap:break-word를
   비상 대피용으로 같이 둔다(무조건 아무 데서나 끊는 anywhere보다는 온건하다).
   .home-card__row가 부모라 이 아래 신·구 요소(이유·제목·발신자) 전부에 상속된다
   — 나의 일정 레일 칸 안의 행(.home-rail__col-list .home-card__row)도 같은
   부모 클래스를 쓰므로 같이 상속받는다. */
.home-card__row { display:flex; gap:8px; align-items:baseline; font-size:13.5px;
                  word-break:keep-all; overflow-wrap:break-word; }
.home-card__when { flex-shrink:0; min-width:64px; color:var(--c-text-3); font-size:12px;
                   font-family:var(--f-mono); }
/* 이 화면의 유일한 색 채움은 오늘 열의 네이비다(스펙: "대담함은 한 곳에만
   쓴다" — 자리 교체 뒤로는 나의 일정 레일의 오늘 열). 이 사유 배지가
   --c-primary를 쓰던 건 그 원칙과 어긋나는 흔적이라 시각 다듬기(2026-08-21)
   에서 걷어냈다 — Chanel의 규칙, 뺀 것 하나. */
.home-card__reason { flex-shrink:0; color:var(--c-text-3); font-size:11.5px; }
.home-card__link { color:inherit; text-decoration:none; }
.home-card__link:hover { text-decoration:underline; }

/* 나의 일정 — 업로드 일정 병합(2026-08-22). 유형 칸(IG·REELS…)은 이유 칸
   (.home-card__reason)과 같은 조용한 회색 라벨이다 — 같은 시각 언어를
   새 클래스 하나로만 나눠 쓴다(이유는 메일 전용 이름이라 그대로 재사용하면
   의미가 헷갈린다). */
.home-card__media { flex-shrink:0; color:var(--c-text-3); font-size:11.5px; }
/* 배지('유가'/'일반') — 사용자 결정: 단어 자체를 색으로 하이라이트한다,
   배경은 안 채운다(위 :3348 주석과 같은 원칙 — 이 화면의 유일한 색 채움은
   오늘 열의 네이비 하나뿐이어야 한다). 새 색 토큰을 만들지 않고 이 앱이
   이미 쓰는 위험(빨강)·정보(파랑) 토큰을 그대로 가져온다 — 인사이트
   스코어카드의 증감 화살표(.scorecard__delta.is-down)가 쓰는 빨강과 같은
   --c-danger, 안내 상자(.infobox)·작업 배너(.wl-banner)가 쓰는 파랑과 같은
   --c-info. */
.home-card__badge { font-weight:700; }
.home-card__badge--paid   { color:var(--c-danger); }
.home-card__badge--normal { color:var(--c-info); }

/* 메일 카드 — 제목과 발신자를 한 덩어리(.home-card__subject)로 묶는다.
   전에는 발신자가 .home-card__row의 세 번째 플렉스 아이템이라 제목이
   두 줄로 접혀도 첫 줄 옆에 그대로 떠 있었다(390px 실측: "광고 소재
   최종 확인 요청"이 접혀도 "문경원"은 첫 줄 높이에 고정). 이 래퍼는
   플렉스가 아니라 그냥 블록이라 안의 링크·발신자 span이 일반 인라인
   흐름으로 붙는다 — 제목이 줄바꿈되면 발신자는 자연스럽게 마지막 줄을
   따라간다("붙어 있다"고 읽히는 이유). min-width:0은 플렉스 부모
   (.home-card__row) 안에서 이 블록이 안 줄어들고 넘치는 걸 막는다. */
.home-card__subject { min-width:0; }
.home-card__sender { color:var(--c-text-3); font-size:12px; }
.home-card__sender::before { content:'·'; margin:0 6px; color:var(--c-text-4); }

/* 알림 발송이 거부됐을 때 그 자리에 남기는 사유 */
.worklist-send-note { display:inline-block; margin-left:8px; color:var(--c-text-3); font-size:12px; }

/* ── 메타 광고 캠페인 대시보드 ─────────────────────────────────────────
   display를 쓰는 규칙마다 [hidden] 가드를 함께 둔다 — 저자 스타일의 display가
   UA의 [hidden]{display:none}을 이겨서 hidden 속성이 화면에 안 먹는다.
   이 저장소에서 이미 두 번 난 사고다(#wlUnmappedList, #dashDateRow).
   전체/무가/유가 퀵칩은 .seg를 그대로 쓴다(부스팅·유가 서비스 세그먼트와
   같은 마크업, :1239) — 전용 클래스가 필요 없다. */

/* 최종 리뷰 Fix 4 — .meta-ads-quick(margin-bottom:10px 전용 클래스)를 걷어내고
   퀵칩을 공용 .seg로 옮기면서 그 여백까지 같이 사라졌다. 다른 화면의 .seg는
   전부 .toolbar 안에 있어 그쪽이 간격을 대신 주지만, 이 퀵칩 줄은 .toolbar
   밖에 홀로 있고 바로 아래 .meta-ads-filters(:3117)는 margin-bottom만 선언해
   자기 위쪽 간격은 안 챙긴다 — 그 결과 전체/무가/유가 줄이 캠페인·광고세트
   필터 줄에 그대로 붙어 있었다. id로 이 줄에만 스코프해 다른 .seg 사용처(예:
   부스팅·유가 서비스, 이미 자체 여백이 있다)에 영향이 안 가게 한다. */
#metaAdsQuickRow { margin-bottom:10px; }

/* 캠페인·광고세트 드롭다운 + 초기화 버튼이 한 줄. align-items:end로 초기화를
   라벨이 아니라 셀렉트 하단에 맞춘다(레퍼런스 화면 참고) — 셀렉트 칸은
   라벨+셀렉트로 초기화 버튼 한 줄보다 키가 크므로, end 정렬이라야 두 하단이
   맞는다. */
.meta-ads-filters { display:grid; grid-template-columns:1fr 1fr auto; align-items:end; gap:12px; margin-bottom:16px; }
.meta-ads-filters[hidden] { display:none!important; }
@media (max-width: 900px) { .meta-ads-filters { grid-template-columns:1fr; } }
.meta-ads-filters__field { display:flex; flex-direction:column; gap:4px; }
.meta-ads-filters__field[hidden] { display:none!important; }
.meta-ads-filters__label { font-size:12px; color:var(--c-text-3); }
.meta-ads-filters select { height:auto; padding:6px; }
/* 캠페인 다중 선택 드롭다운(묶음 F) — .dash-filter는 원래 필터 바에서 단독으로
   쓰이도록 지어졌다(가변 너비). 이 그리드 칸 안에서는 옆 광고세트 select와
   같은 폭·높이로 맞춘다. */
.meta-ads-filters .dash-filter { width:100%; }
.meta-ads-filters .dash-filter__btn { width:100%; justify-content:space-between; height:30px; padding:0 10px; font-size:12.5px; }

/* 광고세트 패널(묶음 T) — 캠페인 패널과 같은 .dash-filter 골격이지만 검색
   인풋이 있어 헤더(검색+전체선택/해제)는 고정하고 옵션 목록만 스크롤해야
   한다 — 헤더까지 같이 스크롤되면 157개를 내려보다 검색창을 놓친다.
   .dash-filter__panel의 overflow-y:auto·max-height(캠페인 전용으로 지어진
   값, :1885-1891)를 이 패널에서만 되돌린다 — 클래스 하나로 새 규칙을
   보태는 것이라(별도 id 셀렉터가 아니다) 우선순위 다툼 없이 소스 순서로
   이긴다. */
.meta-ads-adset-panel {
  display:flex; flex-direction:column; min-width:260px;
  max-height:min(60vh, 420px); overflow:visible;
}
.meta-ads-adset-panel[hidden] { display:none!important; }
.meta-ads-adset-search { flex-shrink:0; margin-bottom:6px; }
.meta-ads-adset-search .search-field__input,
.meta-ads-adset-search .search-field__input:focus { width:100%; }
.meta-ads-adset-panel .dash-filter__panel-actions { flex-shrink:0; }
.meta-ads-adset-options { overflow-y:auto; max-height:260px; }

.meta-ads-scorecards { display:grid; grid-template-columns:repeat(5, 1fr); gap:10px; margin-bottom:16px; }
.meta-ads-scorecards[hidden] { display:none!important; }
/* 5칸은 사이드바(260px 고정) + .main 여백을 뺀 실제 카드 폭이 넉넉할 때만 쓴다
   (최종 리뷰 "delta-label wrap" 지적). 화살표 아이콘(13px+간격, 묶음 S)이 더해진
   뒤로 5칸 카드 안의 "전월 대비" 증감 라벨이 900~1200px 뷰포트에서 최대 4줄까지
   접혔다 — 실측(scripts/__shot-delta-tmp.js로 카드별 줄 수를 직접 쟀다): 900px
   대(카드 실폭 ~95~106px)에서 라벨이 2줄, 1000~1045px 부근은 카드마다 들쭉날쭉
   (그리드 트랙이 지출 카드의 넓은 값 텍스트에 밀려 균등하지 않다), 1050px부터
   안정적으로 1줄이다. 라벨을 줄이거나("전월 대비"는 services/periodCompare.js의
   설계 의도상 정밀한 문구라 손대지 않는다, 리뷰 지시) 임계 아래서 숨기는 대신
   —라벨은 항상 같은 자리에서 같은 말을 해야 아이콘·숫자만 보고 방향을 다시
   추측하지 않는다— 이 구간엔 3칸으로 되돌려 카드 하나당 실제 폭을 넓힌다.
   1200은 --content-max(:99행)와 맞춘 값이라 "본문이 최대폭에 닿기 전까지는
   3칸"으로 읽기도 쉽다. 901·1100·1400px 세 지점(리뷰가 지정한 확인 지점)
   모두 이 규칙으로 1줄임을 실제 Chrome으로 확인했다(작업 보고서 스크린샷 참고). */
@media (max-width: 1200px) { .meta-ads-scorecards { grid-template-columns:repeat(3, 1fr); } }
@media (max-width: 900px) { .meta-ads-scorecards { grid-template-columns:repeat(2, 1fr); } }
.meta-ads-card { padding:12px 14px; border:1px solid var(--c-border); border-radius:var(--r-sm);
                 background:var(--c-surface); }
.meta-ads-card__label { display:block; font-size:11.5px; color:var(--c-text-3); margin-bottom:4px; }
.meta-ads-card__label[hidden] { display:none!important; }
.meta-ads-card__value { display:block; font-size:18px; font-weight:700; font-family:var(--f-mono); }
.meta-ads-card__value[hidden] { display:none!important; }
/* 라벨 줄에 인라인으로 붙는다 — 블록이면 그 카드만 길어져 다섯 장이 어긋난다. */
.meta-ads-card__note  { margin-left:6px; font-size:10.5px; color:var(--c-text-4); font-weight:400; }

/* 증감 칩 — 인사이트 계정 스코어카드(.scorecard__delta, :2119)와 같은 색 규칙을
   그대로 쓴다(증가 초록·감소 빨강을 두 번 정의하지 않는다). 이 카드 안에서만
   여백·글자 크기를 살짝 좁힌다 — 구조·색은 public/meta-ads.js의 buildDeltaChip()가
   그 클래스들을 그대로 재사용해서 짓는다. */
.meta-ads-card .scorecard__delta { margin-top:6px; font-size:11px; }

/* 스파크라인 — 카드 폭에 맞춰 늘어나는 선(채움 없음, Task 1). 지표마다 다른
   색조를 --spark-hue로 지정한다(.chip의 --chip-hue(:881)와 같은 패턴). path
   자체가 열린 폴리라인이라(meta-ads.js:sparkPath) fill:none만으로 충분하다 —
   닫는 변이 없으니 stroke로 남을 바닥선·양끝 수직선도 없다. */
.meta-ads-spark { display:block; width:100%; height:34px; margin-top:8px; }
.meta-ads-spark[hidden] { display:none!important; }
.meta-ads-spark path {
  fill: none;
  stroke: hsl(var(--spark-hue, 220) 55% 45%);
  stroke-width: 1.6;
  stroke-linejoin: round;
  stroke-linecap: round;
  /* viewBox가 preserveAspectRatio="none"으로 폭·높이 각각 다르게 늘어나므로
     (카드 폭에 맞춰 가로만 크게 늘어난다) stroke도 그 비율대로 같이 늘어나
     굵기가 구간마다 달라진다(실측 1.700px~2.898px, 900px 폭에서 3.60배). 이
     한 줄이 stroke를 사용자 공간(뷰포트) 기준 고정폭으로 그리게 한다 — 선
     자체의 좌표·곡선 모양은 그대로다. */
  vector-effect: non-scaling-stroke;
}
.meta-ads-spark--spend       { --spark-hue: 222; }
.meta-ads-spark--impressions { --spark-hue: 199; }
.meta-ads-spark--reach       { --spark-hue: 262; }
.meta-ads-spark--ctr         { --spark-hue: 142; }
.meta-ads-spark--cpm         { --spark-hue: 24; }

.meta-ads-divider { margin:24px 0; border:0; border-top:1px solid var(--c-border); }

/* 조회 실패 메모. .meta-ads-scorecards는 5열 grid라 그냥 두면 메시지가 한 칸(1/5
   너비)에 눌려 들어간다 — grid-column으로 전체 행을 차지하게 한다. display는
   건드리지 않으므로(기본값 block) [hidden] 가드는 필요 없다. */
.meta-ads-error { grid-column:1 / -1; padding:12px 14px; border:1px solid var(--c-border);
                   border-radius:var(--r-sm); background:var(--c-surface);
                   font-size:12px; color:var(--c-text-3); }
