/* ==========================================================================
   admin.css : XiData 관리 화면
   대시보드(dashboard.css)는 어두운 테마다. 관리 화면은 숫자를 오래 들여다보는
   입력 작업이라 밝은 테마로 분리했다. 두 파일은 서로 참조하지 않는다.
   ========================================================================== */

:root{
  --ground:#F6F7F9;
  --surface:#FFFFFF;
  --surface-2:#F0F3F7;
  --ink:#131A22;
  --ink-2:#3C4956;
  --muted:#5B6B7B;
  --faint:#8A98A6;
  --line:#E0E5EC;
  --line-2:#CBD3DD;
  --navy:#16304B;
  --blue:#2E7DF7;
  --blue-soft:#E8F0FE;
  --good:#2F8F4E;
  --good-soft:#E8F5EC;
  --warn:#9A6A12;
  --warn-soft:#FCF3E2;
  --bad:#C0392B;
  --bad-soft:#FBECEA;
  --sans:"Pretendard","Malgun Gothic","맑은 고딕",system-ui,-apple-system,sans-serif;
  --mono:"IBM Plex Mono","D2Coding",Consolas,monospace;
  --shadow:0 1px 2px rgba(19,26,34,.06),0 8px 24px -14px rgba(19,26,34,.20);
  --gnb-h:54px;
}

*{box-sizing:border-box}
html,body{margin:0;padding:0}
body{
  background:var(--ground);color:var(--ink);
  font-family:var(--sans);font-size:14px;line-height:1.6;
  -webkit-font-smoothing:antialiased;
  word-break:keep-all;overflow-wrap:break-word;
}
a{color:var(--blue);text-decoration:none}
h1,h2,h3{margin:0;font-weight:700;line-height:1.35}
input,select,textarea,button{font-family:inherit;font-size:inherit}
:focus-visible{outline:2px solid var(--blue);outline-offset:2px}

/* ---- GNB ---------------------------------------------------------------- */
.gnb{
  position:sticky;top:0;z-index:40;height:var(--gnb-h);
  display:flex;align-items:center;gap:16px;padding:0 18px;
  background:var(--navy);color:#E8EFF7;
}
.brand{display:flex;align-items:center;gap:7px;color:#fff}
/* GNB 로고. 흰 워드마크라 네이비 배경 위에 그대로 올린다. */
.brand-logo{display:block;width:47px;height:24px;flex:0 0 auto}
.brand-mark{
  font-family:var(--mono);font-weight:600;font-size:15px;letter-spacing:-.02em;
  background:rgba(255,255,255,.14);border-radius:5px;padding:3px 7px;
}
.brand-txt{font-size:14.5px;font-weight:600;letter-spacing:-.01em}
.gnb-right{margin-left:auto;display:flex;align-items:center;gap:10px}
.who{font-size:13px;color:rgba(232,239,247,.8);display:flex;align-items:center;gap:7px}
.who b{color:#fff;font-weight:600}
.role{
  font-size:11px;padding:2px 7px;border-radius:20px;
  border:1px solid rgba(255,255,255,.25);color:rgba(232,239,247,.9);
}
.role-SUPER{background:rgba(94,162,240,.22);border-color:transparent}
.btn-s{
  font-size:12.5px;color:#E8EFF7;border:1px solid rgba(255,255,255,.22);
  border-radius:6px;padding:5px 11px;transition:.14s;
}
.btn-s:hover{background:rgba(255,255,255,.12)}

/* ---- 레이아웃 ------------------------------------------------------------ */
.shell{display:grid;grid-template-columns:196px minmax(0,1fr);min-height:calc(100vh - var(--gnb-h))}
.lnb{
  background:var(--surface);border-right:1px solid var(--line);
  padding:14px 0 40px;
  position:sticky;top:var(--gnb-h);align-self:start;
  max-height:calc(100vh - var(--gnb-h));overflow-y:auto;
}
.lnb-group + .lnb-group{margin-top:6px;padding-top:12px;border-top:1px solid var(--line)}
.lnb-h{
  font-family:var(--mono);font-size:10.5px;font-weight:600;letter-spacing:.1em;
  text-transform:uppercase;color:var(--faint);padding:6px 16px 8px;
}
.nav-i,.lnb-a{
  display:flex;align-items:center;gap:9px;padding:9px 16px;
  color:var(--ink-2);border-left:2px solid transparent;font-size:13.5px;transition:.12s;
}
.nav-i:hover,.lnb-a:hover{background:var(--surface-2)}
.nav-i.on,.lnb-a.on{
  background:var(--blue-soft);color:var(--blue);
  border-left-color:var(--blue);font-weight:600;
}
.lnb-a .dot{width:6px;height:6px;border-radius:50%;background:var(--line-2);flex:none}
.lnb-a .dot.done{background:var(--good)}
.lnb-a .dot.part{background:var(--warn)}
.lnb-a .cnt{margin-left:auto;font-family:var(--mono);font-size:11px;color:var(--faint)}

.main{padding:22px 26px 90px;min-width:0}
.page-h{display:flex;flex-wrap:wrap;align-items:center;gap:12px;margin-bottom:18px}
.page-h h1{font-size:20px;letter-spacing:-.01em}
.page-h .sub{font-size:13px;color:var(--muted)}
.page-h .right{margin-left:auto;display:flex;gap:8px;align-items:center}

/* ---- 공통 조각 ----------------------------------------------------------- */
.card{
  background:var(--surface);border:1px solid var(--line);border-radius:10px;
  box-shadow:var(--shadow);margin-bottom:16px;overflow:hidden;
}
.card-h{
  display:flex;flex-wrap:wrap;align-items:center;gap:10px;
  padding:14px 18px 12px;border-bottom:1px solid var(--line);
}
.card-h .n{
  font-family:var(--mono);font-size:10.5px;font-weight:600;color:var(--faint);
  border:1px solid var(--line-2);border-radius:4px;padding:1px 6px;
}
.card-h b{font-size:14.5px}
.card-h .sub{font-size:12.5px;color:var(--muted)}
.card-h .tbl{margin-left:auto;font-family:var(--mono);font-size:11px;color:var(--faint)}
.card-b{padding:16px 18px}

.pill{
  font-family:var(--mono);font-size:10.5px;font-weight:600;letter-spacing:.06em;
  padding:2px 7px;border-radius:20px;border:1px solid var(--line-2);color:var(--muted);
}
.pill.ok{background:var(--good-soft);color:var(--good);border-color:transparent}
.pill.warn{background:var(--warn-soft);color:var(--warn);border-color:transparent}
.pill.off{background:var(--surface-2)}
.pill.blue{background:var(--blue-soft);color:var(--blue);border-color:transparent}
.swatch{width:10px;height:10px;border-radius:2px;flex:none;display:inline-block}

.btn{
  appearance:none;border:1px solid transparent;border-radius:7px;cursor:pointer;
  font-size:13.5px;font-weight:600;padding:9px 16px;transition:.14s;
}
.btn.primary{background:var(--blue);color:#fff}
.btn.primary:hover{filter:brightness(1.08)}
.btn.ghost{background:var(--surface);border-color:var(--line-2);color:var(--ink-2)}
.btn.ghost:hover{background:var(--surface-2)}
.btn.danger{background:var(--surface);border-color:var(--bad);color:var(--bad)}
.btn.danger:hover{background:var(--bad-soft)}
.btn.wide{width:100%}
.btn.sm{padding:6px 11px;font-size:12.5px;font-weight:500}
.btn[disabled]{opacity:.5;cursor:not-allowed}

/* ---- 입력 --------------------------------------------------------------- */
.fields{display:grid;gap:14px}
.f2{grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}
.f3{grid-template-columns:repeat(auto-fit,minmax(170px,1fr))}
.f4{grid-template-columns:repeat(auto-fit,minmax(140px,1fr))}
.fld label,.field label{display:block;font-size:12.5px;font-weight:500;color:var(--muted);margin-bottom:5px}
.field label .req,.fld label .req{color:var(--bad);margin-left:2px}
/* ★ 2026-09-30 — '선택' 꼬리표. 필수(.req)의 반대다. 빈 칸을 보고
     '안 채웠다' 로 읽지 않게 라벨에서 미리 말해 준다. */
.field label .opt,.fld label .opt{
  margin-left:5px;font-size:11px;font-weight:400;color:var(--faint);
  border:1px solid var(--line);border-radius:3px;padding:0 4px;
}
.inp{display:flex;align-items:center;background:var(--surface);
  border:1px solid var(--line-2);border-radius:7px;transition:.14s}
.inp:focus-within{border-color:var(--blue);box-shadow:0 0 0 3px var(--blue-soft)}
.inp input,.inp select{
  flex:1;min-width:0;appearance:none;border:0;background:transparent;outline:0;
  font-size:14px;color:var(--ink);padding:9px 12px;
}
.inp input[type=number]{font-family:var(--mono);font-variant-numeric:tabular-nums;text-align:right}
.inp input[type=number]::-webkit-outer-spin-button,
.inp input[type=number]::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}
.inp input[type=number]{-moz-appearance:textfield}
.inp .unit{font-size:12px;color:var(--faint);padding:0 11px 0 0;white-space:nowrap}
.inp.bad{border-color:var(--bad);background:var(--bad-soft)}
.inp.bad:focus-within{box-shadow:0 0 0 3px var(--bad-soft)}
.inp input[type=color]{padding:3px;width:38px;flex:none;height:34px;cursor:pointer}
/* 산출값 칸 — 사람이 입력하지 않고 다른 값에서 계산되어 나오는 자리.
   입력칸과 같은 자리에 놓이므로, 눌러도 커서가 안 생긴다는 것이 보여야 한다. */
.inp.is-calc{background:var(--line);border-style:dashed}
.inp.is-calc output{
  flex:1;min-width:0;font-size:14px;padding:9px 12px;
  font-family:var(--mono);font-variant-numeric:tabular-nums;text-align:right;
  color:var(--ink);
}
/* 주소 입력 줄 : 긴 주소가 다 보이도록 입력칸이 남는 폭을 다 먹고,
   '열어보기' 버튼만 오른쪽에 고정 폭으로 붙는다.
   min-width:0 이 없으면 flex 항목이 내용 너비 아래로 줄지 않아
   버튼이 밖으로 밀려난다. */
.urlrow{display:flex;align-items:center;gap:8px}
.urlrow .inp{flex:1 1 auto;min-width:0}
.urlrow .inp input{font-family:var(--mono);font-size:12.5px}
.urlrow .btn{flex:0 0 auto}

/* 주소가 아직 유효하지 않을 때의 '열어보기'.
   href 를 지워 두었으므로 눌러도 아무 일이 없다. 눌리지 않는다는 것이
   보이도록 흐리게 만든다. */
.btn.is-off{opacity:.4;cursor:not-allowed;pointer-events:none}

.hint{font-size:12px;margin:5px 0 0;color:var(--muted);line-height:1.5}
.hint.bad{color:var(--bad);font-weight:500}
.hint.good{color:var(--good)}
.hint.warn{color:var(--warn)}
.fld{margin-bottom:14px}
.fld input,.fld select{
  width:100%;border:1px solid var(--line-2);border-radius:7px;
  padding:10px 12px;background:var(--surface);color:var(--ink);
}
.fld input:focus{outline:0;border-color:var(--blue);box-shadow:0 0 0 3px var(--blue-soft)}

/* ---- 표 ---------------------------------------------------------------- */
.tscroll{overflow-x:auto}
table.t{border-collapse:collapse;width:100%;font-size:13.5px}
table.t th,table.t td{padding:10px 12px;border-bottom:1px solid var(--line);text-align:left;vertical-align:middle}
table.t thead th{
  font-family:var(--mono);font-size:10.5px;font-weight:600;letter-spacing:.06em;
  text-transform:uppercase;color:var(--faint);border-bottom:1px solid var(--line-2);
  white-space:nowrap;
}
table.t tbody tr:last-child td{border-bottom:0}
table.t td.num,table.t th.num{text-align:right;font-family:var(--mono);font-variant-numeric:tabular-nums}
table.t td .inp{max-width:118px;margin-left:auto}
table.t.center td,table.t.center th{text-align:center}
table.t.center td:first-child,table.t.center th:first-child{text-align:left}
table.t.center td .inp{margin:0 auto}
table.t tr.off{opacity:.55}

/* 날짜 없는 양식(고객DB 최종)에서는 기간 칸을 감춘다 */
.tpl-range.nodate input[type=date],
.tpl-range.nodate > span{display:none}

/* 표 아래 합계 줄. 값이 아니라 확인용이므로 입력칸과 다르게 보여야 한다. */
table.t tfoot .sumrow th,
table.t tfoot .sumrow td{
  border-top:1px solid var(--line-2);border-bottom:0;
  font-family:var(--mono);font-variant-numeric:tabular-nums;
  font-weight:600;color:var(--ink-2);background:var(--surface-2);
}
table.t tfoot .sumrow th{font-family:var(--sans);color:var(--muted)}

/* 채널의 DB 수집 방식 꼬리표.
   '이 칸이 왜 회색인가' 를 그 자리에서 답한다. */
.modetag{
  display:inline-block;margin-left:7px;padding:1px 7px;border-radius:20px;
  font-size:10.5px;font-weight:600;letter-spacing:0;vertical-align:middle;
  white-space:nowrap;
}
.modetag.daily { background:var(--surface-2);color:var(--faint) }
.modetag.onsite{ background:var(--blue-soft);color:var(--blue) }

/* 두 방식을 나란히 설명하는 블록 (현장 마스터의 유입채널 안내) */
.modehelp{display:grid;gap:8px;margin-top:10px}
.modehelp > div{
  display:grid;grid-template-columns:78px minmax(0,1fr);gap:10px;align-items:start;
  padding-top:8px;border-top:1px solid rgba(154,106,18,.18);
}
.modehelp .modetag{margin-left:0;text-align:center;padding:2px 0}
/* 설명은 통째로 한 칸이다 (JS 가 .modewhy 로 감싼다) */
.modehelp .modewhy{line-height:1.65;min-width:0}
@media (max-width:640px){
  .modehelp > div{grid-template-columns:1fr;gap:4px}
  .modehelp .modetag{justify-self:start;padding:2px 9px}
}

/* 수집 방식 선택칸 — 값에 따라 색이 달라져 한 줄만 봐도 구분된다.
   ★ option 의 색을 바꾸는 것은 브라우저마다 다르므로 select 자체에 준다.
     JS 가 값이 바뀔 때마다 data-v 를 새로 쓴다. */
.mrow .modesel{font-size:12.5px}
.mrow .modesel[data-v="ONSITE"]{
  border-color:var(--blue);color:var(--blue);background:var(--blue-soft);font-weight:600;
}

/* 성별 · 연령대 표 — 7구간 x 남/여 + 합계 = 아홉 칸이다.
   기본 칸 폭(118px)으로는 좁은 화면에서 합계가 가로 스크롤 밖으로 밀린다.
   합계가 안 보이면 '총 방문자와 맞는지' 를 볼 수가 없어서, 이 표에서만 좁힌다. */
#secDemo table.t td .inp{max-width:88px}
#secDemo table.t th,#secDemo table.t td{padding-left:7px;padding-right:7px}
#secDemo table.t thead th{font-size:11px;text-transform:none;font-family:var(--sans)}

/* 구역 행 (일자별 입력) */
.zrow-h,.zrow{display:grid;grid-template-columns:minmax(0,1fr) 130px 130px;gap:12px;align-items:center}
.zrow-h{padding-bottom:8px;border-bottom:1px solid var(--line-2)}
.zrow-h span{font-family:var(--mono);font-size:10.5px;font-weight:600;letter-spacing:.06em;
  text-transform:uppercase;color:var(--faint)}
.zrow-h span+span{text-align:right}
.zrow{padding:10px 0;border-bottom:1px solid var(--line)}
.zrow:last-child{border-bottom:0}
.zname{display:flex;align-items:center;gap:9px;font-size:14px}
.zkind{font-family:var(--mono);font-size:10.5px;color:var(--faint)}
.note-cell{grid-column:2/4;font-size:12px;color:var(--muted);text-align:right}

/* ---- 저장 바 ------------------------------------------------------------- */
.savebar{
  position:sticky;bottom:0;z-index:30;
  display:flex;flex-wrap:wrap;align-items:center;gap:14px;
  padding:12px 18px;margin:0 -26px -90px;
  background:var(--surface);border-top:1px solid var(--line-2);
  box-shadow:0 -6px 20px -14px rgba(19,26,34,.4);
}
.savebar .right{margin-left:auto;display:flex;gap:9px}
.prog{display:flex;align-items:center;gap:10px;font-size:12.5px;color:var(--muted)}
/* ★ span 은 인라인이라 폭·높이가 먹지 않는다. 반드시 block 으로 바꿀 것.
     이걸 빼면 진행률 막대가 항상 비어 보인다. */
.prog .track{display:inline-block;width:130px;height:6px;border-radius:3px;
  background:var(--surface-2);overflow:hidden;border:1px solid var(--line);vertical-align:middle}
.prog .fill{display:block;width:0;height:100%;background:var(--good);transition:width .25s}

/* ---- 현장/날짜 선택 ------------------------------------------------------ */
.picker{
  display:flex;flex-wrap:wrap;align-items:center;gap:10px;
  background:var(--surface);border:1px solid var(--line);border-radius:10px;
  padding:11px 14px;margin-bottom:16px;box-shadow:var(--shadow);
}
.picker label{font-size:12.5px;color:var(--muted);font-weight:500}
.picker select,.picker input[type=date]{
  border:1px solid var(--line-2);border-radius:7px;padding:8px 11px;
  background:var(--surface);color:var(--ink);
}
.picker .nav{display:flex;gap:4px}
.picker .right{margin-left:auto;display:flex;align-items:center;gap:8px;font-size:12.5px;color:var(--muted)}

/* ---- 현장 카드 (index) --------------------------------------------------- */
.sites{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:14px}
.site-c{
  background:var(--surface);border:1px solid var(--line);border-radius:10px;
  padding:16px 18px;box-shadow:var(--shadow);display:flex;flex-direction:column;gap:10px;
}
.site-c .top{display:flex;align-items:center;gap:8px}
.site-c .nm{font-size:15px;font-weight:700}
.site-c .code{font-family:var(--mono);font-size:11.5px;color:var(--faint)}
.site-c .meta{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12.5px;color:var(--muted)}
.site-c .meta b{color:var(--ink);font-family:var(--mono);font-weight:600}
.site-c .acts{display:flex;gap:7px;margin-top:2px;flex-wrap:wrap}

/* ---- 안내/오류 ----------------------------------------------------------- */
.callout{
  border:1px solid var(--line);border-left:3px solid var(--blue);border-radius:8px;
  background:var(--surface);padding:13px 16px;font-size:13px;color:var(--ink-2);
  margin-bottom:16px;line-height:1.65;
}
.callout.warn{border-left-color:var(--warn);background:var(--warn-soft)}
/* 접히는 안내 — 처음에는 한 줄만 보이고, 필요할 때 펼친다 */
details.callout > summary{ cursor:pointer; list-style:none; }
details.callout > summary::-webkit-details-marker{ display:none }
details.callout > summary::before{ content:'▸ '; color:var(--muted) }
details.callout[open] > summary::before{ content:'▾ ' }
details.callout ul{ margin:8px 0 0 18px; padding:0 }
details.callout li{ margin:3px 0 }
.callout.bad{border-left-color:var(--bad);background:var(--bad-soft);color:var(--bad)}
/* ★ 69판 — '지금 이 현장은 분양중' 처럼 ★ 잘 돌아가는 상태 ★ 를 알릴 때.
     ★ 글자색은 ★ 안 바꾼다 ★ (.bad 와 다르다) : 안내문이 아니라 상태 표시라
     본문이 초록이 되면 읽기 어렵고, 안의 꼬리표 색과도 부딪친다. */
.callout.good{border-left-color:var(--good);background:var(--good-soft)}
.callout b{color:var(--ink)}
.empty{padding:40px 20px;text-align:center;color:var(--muted);font-size:13.5px}

/* ---- 토스트 ------------------------------------------------------------- */
.toast{
  position:fixed;left:50%;bottom:26px;transform:translate(-50%,20px);
  z-index:100;max-width:min(560px,90vw);
  background:var(--navy);color:#EAF1F8;border-radius:9px;
  padding:12px 18px;font-size:13.5px;box-shadow:0 10px 30px -8px rgba(19,26,34,.5);
  opacity:0;pointer-events:none;transition:.22s;
}
.toast.show{opacity:1;transform:translate(-50%,0)}
.toast.bad{background:var(--bad)}
.toast.good{background:var(--good)}

/* ---- 로그인 ------------------------------------------------------------- */
.login-body{
  min-height:100vh;display:grid;place-items:center;padding:24px;
  background:linear-gradient(180deg,#1B3752 0%,#16304B 100%);
}
.login-card{
  width:100%;max-width:360px;background:var(--surface);
  border-radius:12px;padding:32px 30px 26px;box-shadow:0 24px 60px -20px rgba(0,0,0,.5);
}
.login-brand{display:flex;align-items:center;gap:7px;margin-bottom:6px}
/* 로그인 카드는 흰 배경이라 흰 로고가 안 보인다.
   네이비 칩 안에 넣어 대비를 확보한다. */
.login-brand .brand-mark{background:var(--navy);color:#fff;display:flex;align-items:center;padding:7px 9px}
.login-brand .brand-mark img{display:block;width:40px;height:20px}
.login-brand .brand-txt{color:var(--ink)}
.login-lead{margin:0 0 22px;font-size:13px;color:var(--muted)}
.login-err{
  margin:0 0 14px;font-size:13px;color:var(--bad);background:var(--bad-soft);
  border:1px solid rgba(192,57,43,.25);border-radius:7px;padding:10px 12px;
}
.login-foot{margin:16px 0 0;font-size:12px;color:var(--faint);text-align:center}

/* ---- 마스터 편집 행 ------------------------------------------------------ */
.mrow{
  display:grid;gap:10px;align-items:center;
  padding:10px 0;border-bottom:1px solid var(--line);
}
.mrow:last-of-type{border-bottom:0}
.mrow.zone   {grid-template-columns:62px 34px minmax(90px,110px) minmax(0,1fr) 116px 46px 74px 40px}
/* 수집 방식 칸은 '일자별 등록' 이 화살표에 물리지 않을 만큼 준다 (104px 이면 끝 글자가 깎인다) */
.mrow.chan   {grid-template-columns:62px 34px minmax(90px,110px) minmax(0,1fr) 46px 118px 74px 40px}
/* 비고는 둘째 줄을 통째로 쓴다 (코드 칸부터 끝까지).
   ★ 8열 격자에 9번째 자식을 넣으면 자동으로 다음 줄로 넘어간다.
     grid-column 으로 시작 위치만 잡아 주면 코드·채널명 아래에 맞춰 선다. */
.mrow.chan > .chnote,
.mrow.chan > .chnote-h{ grid-column:3 / -1; }
/* 둘째 줄 : 비고 · 비용예산(24_channel_budget.sql) · 산출 결과.
   ★ 머리글도 같은 격자를 써야 칸과 글자가 맞는다. 한 덩어리 글로 두면
     '비용예산' 이 비고 칸 위에 온다 (캠페인 줄에서 겪은 것과 같다). */
.mrow.chan > .chnote,
.mrow.chan > .chnote-h{
  display:grid; gap:8px; align-items:center;
  grid-template-columns:minmax(0,1fr) 150px minmax(0,1.1fr);
}
.mrow.chan > .chnote{ margin-top:6px; }
.mrow.chan > .chnote-h{ margin-top:4px; font-size:11.5px; opacity:.75; }
.mrow.chan .calc{
  font-family:var(--mono); font-size:11.5px; color:var(--blue);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.mrow.chan .calc.off{ color:var(--faint); }
.mrow.event  {grid-template-columns:62px 34px minmax(0,1fr) 132px 132px 46px 74px 40px}

/* 이벤트의 둘째 줄 — 측정 항목 (44_event_metrics.sql).
   ★ 8열 격자의 다음 줄로 넘어가 이벤트명 아래에 선다 (아래 .ezwrap 과 같다).
   ★ 분석희망구역과 같은 색 띠를 둔다 — 둘 다 '이 이벤트의 설정' 이다. */
.mrow.event > .evopt{ grid-column:3 / -1; }
.mrow.event > .evopt{
  margin-top:8px; padding:6px 0 2px 10px;
  border-left:2px solid var(--line);
  display:flex; flex-wrap:wrap; align-items:center; gap:10px;
}
.evopt .ezlb{
  font-size:11.5px; font-weight:600; color:var(--muted); white-space:nowrap;
}
.evopt .evo{
  display:inline-flex; align-items:center; gap:4px;
  font-size:12px; color:var(--fg); cursor:pointer; white-space:nowrap;
}
.evopt .evo input{ margin:0; cursor:pointer; }
/* ★ 꺼 둔 항목은 흐리게. 체크를 푼 것을 잊으면 '왜 입력칸이 없지' 를
     엉뚱한 데서 찾게 된다. */
.evopt .evo:has(input:not(:checked)){ color:var(--faint); }
.evopt .ezhint{ font-size:11.5px; color:var(--muted); }

/* ★★ 65판 (2026-10-02 · 요청) — 현장 기본정보의 '사용 메뉴' 한 줄.
     안쪽 규칙(.evopt .ezlb · .evo · .ezhint)은 ★ 그대로 쓴다 ★ — 체크
     한 칸의 모양이 이벤트 측정항목과 같아야, 같은 뜻으로 읽힌다.
   ★ 줄의 모양만 여기서 새로 준다. 위 .mrow.event > .evopt 는 ★ 8열
     격자 안에서만 ★ 뜻이 있는 규칙이라(grid-column), 격자 밖인 1번 카드로
     가져오면 아무 모양도 안 잡힌다 — 체크 다섯이 세로로 쌓인다. */
.evopt.mnopt{
  margin-top:14px; padding:8px 0 4px 10px;
  border-left:2px solid var(--line);
  display:flex; flex-wrap:wrap; align-items:center; gap:10px;
}
/* ★ 다섯을 다 끄면 빨갛게 알린다. 저장은 서버가 막지만(NO_MENU),
     누르기 전에 알려 주는 편이 낫다. */
.evopt.mnopt .ezhint.bad{ color:var(--bad); font-weight:600; }

/* 이벤트의 셋째 줄 — 분석희망구역 (23_event_zone.sql).
   ★ 8열 격자에 9번째 자식을 넣으면 자동으로 다음 줄로 넘어간다.
     grid-column 으로 시작 위치만 잡아 주면 이벤트명 아래에 맞춰 선다.
   ★ 위 2번 '구역'(SiteZone)과 헷갈리지 않게 왼쪽에 색 띠를 둔다. */
.mrow.event > .ezwrap{ grid-column:3 / -1; }
.mrow.event > .ezwrap{
  margin-top:8px; padding:8px 0 2px 10px;
  border-left:2px solid var(--line);
  display:flex; flex-wrap:wrap; align-items:center; gap:8px;
}
.ezwrap .ezlb{
  font-size:11.5px; font-weight:600; color:var(--muted);
  white-space:nowrap; align-self:flex-start; padding-top:6px;
}
/* ★ flex:1 로 늘리지 않는다. 구역이 하나도 없는 이벤트에서 빈 상자가 줄을
   통째로 차지해, ＋구역 버튼과 경고가 화면 오른쪽 끝으로 밀려난다. */
.ezwrap .ezbox{ display:flex; flex-wrap:wrap; gap:6px; flex:0 1 auto; min-width:0; }
.ezwrap .ezacts{ display:flex; flex-wrap:wrap; align-items:center; gap:8px; }
.ezwrap .ezhint{ font-size:11.5px; color:var(--muted); }
.ezwrap .ezhint.warn{ color:var(--warn); font-weight:600; }

/* 구역 한 칸
   ★ 2026-09-20 — '합계' 체크가 늘어 다섯 칸이 됐다 (37_event_zone_summary.sql).
     칸 수를 안 늘리면 체크가 삭제 버튼 위로 겹쳐 눌린다. */
.ez{
  display:inline-grid; align-items:center; gap:4px;
  grid-template-columns:28px minmax(84px,150px) 64px auto 26px;
  padding:4px 6px; border:1px solid var(--line); border-radius:6px;
}
/* 합계에 넣을 구역인가 (37). 이벤트 화면의 '관람 및 상담 합계' 가 이것을 본다. */
.ez .ezsum{
  display:inline-flex; align-items:center; gap:3px;
  font-size:11px; color:var(--muted); cursor:pointer; white-space:nowrap;
}
.ez .ezsum input{ margin:0; cursor:pointer; }
/* ★ 체크가 풀린 칸은 눈에 띄어야 한다. 풀어 둔 것을 잊으면 '합계가 왜
     이렇게 작지' 를 엉뚱한 데서 찾게 된다. */
.ez:has(.ezsum input:not(:checked)) .ezsum{ color:var(--warn); font-weight:600; }
.ez input[type=text]{ min-width:0; }
.ez .ezdel{
  background:transparent; border:0; color:var(--faint);
  cursor:pointer; font-size:14px; line-height:1; padding:3px;
}
.ez .ezdel:hover{ color:var(--warn); }
/* 사용중지로 내린 칸 — 값은 남아 있지만 입력 화면과 표에서 빠진다 */
.ez.off{ opacity:.55; }
/* 삭제 표시 — 저장 전까지는 되돌릴 수 있다 (× 가 ↺ 로 바뀐다) */
.ez.removed{ opacity:.45; text-decoration:line-through; }
.ez.removed input, .ez.removed select{ pointer-events:none; }

.btn.xs{padding:4px 8px;font-size:11.5px;font-weight:500}

/* 온라인 캠페인 — 두 줄짜리다.
   첫 줄 : 순서 · 수집 · 카테고리 · 한글명 · utm_campaign · 시작 · 종료 · 사용 · 삭제
   둘째 줄: 타겟지역 · 집행금액 · 기준단가 · ★ 단가설명 ★ · 산출 결과
   ★ 한 줄에 열세 칸을 밀어 넣으면 이름이 15자도 안 보인다.
     실제 이름은 '지역구 카페2_천안아산봉당신도시맘' 처럼 길다.
   ★ 이름 칸이 둘이다 (33). 한글명이 대시보드에 나가는 이름이고,
     utm_campaign 은 GA4 와 맞추는 열쇠다. 둘 다 길어서 폭을 똑같이 준다. */
.mrow.camp   {grid-template-columns:56px 30px minmax(78px,104px) minmax(0,1.1fr) minmax(0,1.1fr) 126px 126px 70px 36px}
/* utm_campaign 은 사람이 읽는 말이 아니라 열쇠다. 고정폭으로 두면
   'l' 과 '1', '0' 과 'O' 가 갈리고, 한글명과 눈으로 구분된다. */
.mrow.camp input[data-f="name"]{ font-family:var(--mono); font-size:12px; }
.mrow.camp > .camp2,
.mrow.camp > .camp2-h{ grid-column:3 / -1; }
.mrow.camp > .camp2{
  margin-top:6px; display:grid; gap:8px; align-items:center;
  /* ★★ 2026-09-30(52판) — ★ 단가 설명 ★ 칸이 늘어 다섯이 됐다.
       설명은 길다(200자). 기준 단가(짧은 값) 바로 오른쪽에 두고,
       남는 폭을 가장 많이 준다 — 여기만 1.6fr 이다. */
  grid-template-columns:minmax(0,1.1fr) 150px minmax(0,.85fr)
                        minmax(0,1.6fr) minmax(0,1.1fr);
}
/* 둘째 줄 머리글도 같은 격자를 써야 칸과 글자가 맞는다.
   한 덩어리 글로 두면 '집행 금액' 이 타겟 지역 칸 위에 온다. */
.mrow.camp > .camp2-h{
  margin-top:4px; font-size:11.5px; opacity:.75;
  display:grid; gap:8px;
  grid-template-columns:minmax(0,1.1fr) 150px minmax(0,.85fr)
                        minmax(0,1.6fr) minmax(0,1.1fr);
}
/* ★ 단가 설명은 ★ 선택 ★ 이다. 비어 있는 것이 정상이라, 비었을 때 눈에
     덜 띄게 둔다 — 안 그러면 스무 줄 내내 '빈 칸이 있다' 로 읽힌다. */
.mrow.camp input[data-f="rateDesc"]{ font-size:12px; }
.mrow.camp input[data-f="rateDesc"]:placeholder-shown{ opacity:.72; }
.mrow.camp input[data-f="rateDesc"]:focus{ opacity:1; }
.mrow.camp .calc{
  font-family:var(--mono); font-size:11.5px; color:var(--blue);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.mrow.camp .calc.off{ color:var(--faint); }
/* ★ 캠페인의 '수집' 칸은 뜻이 반대다.
   구역·채널은 값이 있으면 '지우면 위험' 이라 주황이지만,
   캠페인은 0 이면 'GA4 이름이 안 붙고 있다' 는 위험 신호다. */
.mrow.camp .used.has { color:var(--muted); font-weight:400; }
.mrow.camp .used.none{ color:var(--warn); font-weight:600; }

/* ── 넓은 화면에서는 한 줄로 편다 ─────────────────────────────────────────
   캠페인은 현장마다 20~40개다. 두 줄씩 쌓이면 한 화면에 여덟 개밖에 안
   보여서, 스무 개를 훑는 데 스크롤을 네 번 한다. 화면이 넉넉하면 한 줄이 낫다.

   ★ 어떻게 — 둘째 줄 묶음(.camp2)에 display:contents 를 준다.
     묶음 상자가 사라지고 그 안의 네 칸이 바깥 격자의 칸이 된다.
     HTML 을 건드리지 않으므로 좁은 화면의 두 줄 배치는 그대로다.

   ★ order 를 쓰는 이유 — DOM 순서가 '… 사용 · 삭제 · (묶음)' 이라,
     그냥 펴면 삭제 버튼이 한가운데로 온다. 사용·삭제만 뒤로 보낸다.
     DOM 순서를 바꾸는 쪽이 깔끔해 보이지만, 그러면 좁은 화면에서
     자동 배치가 어긋나 삭제 버튼이 셋째 줄로 떨어진다 (해 보고 되돌렸다).

   ★ 1440px 아래로는 손대지 않는다. 열세 칸을 다 펴려면 내용만
     1,150px 가까이 필요해서, 그보다 좁으면 이름 칸이 열 글자도 안 남는다.
     실제 이름은 '지역구 카페2_천안아산봉당신도시맘' 처럼 길다.
     ★ 33 에서 이름 칸이 하나 늘면서 1280 → 1440 으로 올렸다.
       그냥 두면 한글명과 utm 이 둘 다 뭉개져서, 한 줄로 편 것이 오히려
       읽기 어려워진다 — 시험(12-34)이 이 폭을 지킨다.
     ★★ 52 에서 단가 설명이 늘면서 1440 → 1560 으로 올렸다 (같은 이유).
       칸이 열넷이 됐다. 1440 에서 그대로 펴 보면 한글명 칸이 130px 아래로
       내려가 열 글자도 안 남는다 — 한 줄로 편 것이 오히려 읽기 어려워진다.
       ★ 1440~1559 에서는 두 줄로 나온다. 잃는 것이 아니다 — 두 줄 배치가
         원래 모습이고, 한 줄은 넉넉할 때만 주는 덤이다.
  ───────────────────────────────────────────────────────────────────────── */
@media (min-width:1560px){
  .mrow.camp{
    column-gap:6px;
    grid-template-columns:
      52px                 /* 순서         */
      26px                 /* 수집         */
      minmax(66px,84px)    /* 카테고리     */
      minmax(116px,1.3fr)  /* 한글명       */
      minmax(116px,1.3fr)  /* utm_campaign */
      112px 112px          /* 시작·종료    */
      minmax(78px,1fr)     /* 타겟 지역    */
      98px                 /* 집행 금액    */
      minmax(70px,.7fr)    /* 기준 단가    */
      minmax(92px,1.2fr)   /* 단가 설명 (52) */
      minmax(78px,.8fr)    /* 1일 집행금액 */
      58px 26px;           /* 사용·삭제    */
  }
  .mrow.camp > .camp2,
  .mrow.camp > .camp2-h{
    display:contents;
    /* 묶음에 걸려 있던 값은 상자가 없어지므로 쓰이지 않지만, 글자 크기는
       상속되므로 되돌려 준다 — 안 그러면 뒤 네 칸의 머리글만 작아진다. */
    font-size:inherit; opacity:1;
  }
  .mrow.camp > .camp2 > *,
  .mrow.camp > .camp2-h > *{ order:5; margin-top:0; }
  .mrow.camp > select[data-f="use"],
  .mrow.camp > .c-use{ order:8; }
  .mrow.camp > .del,
  .mrow.camp > .c-del{ order:9; }
  /* 한 줄이 되면 칸이 좁아진다. 긴 이름이 칸을 밀어내지 않도록 눌러 둔다. */
  .mrow.camp input[type=text]{ min-width:0; }
  /* 머리글이 칸보다 길면 줄바꿈 대신 말줄임으로 자른다.
     줄바꿈을 두면 머리글 줄만 두 줄이 되어 아래 줄과 칸이 어긋나 보인다.
     잘린 글자는 title 로 남겨 두었다 (adm-master.js). */
  .mrow.camp.mrow-h > span,
  .mrow.camp.mrow-h > .camp2-h > span{
    white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
  }
}
.campname{
  font-family:var(--mono); font-size:11.5px;
  background:var(--surface-2,rgba(0,0,0,.05)); border-radius:4px; padding:1px 5px;
}

/* 그날의 이슈 — 내용 한 칸 + 대시보드 체크 ★ 4개 ★ + 삭제
   ★ 체크박스에 글자를 붙여 둔다. 아이콘만 두면 무엇에 체크하는지 모른다.
   ★★ 2026-09-30(55판) — '캠페인별' 이 늘어 넷이 됐다. 체크 묶음은 auto 라
     제 폭을 스스로 잡지만, 내용 칸이 minmax(0,1fr) 이라 좁은 화면에서
     내용이 먼저 눌린다. 그래서 ★ 내용 칸에 바닥 ★ 을 깔아 둔다 —
     한 줄짜리 이슈 제목이 열 글자도 안 남는 것을 막는다.
   ★ 바닥보다 좁아지면 아래 @media 가 체크 묶음을 다음 줄로 내린다. */
.mrow.issue  {grid-template-columns:minmax(260px,1fr) auto 40px}
/* 첫 칸이 순서 번호가 아니라 '내용' 이라 가운데 정렬이 어울리지 않는다 */
.mrow.issue.mrow-h span:first-child{ text-align:left; }
/* ★ 칸이 넷이 되어 사이를 12 -> 10 으로 좁혔다 (한 줄에 들어가게) */
.mrow.issue .tgts{ display:flex; gap:10px; align-items:center; white-space:nowrap; }
.mrow.issue .tgts label{
  display:inline-flex; align-items:center; gap:5px;
  font-size:12px; color:var(--muted); cursor:pointer; user-select:none;
}
.mrow.issue .tgts input[type=checkbox]{ width:15px; height:15px; margin:0; cursor:pointer; }
.mrow.issue .tgts label.on{ color:var(--ink); font-weight:600; }
.mrow.issue .tgts.none label{ color:var(--warn); }
/* ★ 55판 — 칸이 넷이라 꺾이는 폭을 1100 -> 1280 으로 올렸다.
     안 올리면 1100~1280 구간에서 체크 넷이 내용 칸을 밀어내 제목이
     열 글자도 안 남는다. */
@media (max-width:1280px){
  .mrow.issue{ grid-template-columns:minmax(0,1fr) 40px; }
  .mrow.issue .tgts{ grid-column:1 / -1; margin-top:6px; flex-wrap:wrap; }
}
/* ★★★ 69판 — 현장 단계 격자 (2026-10-13 · 개편 A1)
     순서 40 · 단계 104 · 시작일 132 · 기준일 132 · 메모 1fr · 구간 170 ·
     지금 44 · 삭제 56
   ★ 날짜 칸을 ★ 132px ★ 로 둔다 — date 입력은 브라우저가 'yyyy-mm-dd' 와
     달력 단추를 같이 그린다. 120px 이면 크롬에서 연도가 깎인다. */
.mrow.phase  {grid-template-columns:40px 104px 132px 132px minmax(0,1fr) 170px 44px 56px}
/* ★★★ 칸 자리를 ★ 하나하나 못 박는다 ★ (자동 배치에 맡기지 않는다)
     ★ 왜 — 2026-10-13 에 여기서 밟았다 ★ : 아래 좁은 화면 규칙이 메모와
     구간을 `grid-column:2 / -1` 로 옮기자, 자동 배치가 ★ 그 뒤의 '지금' 과
     '삭제' 를 다음 줄 1·2번 칸으로 흘려보냈다 ★. 삭제 단추가 40px 칸에
     눌리는데 ★ 오류는 없고 레이아웃만 조용히 어긋난다 ★.
     (admin.css 의 '그날의 이슈' 주석에도 같은 사고가 적혀 있다)
   ★ 이름표(.phlb)는 넓은 화면에서 감춘다 — display:none 은 격자에서 아예
     빠지므로, 아래 칸 번호를 흔들지 않는다. */
.mrow.phase > .phlb{ display:none; }
.mrow.phase > .phno                    { grid-column:1; }
.mrow.phase > select[data-f="phcode"]  { grid-column:2; }
.mrow.phase > input[data-f="phstart"]  { grid-column:3; }
.mrow.phase > input[data-f="phbase"]   { grid-column:4; }
.mrow.phase > input[data-f="phmemo"]   { grid-column:5; }
.mrow.phase > .phspan                  { grid-column:6; }
.mrow.phase > .phcur                   { grid-column:7; }
.mrow.phase > .phdel                   { grid-column:8; }
/* ★ 구간은 ★ 읽기 전용 계산값 ★ 이다 — 입력칸처럼 보이면 안 된다.
     사람이 고쳐 보려 눌렀다가 '왜 안 되지' 가 되면, 그 다음에 하는 일은
     ★ 다른 칸의 날짜를 억지로 맞추는 것 ★ 이다. */
.mrow.phase .phspan{
  font-family:var(--mono); font-size:11.5px; color:var(--muted);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
/* ★ 지금 단계 표시 — 점 하나. 글자('현재')를 넣으면 44px 를 넘는다 */
.mrow.phase .phcur{ text-align:center; color:var(--blue); font-size:15px; line-height:1; }
.mrow.phase .phno{ text-align:center; }
.mrow.phase input[type=date]:disabled{
  background:var(--line); color:var(--faint); cursor:not-allowed;
}
.mrow.phase input[data-f="phmemo"]{ font-size:12.5px; }
/* ★ 1180px 밑에서는 메모와 구간을 다음 줄로 내린다. 안 내리면 날짜 칸이 눌려
     'yyyy-mm-dd' 가 잘린다 — ★ 날짜가 잘리면 이 격자는 쓸 수 없다 ★.
   ★★ '지금' 과 '삭제' 는 ★ 줄과 칸을 둘 다 못 박는다 ★ (위 주석의 사고). */
@media (max-width:1180px){
  .mrow.phase{ grid-template-columns:40px 104px 132px 132px 44px 56px; }
  .mrow.phase > .phcur                 { grid-column:5; grid-row:1; }
  .mrow.phase > .phdel                 { grid-column:6; grid-row:1; }
  .mrow.phase > input[data-f="phmemo"] { grid-column:2 / -1; grid-row:2; margin-top:6px; }
  .mrow.phase > .phspan                { grid-column:2 / -1; grid-row:3; margin-top:4px; }
  /* 머리글의 메모·구간을 감춘다 — 남은 여섯이 1~6 칸에 그대로 떨어진다 */
  .mrow.phase.mrow-h > span:nth-child(5),
  .mrow.phase.mrow-h > span:nth-child(6){ display:none; }
}
/* ★★★ 900px 밑 — ★ 프로젝트 공통 규칙이 순서 손잡이(.grip)와 머리글을
     감춘다 ★. 그래서 ① 칸이 하나 줄고 ② 날짜 둘의 이름이 사라진다.
     ★ 날짜 둘을 바꿔 넣으면 관측창이 두 달 틀어지는데 ★ 화면에는 아무
     표시가 없다 ★ — 그래서 줄 안의 이름표(.phlb)를 여기서 켠다.
   ★ 두 칸으로 세우고 ★ 줄·칸을 전부 못 박는다 ★. */
@media (max-width:900px){
  .mrow.phase{ grid-template-columns:minmax(0,1fr) minmax(0,1fr); gap:8px; }
  .mrow.phase > .phlb{
    display:block; font-family:var(--mono); font-size:10.5px; font-weight:600;
    letter-spacing:.04em; color:var(--faint); margin-bottom:-4px;
  }
  .mrow.phase > select[data-f="phcode"]  { grid-column:1 / -1; grid-row:1; }
  .mrow.phase > .phlb.s                  { grid-column:1; grid-row:2; }
  .mrow.phase > .phlb.b                  { grid-column:2; grid-row:2; }
  .mrow.phase > input[data-f="phstart"]  { grid-column:1; grid-row:3; }
  .mrow.phase > input[data-f="phbase"]   { grid-column:2; grid-row:3; }
  .mrow.phase > input[data-f="phmemo"]   { grid-column:1 / -1; grid-row:4; margin-top:2px; }
  .mrow.phase > .phspan                  { grid-column:1 / -1; grid-row:5; margin-top:0; }
  /* ★ 칸이 넓어졌으니 단추를 ★ 늘리지 않는다 ★ — 격자 자식은 기본이
       stretch 라, 그냥 두면 삭제 단추가 화면 절반을 덮는 빨간 띠가 된다. */
  .mrow.phase > .phcur                   { grid-column:1; grid-row:6; text-align:left;
                                           justify-self:start; }
  .mrow.phase > .phdel                   { grid-column:2; grid-row:6;
                                           justify-self:end; }
  /* 설명표의 꼬리표 칸도 좁은 화면에서는 한 줄을 통째로 쓴다 */
  .phhelp > div{ grid-template-columns:minmax(0,1fr); gap:2px; }
}

/* 단계 꼬리표 — 안내문과 '지금 이 현장은 …' 줄에서 쓴다.
   ★ 색으로만 구별하지 않는다 ★ : 글자가 늘 같이 있다 (색각 이상 · 흑백 인쇄) */
.phtag{
  display:inline-block; padding:1px 7px; border-radius:999px;
  font-size:11.5px; font-weight:700; white-space:nowrap;
  border:1px solid transparent;
}
.phtag.teaser{ background:var(--blue-soft);  color:var(--blue); }
.phtag.sale  { background:var(--good-soft);  color:var(--good); }
.phtag.remain{ background:var(--warn-soft);  color:var(--warn); }
.phtag.done  { background:var(--line);       color:var(--muted); }
.phtag.none  { background:var(--line);       color:var(--faint); }

/* 단계 설명표 — 꼬리표와 설명을 두 칸으로 세운다.
   ★ 격자로 두는 이유 : 설명 길이가 달라도 꼬리표 오른쪽 끝이 맞는다.
     흐름에 두면 줄마다 설명 시작점이 달라져 네 줄이 삐뚤게 보인다. */
.phhelp{ display:grid; gap:6px; margin:10px 0; }
.phhelp > div{ display:grid; grid-template-columns:78px minmax(0,1fr); gap:10px;
  align-items:start; }
.phhelp > div > span:last-child{ font-size:12.5px; color:var(--muted); }
@media (max-width:640px){
  .phhelp > div{ grid-template-columns:minmax(0,1fr); gap:2px; }
}

.mrow-h{font-family:var(--mono);font-size:10.5px;font-weight:600;letter-spacing:.06em;
  text-transform:uppercase;color:var(--faint);border-bottom:1px solid var(--line-2);padding-bottom:8px}
.mrow .grip{font-family:var(--mono);font-size:11px;color:var(--faint);text-align:center}
.mrow input[type=text],.mrow input[type=date],.mrow select{
  width:100%;border:1px solid var(--line-2);border-radius:6px;padding:7px 9px;
  background:var(--surface);color:var(--ink);font-size:13px;
}
.mrow input:focus,.mrow select:focus{outline:0;border-color:var(--blue);box-shadow:0 0 0 3px var(--blue-soft)}
.mrow input[type=color]{width:38px;height:32px;padding:2px;border:1px solid var(--line-2);
  border-radius:6px;background:var(--surface);cursor:pointer}
.mrow .del{
  appearance:none;border:1px solid var(--line-2);background:var(--surface);
  border-radius:6px;width:32px;height:32px;cursor:pointer;color:var(--muted);font-size:15px;line-height:1;
}
.mrow .del:hover{border-color:var(--bad);color:var(--bad);background:var(--bad-soft)}
.mrow.removed{opacity:.45}
.mrow.removed input,.mrow.removed select{text-decoration:line-through}
.mrow .used{font-family:var(--mono);font-size:11px;color:var(--faint);text-align:center}
.mrow .used.has{color:var(--warn);font-weight:600}
.addrow{margin-top:12px}

/* ---- 마스터 순서 칸 ------------------------------------------------------
   [번호] [끌기 손잡이] [▲/▼]  — 화면에 보이는 번호가 곧 저장되는 sortOrder 다.
-------------------------------------------------------------------------- */
.mrow .ord{display:flex;align-items:center;gap:3px}
.mrow .onum{
  font-family:var(--mono);font-size:11px;font-weight:600;color:var(--faint);
  min-width:15px;text-align:right;font-style:normal;
}
.mrow .grip{
  cursor:grab;color:var(--line-2);font-size:13px;line-height:1;
  padding:2px 1px;border-radius:4px;user-select:none;
}
.mrow .grip:hover{color:var(--muted);background:var(--line)}
.mrow .grip:active{cursor:grabbing}
.mrow .mvs{display:flex;flex-direction:column;gap:1px}
.mrow .mv{
  appearance:none;border:1px solid var(--line-2);background:var(--surface);
  color:var(--muted);width:20px;height:15px;padding:0;border-radius:4px;
  font-size:8px;line-height:1;cursor:pointer;
}
.mrow .mv:hover:not(:disabled){border-color:var(--blue);color:var(--blue);background:var(--blue-soft)}
.mrow .mv:focus-visible{outline:2px solid var(--blue);outline-offset:1px}
.mrow .mv:disabled{opacity:.3;cursor:default}
/* 끌고 있는 행 — 어디로 가는지 보이도록 자리만 남긴다 */
.mrow.dragging{opacity:.4;background:var(--blue-soft)}

@media (max-width:900px){
  .shell{grid-template-columns:1fr}
  .lnb{
    position:static;max-height:none;border-right:0;border-bottom:1px solid var(--line);
    display:flex;overflow-x:auto;padding:8px;gap:4px;
  }
  .lnb-group{display:flex;align-items:center;gap:4px}
  .lnb-group + .lnb-group{margin-top:0;padding-top:0;border-top:0;border-left:1px solid var(--line);
    margin-left:6px;padding-left:6px}
  .lnb-h{display:none}
  .nav-i,.lnb-a{white-space:nowrap;border-left:0;border-bottom:2px solid transparent;padding:8px 12px}
  .nav-i.on,.lnb-a.on{border-left:0;border-bottom-color:var(--blue)}
  .lnb-a .cnt{display:none}
  .main{padding:16px 14px 90px}
  .savebar{margin:0 -14px -90px}
  .zrow,.zrow-h{grid-template-columns:minmax(0,1fr) 92px 92px;gap:8px}
  .mrow.zone,.mrow.chan,.mrow.event{grid-template-columns:1fr 1fr;gap:8px}
  /* 좁은 화면에서는 2열이라 3 / -1 이 성립하지 않는다. 한 줄 전체로 돌린다. */
  .mrow.chan > .chnote,
  .mrow.chan > .chnote-h{ grid-column:1 / -1; grid-template-columns:1fr; }
  .mrow.event > .ezwrap{ grid-column:1 / -1; }
  .mrow.event > .evopt{ grid-column:1 / -1; }
  /* 칸 하나가 화면 폭을 넘지 않게 — 넘으면 가로 스크롤이 생긴다 */
  .ez{ grid-template-columns:26px minmax(0,1fr) 58px 24px; width:100%; }
  .ezwrap .ezbox{ width:100%; }
  /* 좁은 화면에서는 끌어놓기가 사실상 안 된다. 손잡이는 감추고 ▲▼ 만 남긴다. */
  .mrow .grip{display:none}
  .mrow .ord{gap:6px}
  /* 2단으로 접히면 머리글이 어느 칸을 가리키는지 알 수 없게 된다.
     자리만 차지하고 오히려 잘못 읽히므로 감춘다 (칸마다 placeholder 가 있다). */
  .mrow-h{display:none}
}
@media (prefers-reduced-motion:reduce){*{transition:none!important}}

/* 마스터 헤더의 '집계' 열은 좁게 */
.mrow-h .grip,.mrow-h span:first-child{text-align:center;font-size:10px}

/* 그날의 이슈 행 — 규칙은 위 '마스터 편집 행' 쪽에 모아 두었다.
   ★ 여기 있던 2열짜리 규칙(내용 + 삭제)이 위에서 만든 3열 규칙을 덮어써서,
     대시보드 체크박스가 40px 칸에 눌려 삭제 버튼이 다음 줄로 밀렸다.
     선택자 무게가 같으면 뒤에 오는 쪽이 이긴다 — 같은 선택자를 두 군데
     두면 반드시 이런 일이 생긴다. 그래서 한 군데로 합쳤다. */


/* ---- CSV 업로드 ---------------------------------------------------------- */
.kinds{display:grid;grid-template-columns:repeat(auto-fill,minmax(168px,1fr));gap:9px}
.kind{
  appearance:none;cursor:pointer;text-align:left;
  background:var(--surface);border:1px solid var(--line-2);border-radius:9px;
  padding:11px 13px;transition:.14s;display:flex;flex-direction:column;gap:2px;
}
.kind:hover{border-color:var(--blue);background:var(--surface-2)}
.kind.on{border-color:var(--blue);background:var(--blue-soft);box-shadow:0 0 0 1px var(--blue)}
.kind b{font-size:13.5px;color:var(--ink)}
.kind.on b{color:var(--blue)}
.kind span{font-size:12px;color:var(--muted)}
/* 양식 버튼 줄은 카드 격자 안이 아니라 한 줄 전체를 쓴다 */
.kind-act{grid-column:1/-1;display:flex;gap:10px;margin-top:6px;flex-wrap:wrap;
  align-items:center;padding-top:12px;border-top:1px solid var(--line)}

.drop{
  border:2px dashed var(--line-2);border-radius:10px;background:var(--surface-2);
  padding:26px 20px;text-align:center;transition:.14s;
  display:flex;flex-direction:column;align-items:center;gap:9px;
}
.drop.over{border-color:var(--blue);background:var(--blue-soft)}
.drop b{font-size:14px;color:var(--ink-2)}
.drop .hint{margin:0}
.fileline{
  display:flex;flex-wrap:wrap;align-items:baseline;gap:10px;margin-top:13px;
  padding:11px 14px;background:var(--surface-2);border:1px solid var(--line);border-radius:8px;
}
.fileline b{font-size:13.5px}
.fileline span{font-family:var(--mono);font-size:12px;color:var(--muted)}
.tpl-range{display:inline-flex;align-items:center;gap:6px;flex-wrap:wrap}
.tpl-range label{font-size:12.5px;color:var(--muted)}
.tpl-range span{color:var(--faint)}
.tpl-range input[type=date]{
  border:1px solid var(--line-2);border-radius:6px;padding:5px 8px;
  background:var(--surface);color:var(--ink);font-size:12.5px;
}

/* ============================================================================
   보고서 파일 (38_site_report.sql)
   ★ 현장 마스터 6번 칸. 이 줄만 '마스터 저장' 과 무관하다 —
     파일은 고르는 즉시 올라가고 내리는 것도 그 자리에서 끝난다.
   ========================================================================== */
/* ---- 소스(플랫폼) 한글명 (39_online_basis.sql) -----------------------------
   GA4 원문 | 세션수 | 이 현장에서 부를 이름
   ★ 원문 칸을 고정폭으로 둔다. 소스 이름은 대개 짧고(naver·google·daum)
     길이가 제각각이라, 1fr 로 두면 줄마다 입력칸의 왼쪽 끝이 어긋나서
     열 개만 넘어가도 읽기가 어려워진다. */
/* ★ 42 (2026-09-27) — 넷째 칸이 생겼다 (차트에서 뺄지).
     제외 칸은 체크 하나라 고정 폭이다. minmax(0,1fr) 로 두면 이름 칸이
     줄어드는 대신 체크만 가운데로 멀어져 한 줄이 읽기 어려워진다. */
.mrow.src{
  grid-template-columns:200px 90px minmax(0,1fr) 54px;
}
.mrow.src .srcname{
  font-family:var(--mono); font-size:12.5px; color:var(--ink-2);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.mrow.src .srcn{
  text-align:right; color:var(--ink-3); font-size:12.5px;
  font-variant-numeric:tabular-nums;
}
/* ★ 머리글에서 마지막 칸(제외)만 가운데다. 이름 칸은 왼쪽이 맞다 —
     :last-child 로 뭉뚱그리면 이름 머리글까지 가운데로 간다. */
.mrow.src.mrow-h span:nth-child(3){ text-align:left; }
.mrow.src.mrow-h span.c{ text-align:center; font-size:12px; }

/* 제외 체크 */
.mrow.src .srchide{
  display:flex; align-items:center; justify-content:center; cursor:pointer;
}
.mrow.src .srchide input{ cursor:pointer; }

/* ★ 제외한 줄은 흐리게. 지운 것이 아니라 '안 보이게 한 것' 이라,
     목록에는 남아 있어야 다시 켤 수 있다 (구역의 .off 와 같은 규칙). */
.mrow.src.off .srcname,
.mrow.src.off .srcn{ opacity:.45; }
.mrow.src.off input[data-f="ko"]{ opacity:.45; }

/* 도넛에 보여 줄 개수 — 목록 위 한 줄 */
.srctop{
  display:flex; align-items:center; gap:10px; flex-wrap:wrap;
  padding:10px 12px; margin-bottom:12px;
  background:var(--panel-2); border:1px solid var(--line); border-radius:8px;
}
.srctop label{ font-size:13px; color:var(--ink-2); font-weight:600; }
.srctop input{ width:80px; text-align:center; }
.srctop .hint{ margin:0; }

@media (max-width:760px){
  /* 좁은 화면에서는 원문·세션수·제외를 한 줄, 입력칸을 그 밑에 통째로 */
  .mrow.src{ grid-template-columns:minmax(0,1fr) 90px 54px; }
  .mrow.src input[data-f="ko"]{ grid-column:1 / -1; margin-top:6px; }
}

.rptrow{
  display:grid; align-items:center; gap:10px;
  grid-template-columns:150px 1fr auto;
  padding:12px 0; border-bottom:1px solid var(--line);
}
.rptrow:last-child{ border-bottom:0; }
.rptrow .rk{ font-weight:600; color:var(--ink); }
.rptrow .rv{ min-width:0; display:flex; flex-direction:column; gap:2px; }
.rptrow .rv a{ color:var(--blue); word-break:break-all; }
.rptrow .rmeta{ font-size:11.5px; color:var(--muted); }
.rptrow .rnone{ font-size:12px; color:var(--faint); }
.rptrow .ra{ display:flex; align-items:center; gap:6px; }
/* 파일 고르기는 label 이 버튼처럼 보이게 한다 (input[type=file] 은 숨긴다) */
.rptrow .ra label.btn{ cursor:pointer; margin:0; }
/* 진행 막대와 안내는 줄 전체 너비를 쓴다 */
.rptrow .rprog{
  grid-column:1 / -1; height:4px; border-radius:2px;
  background:var(--line); overflow:hidden;
}
.rptrow .rbar{ display:block; height:100%; width:0; background:var(--blue); }
.rptrow .rmsg{ grid-column:1 / -1; font-size:12px; color:var(--muted); }
.rptrow .rmsg.bad{ color:var(--bad); font-weight:600; }
.rptrow .rmsg:empty{ display:none; }

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

/*==============================================================================
  이벤트v2 입력 폼 (adm-eventv2.js) — 2026-10-01 · 62판
  ★★ 이 폼은 ★ 화면을 짜는 폼 ★ 이다. 그래서 ★ 결과와 닮게 ★ 보이도록 한다 —
     특히 '선추가' 는 ★ 폼에서도 세로선이 보여야 ★ 어디에 그어질지 알 수 있다.
     안 보이면 저장하고 대시보드를 열어 봐야 알게 되고, 그 왕복이 비싸다.
  ★ input 에 .inp 를 ★ 바로 ★ 걸었다. admin.css 의 .inp 는 원래 ★ 감싸는 상자 ★
    라서 안쪽 여백 규칙(.inp input)이 안 걸린다. 그래서 여기서 따로 준다.
==============================================================================*/
.evl-block{
  border:1px solid var(--line-2); border-radius:10px;
  padding:14px 16px 10px; margin-bottom:14px; background:var(--surface);
}
.evl-block input.inp, .evl-tbl input.inp{
  height:34px; padding:0 10px; font-size:13.5px; width:100%;
  border:1px solid var(--line-2); border-radius:7px; background:var(--surface);
  color:var(--ink); box-sizing:border-box;
}
.evl-block input.inp:focus, .evl-tbl input.inp:focus{
  outline:none; border-color:var(--blue); box-shadow:0 0 0 3px var(--blue-soft);
}
.evl-block-h{ display:flex; gap:8px; align-items:center; margin-bottom:10px; }
.evl-block-h .inp{ flex:1; font-weight:600; }
.evl-no{
  flex:none; min-width:22px; height:22px; line-height:22px; text-align:center;
  font-size:12px; color:var(--muted);
  border:1px solid var(--line-2); border-radius:5px;
}
.evl-sub{
  font-size:12.5px; font-weight:600; color:var(--muted);
  margin:12px 0 6px; padding-top:10px; border-top:1px dashed var(--line);
}
/* ── 스코어 카드 ── */
.evl-cards{ display:flex; flex-wrap:wrap; gap:8px; }
.evl-card{
  display:flex; gap:6px; align-items:center;
  border:1px solid var(--line); border-radius:8px; padding:6px 8px;
  background:var(--bg, transparent); min-width:360px; flex:1 1 360px;
}
.evl-card .inp{ flex:1; }
.evl-card .evl-v{ max-width:180px; }
/* ── 표 ── */
.evl-tbl{
  border:1px solid var(--line); border-radius:8px;
  padding:10px 12px; margin-bottom:10px;
}
.evl-tbl-h{ display:flex; gap:8px; align-items:center; margin-bottom:8px; }
.evl-tbl-h .inp{ flex:1; font-weight:600; }
.evl-n{
  display:flex; align-items:center; gap:6px; flex:none;
  font-size:12.5px; color:var(--muted);
}
.evl-n input.inp{ width:68px; text-align:right; }
/* 열 */
.evl-cols{ display:flex; flex-wrap:wrap; gap:8px; align-items:flex-start; }
.evl-col{
  /* ★ 세로 한 줄로 쌓으면 × 단추가 ★ 칸 너비만큼 늘어나 ★ 입력칸처럼 보인다.
       (2026-10-01 화면을 찍어 보고 고쳤다)
     ★ 그래서 ★ 열 제목만 한 줄 ★ 을 차지하고, 선추가와 × 는 그 아래
       ★ 나란히 ★ 둔다. */
  display:flex; flex-wrap:wrap; gap:6px; align-items:center;
  padding:6px 8px; border:1px solid var(--line); border-radius:7px;
  min-width:160px;
}
.evl-col input[type=text]{ flex:1 1 100%; }
.evl-col .evl-ln{ flex:1 1 auto; }
.evl-col .evl-x{ flex:none; }
/* ★★ 선추가 — ★ 폼에서도 ★ 그 열 ★ 왼쪽 ★ 에 세로선을 보여 준다.
     대시보드에 그려질 자리와 ★ 같은 쪽 ★ 이어야 한다. 오른쪽에 그리면
     사람이 한 칸 옆으로 읽어 엉뚱한 열에 체크한다. */
.evl-col.on{ border-left:3px solid var(--blue); }
/* ★★ '앞과 잇기' (2026-10-06) — 앞 덩어리에 ★ 붙은 ★ 열.
     ★ 색 띠(파란 세로선)를 ★ 안 준다 ★ : 화면에서도 띠는 덩어리의 ★ 시작
       칸에만 ★ 붙는다. 여기에 띠를 그리면 폼과 화면이 달라 보인다.
     ★ 대신 ★ 바탕을 깐다 ★ — 화면에서 이 열이 받는 것이 바로 '바탕' 이다.
       그래서 폼에서도 선추가 열부터 여기까지가 ★ 한 줄기로 ★ 읽힌다. */
.evl-col.jn{ background:var(--blue-soft); }
.evl-ln{
  display:flex; align-items:center; gap:4px;
  font-size:12px; color:var(--muted); cursor:pointer; user-select:none;
}
.evl-ln input{ margin:0; }
/* ★ 켜 두었지만 ★ 지금은 아무 일도 안 하는 ★ 체크 (앞에 선추가한 열이 없다).
     ★ 지우지 않고 ★ 흐리게 ★ 둔다 — 감추면 켜 둔 것을 끌 방법이 없어진다.
     까닭은 title 로 알려 준다. */
.evl-ln.evl-jn.dead{ color:var(--faint); text-decoration:line-through; }
/* 행 */
.evl-rows{ margin-top:10px; display:flex; flex-direction:column; gap:6px; }
.evl-row{ display:flex; gap:6px; align-items:center; }
.evl-row input.inp{ flex:1 1 0; min-width:0; }
.evl-row input.inp.ln{ border-left:3px solid var(--blue); }
/* ★ '앞과 잇기' 인 칸 — 머리글과 ★ 같은 바탕 ★ 을 깐다 (위 .evl-col.jn 참고).
     행에서도 덩어리가 어디까지인지 보여야, 값을 적다가 한 칸 옆으로 밀려
     ★ 엉뚱한 열에 적는 ★ 일이 줄어든다. */
.evl-row input.inp.jn{ background:var(--blue-soft); }
.evl-x{ flex:none; padding:0 8px; }
.evl-empty{
  font-size:13px; color:var(--muted); padding:14px;
  border:1px dashed var(--line-2); border-radius:8px; text-align:center;
}
@media (max-width:900px){
  .evl-card{ min-width:0; flex:1 1 100%; }
  .evl-row{ flex-wrap:wrap; }
  .evl-row input.inp{ flex:1 1 120px; }
}

/* 이벤트v2 — 여백 열 (2026-10-01)
   ★ 적을 것이 없는 열이라 ★ 좁고 수수하게 ★ 둔다. 다른 열과 같은 크기로
     두면 '제목을 안 적은 열' 로 잘못 읽힌다. */
.evl-col.gap{
  min-width:0; flex:0 0 auto; gap:4px;
  background:repeating-linear-gradient(45deg,
    transparent 0 5px, var(--line) 5px 6px);
  border-style:dashed;
}
.evl-gaplb{ font-size:12px; color:var(--muted); font-weight:600; }
/* 행에서도 ★ 자리만 ★ 차지한다 (적을 수 없다) */
.evl-gapcell{
  flex:0 0 26px; height:34px; border-radius:7px;
  background:repeating-linear-gradient(45deg,
    transparent 0 5px, var(--line) 5px 6px);
}

/* 이벤트v2 — 열·여백을 끌어서 자리 바꾸기 (2026-10-01)
   ★ 손잡이에서만 끌 수 있다 ★ : 제목 칸의 글자를 고르려다 열이 끌려가면
     자리가 조용히 바뀐다 (현장마스터의 .mrow .grip 과 같은 규칙). */
.evl-col .grip{
  cursor:grab; color:var(--line-2); font-size:12px; line-height:1;
  padding:2px 3px; border-radius:4px; user-select:none; flex:0 0 auto;
}
.evl-col .grip:hover{ color:var(--muted); background:var(--line-soft, transparent); }
.evl-col .grip:active{ cursor:grabbing; }
.evl-col.dragging{ opacity:.45; }
/* 끼어들 자리를 ★ 선으로 ★ 보여 준다 — 어디에 놓이는지 모르면 몇 번씩
   다시 끌게 된다. 왼쪽/오른쪽을 구분해 그린다. */
.evl-col.drop-before{ box-shadow:-3px 0 0 0 var(--blue); }
.evl-col.drop-after { box-shadow: 3px 0 0 0 var(--blue); }
/* 여백 블록은 좁아서 손잡이가 한 줄을 다 먹으면 안 된다 */
.evl-col.gap .grip{ padding:2px 1px; }
