/*
 * NEOSGEN 라이선스 발급 관리 — 스타일
 *
 * @since   2026.09.04
 * @version 2.48
 *
 * == Modification Information ==
 * 2026.09.22  정경하  v2.48 대역표 DB 사본 칸(.geo-copy) · 되살림 태그(.geo-tag-restored).
 * 2026.09.21  정경하  v2.47 대역표 지문 경고(.geo-tamper).
 * 2026.09.21  정경하  v2.46 주소 알아보기(.probe-*) · 대역표 파일 요약(.geo-sum).
 * 2026.09.21  정경하  v2.45 허용 예외 표 - 좁은 화면에서도 [저장] · [삭제] 가 안 잘리게
 *                     마지막 칸들을 고정 폭으로. 비율(%)만으로는 화면이 좁아지면 모자란다.
 * 2026.09.21  정경하  v2.44 긴 안내 알약이 낱말마다 끊기던 것(.acc-pill-soft 는 inline-flex 가
 *                     아니라 inline-block 이어야 한다 - flex 는 글자 토막마다 gap 을 넣는다) ·
 *                     허용 예외 표가 옆으로 잘리던 것(칸 너비를 정하고 입력을 100% 로).
 * 2026.09.21  정경하  v2.43 앵커로 돌아온 칸이 상단바에 가리지 않게(scroll-margin-top).
 * 2026.09.21  정경하  v2.42 접속 구분 차단(.zone-* · .allow-*) - 구분 타일 · 예외 표.
 * 2026.09.21  정경하  v2.41 설정 → 접속 구분(.geo-*) - 카드 · 주소 칸 여럿 · 상태 태그 색.
 *                     ★ .tag 는 색이 없으면 흰 글자만 남아 안 보인다(배경을 꼭 준다).
 * 2026.09.18  정경하  v2.40 접속 시간 제한 · 세션 두 칸 - 타일 갈래(.acc-tiles-3 · -2) ·
 *                     스위치 없는 카드(.acc-always · .acc-tag) · 칸 밑 도움말(.acc-hint).
 * 2026.09.18  정경하  v2.39 안쪽 칸도 :hover 대신 .sb-on(common.js)으로. 크롬은 :hover 로는
 *                     스크롤바를 바로 다시 그리지 않아, 들어가고 나갈 때 색이 늦게 따라왔다.
 * 2026.09.17  정경하  v2.38 페이지 스크롤바도 "쓸 때만" 또렷이 - 창 안에 마우스가 있으면 늘 호버라
 *                     늘 색이 보였다. 페이지는 body.sb-on(스크롤 중 · 오른쪽 가장자리)일 때만.
 * 2026.09.17  정경하  v2.37 스크롤바 - 회색 기본 막대 대신 파스텔 그라데이션(평소 흐리게 · 호버 때 또렷이).
 * 2026.09.17  정경하  v2.36 알약 버튼(.pbtn*) · 파일 올리기(.up-*) · 공휴일 선택 삭제(.hol-selbar · .hol-cb).
 * 2026.09.17  정경하  v2.35 바뀐 것이 없어 막힌 저장 버튼(.is-clean).
 * 2026.09.17  정경하  v2.34 접속 제한 다시 그림 - 넓은 카드 · 타일 · 시각 알약 · 요일 동그라미 · 꺼지면 흐리게.
 * 2026.09.17  정경하  v2.33 설정 → 접속 제한(.acc-*).
 * 2026.09.17  정경하  v2.32 설정 → 공휴일 관리(.hol-*).
 * 2026.09.16  정경하  v2.31 켜고 끄는 조건(.fx · .fx-body · .fx-opt) · 로그 줄의 남긴 자리(.au-site).
 *                     ★ 파스텔 알약 스위치. 켜면 딸린 칸이 옆에 톡 나온다.
 *                       색 넷(.fx-mint · .fx-lilac · .fx-peach · .fx-sky)은 변수만 바꾼다 -
 *                       조건마다 색이 달라야 켜 둔 것이 한눈에 보인다.
 *                     ★ 모든 검색 화면이 이것 한 벌을 쓴다. 화면마다 새로 만들지 않는다.
 *                     ★ 켜짐은 .is-on(JS) 과 :has(체크됨) 둘 다로 그린다 - 뒤로 가기로
 *                       돌아와 JS 가 늦게 돌아도 모양이 맞는다.
 * 2026.09.16  정경하  v2.30 오류 카드를 화면 한가운데로.
 *                     main 이 max-width 1180 에 왼쪽 붙음이라, 넓은 모니터에서 카드가
 *                     왼쪽으로 쏠려 보였다. 오류 화면만 폭 제한을 풀고 위아래도 가운데.
 * 2026.09.16  정경하  v2.29 오류 화면(.err-*) - 403 · 404 · 500.
 *                     ★ 큰 숫자 · 떠다니는 빛 방울 · 유리 카드 · 이모지. 막힌 순간에
 *                       딱딱한 안내문보다 무엇이 어떻게 됐는지가 먼저 보이게.
 *                     ★ 갈래마다 색이 다르다 - 권한 없음 코랄 · 시간 만료 금색 ·
 *                       길 잃음 청록 · 서버 오류 보라.
 *                     ★ 움직임은 줄이기 설정(prefers-reduced-motion)을 따른다.
 * 2026.09.16  정경하  v2.28 시험 발송 결과 상자(.set-result) · 보내는 중 버튼(.is-busy).
 *                     ★ 누른 뒤 반응이 없으면 여러 번 누르게 된다. 도는 표시와
 *                       "보내는 중" 문구로 기다리는 중임을 보인다.
 * 2026.09.16  정경하  v2.27 설정 화면을 왼쪽 세로 메뉴 + 칸으로(.set-layout · .set-nav ·
 *                     .set-bento · .set-panel · .set-tpl · .set-logos).
 *                     ★ 요즘 설정 화면의 틀이다 - 메뉴는 고정해 두고 오른쪽만 바뀐다.
 *                       첫 칸은 카드로 현황을 한눈에 보인다.
 *                     ★ 좁은 화면에서는 메뉴가 위로 올라가 가로로 흐른다.
 *                     v2.26 의 .set-card · .set-state 는 이 틀로 바꾸며 걷었다.
 * 2026.09.16  정경하  v2.26 설정 화면(.set-*) · 관리 갈래 다섯일 때 폭(.seg-admin-5).
 *                     설정 폼은 입력 창의 칸 규칙(.modal-form .field)을 그대로
 *                     빌려 쓰고, 바탕만 카드로 준다(.set-card).
 * 2026.09.16  정경하  v2.25 로그의 접속 구분 뱃지(.tag-zone-*).
 *                     접속 자리 칸을 넓히고 주소 옆에 뱃지를 붙인다.
 *                     ★ 해외만 진하게. 매일 보는 사내·국내가 눈을 끌면
 *                       정작 봐야 할 해외가 묻힌다.
 * 2026.09.11  정경하  v2.24 보드 카드의 날짜를 오른쪽 끝에 고정.
 *                     ★ "n번 나감" 뱃지가 붙은 카드만 날짜가 밀려, 카드마다
 *                       날짜 자리가 달랐다. 세로로 줄이 안 맞으면 눈이
 *                       카드마다 다시 찾는다.
 *                     소속이 사내인 카드는 색을 달리 한다.
 * 2026.09.11  정경하  v2.23 서버 보드(.board-* · .svcard-*).
 *                     ★ 벤토 격자다. 카드 하나가 서버 하나이고, 신호등 한 줄로
 *                       상태를 말한다. 3D 는 안 쓴다 - 폐쇄망이라 라이브러리를
 *                       500KB 넣어야 하고, 각도가 생기면 가려지고 크기 비교가
 *                       안 된다. 연결 구조는 평면이 더 잘 읽힌다.
 *                     ★ 손봐야 할 것을 위에 먼저 알린다. 카드를 스무 장
 *                       늘어놓고 "알아서 찾으세요" 가 되면 많을수록 안 본다.
 *                     ★ 움직임은 hover 에서 2px 만. 요란한 연출은 한 번은
 *                       재미있고 그다음부터는 방해다.
 * 2026.09.10  정경하  v2.22 ★ v2.21 이 화면에서 안 먹은 이유 - 특이도다.
 *                       .field 안에 이미 걸려 있는 규칙이 더 셌다.
 *                         .field label (0,1,1) > .pick-row       (0,1,0)
 *                           → display:block 이 이겨 줄이 세로로 쌓였다
 *                         .field input (0,1,1) = .pick-row input (0,1,1)
 *                           → width 만 이기고 padding·border 는 남아
 *                             체크박스가 테두리 있는 넓은 상자가 됐다
 *                       .field 로 감싸 특이도를 올리고, 물려받은 것을 되돌린다.
 *                       ★ 클래스를 새로 만들 때는 <b>그 자리에 이미 걸려 있는
 *                         규칙</b>부터 본다. 이름이 없는 것과 안 걸리는 것은
 *                         다른 일이다.
 *                     비밀번호 칸(토닥 방식) · .field select 도 함께.
 * 2026.09.10  정경하  v2.21 체크박스 한 줄을 위한 자리(.pick-row)를 따로 둔다.
 *                     ★ v2.20 에서 .adm-showall 을 빌려 썼다가 줄이 뭉갰다.
 *                       그것은 <b>검색막대에 한 줄로 붙는 자리</b>라
 *                       inline-flex 에 white-space:nowrap 이고, 여러 줄을
 *                       세로로 쌓지 못한다. 코드와 이름 둘을 넣으니 서로
 *                       겹쳐 "JYES JY알림 시TESTS 시험용" 이 됐다.
 *                     ★ 빌려 올 때는 그 이름이 무엇에 걸려 있었는지부터 본다(v63).
 *                       "이미 있는 이름을 쓴다" 는 규칙이, 쓰임이 다른 것을
 *                       끌어다 쓰라는 뜻은 아니다.
 * 2026.09.10  정경하  v2.20 고를 것을 여러 줄로 늘어놓는 자리(.pick-list).
 *                     계정 창의 "맡을 제품" 체크박스가 쓴다.
 * 2026.09.10  정경하  v2.19 프리즘 라인을 <진짜> 원본(prismline)으로.
 *                          5색 · background-position 이 흐름 · 펼치면 커짐
 * 2026.09.10  정경하  v2.18 gradline 을 옮겼으나 그건 다른 프리셋이었다
 * 2026.09.10  정경하  v2.17 펼치면 프리즘 색이 돌아간다(누른 상태)
 * 2026.09.10  정경하  v2.16 지금 나가 있는 키를 「프리즘 라인」 테두리로
 *                          (ActionFlow gradline). 금색 링은 걷음
 * 2026.09.10  정경하  v2.15 지금 나가 있는 키에 링과 1회 스윕
 *                          (ActionFlow afbtn-glow 를 상태 표시로) ·
 *                          죽은 규칙 둘 걷음(#issueMsg · main.unlinked #view-*)
 * 2026.09.10  정경하  v2.14 경고 태그를 형광 주황(#FF7F00)으로.
 *                          v2.13 의 #F5A623 은 노란기가 남았다
 * 2026.09.10  정경하  v2.13 경고 태그를 노랑에서 주황으로 · 사내 태그에
 *                          배경을 준다(.tag-inhouse) - 안 보이면서 자리만
 *                          차지하던 것
 * 2026.09.10  정경하  v2.12 지문·슬롯 태그를 3색으로(.tag-fp-ok/-warn/-bad),
 *                          (.tag-seal 은 audit.jsp 가 써서 그대로 둠)
 * 2026.09.10  정경하  v2.11 태그 높이 통일 · 회사 태그를 또렷하게 ·
 *                          받아 간 횟수 태그(.tag-dl/.tag-dl0)
 * 2026.09.10  정경하  v2.10 이력 줄의 왼쪽 색이 "지금 나가 있는 키" 를
 *                          뜻하게 바뀜(.is-live/.is-off). .is-re 는 걷음
 * 2026.09.10  정경하  v2.9  이력 태그를 한 자리로(.tag-cust 를 형제들 옆으로)
 *                          + 못 받는 줄 표시(.tag-off) 신설
 * 2026.09.09  정경하  v2.7  되돌릴 수 없는 동작의 버튼(.btn-danger)
 * 2026.09.09  정경하  v2.6  상단바 등급 배지에 CUSTOMER 색(.role-badge.cust)
 * 2026.09.09  정경하  v2.5  안 이어진 것 배지(.chip-nolink)와 경고 안의 링크
 * 2026.09.09  정경하  v2.4  한도 안내의 링크 버튼(.blocked a.btn-main)
 * 2026.09.09  정경하  v2.3  잠깐 보였다 사라지는 안내(.msg.flash) 추가
 * 2026.09.04  정경하  v2.0  밝고 컬러풀한 MZ 톤으로 전면 교체 + 로고 적용
 *                          - 브랜드 골드 #F0C155 / 먹색 #231815 는 로고에서 뽑은 실제 값
 *                          - 로그인 가운데 정렬을 body flex 가 아니라 전용 래퍼의
 *                            grid place-items 로 바꿈. v1 에서 화면이 오른쪽 아래로
 *                            쏠린 적이 있어, 부모 상태에 의존하지 않는 방식으로 고쳤다
 * 2026.09.03  정경하  v1.0  최초 작성
 *
 * Copyright Neosgen Co,Ltd. All right reserved.
 */

/* ============================================================
   토큰
   ============================================================ */

:root {
  /* 브랜드 — 로고에서 뽑은 실제 색 */
  --gold: #F0C155;
  --gold-deep: #D9A22F;
  --gold-soft: #FFF3D6;
  --ink: #231815;

  /* 포인트 — 카드마다 다른 색을 줘서 한눈에 구분되게 */
  --c1: #FF8A5B;   /* 코랄 */
  --c2: #4ECDC4;   /* 민트 */
  --c3: #7C6BF5;   /* 바이올렛 */
  --c4: #4A9DF5;   /* 스카이 */

  --bg: #FBFAF7;
  --panel: #FFFFFF;
  --line: #EDE9E1;
  --text: #2B2724;
  --muted: #8A8378;

  /* 등급 색 넷. ★ 한 자리에서만 정한다 -
     같은 등급이 목록 배지(.chip-*)와 상단바 배지(.role-badge.*) 두 자리에
     나오는데, 종전에는 두 곳이 같은 값을 따로 적고 있었다. 등급 색을 바꾸면
     한쪽만 고치기 쉬운 자리다(v16·v17·v31·v32·v43 과 같은 "두 벌" 이다). */
  --role-super:  linear-gradient(135deg, var(--ink), #55463F);
  --role-admin:  linear-gradient(135deg, #FF6B6B, #EE5A24);
  --role-issuer: linear-gradient(135deg, var(--c3), #5A4BD8);
  --role-cust-bg: #E8F2FF;
  --role-cust-fg: #2F6FBF;

  --warn: #E08B00;
  --warn-bg: #FFF4DC;
  --crit: #E14B4B;
  --crit-bg: #FFECEC;

  --shadow-sm: 0 1px 3px rgba(35, 24, 21, .06);
  --shadow: 0 6px 20px rgba(35, 24, 21, .08);
  --shadow-lg: 0 14px 40px rgba(35, 24, 21, .14);

  --r: 14px;
  --r-lg: 20px;
}

* { box-sizing: border-box; }
html, body { height: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: "Pretendard", "Malgun Gothic", "맑은 고딕",
               "Apple SD Gothic Neo", "Noto Sans KR", sans-serif;
  font-size: 14px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

.hidden { display: none !important; }

/* 움직임을 줄이고 싶은 사용자 배려. OS 접근성 설정이 켜져 있으면 애니메이션을 끈다. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    transition-duration: .001ms !important;
  }
}

/* ============================================================
   로그인
   ============================================================ */

/*
 * 가운데 정렬을 이 래퍼가 책임진다.
 * v1 에서는 body 에 flex 를 걸었는데 화면이 오른쪽 아래로 쏠렸다.
 * 부모 상태에 의존하지 않게 grid place-items 로 바꿨다.
 */
.login-wrap {
  display: grid;
  place-items: center;
  min-height: 100dvh;
  padding: 24px;
  position: relative;
  overflow: hidden;
  background:
    radial-gradient(900px 520px at 8% -10%,  rgba(240, 193, 85, .40), transparent 60%),
    radial-gradient(760px 460px at 100% 0%,  rgba(124, 107, 245, .22), transparent 58%),
    radial-gradient(680px 440px at 50% 115%, rgba(78, 205, 196, .26), transparent 60%),
    var(--bg);
}

/* 배경에 천천히 떠다니는 색 덩어리 */
.blob {
  position: absolute;
  border-radius: 50%;
  filter: blur(60px);
  opacity: .55;
  pointer-events: none;
  animation: float 18s ease-in-out infinite;
}
.blob.b1 { width: 320px; height: 320px; background: var(--gold); top: -80px; left: -60px; }
.blob.b2 { width: 260px; height: 260px; background: var(--c1); bottom: -70px; right: 8%;
           animation-delay: -6s; }
.blob.b3 { width: 220px; height: 220px; background: var(--c2); top: 18%; right: -60px;
           animation-delay: -11s; }

@keyframes float {
  0%, 100% { transform: translate(0, 0) scale(1); }
  33%      { transform: translate(24px, -20px) scale(1.08); }
  66%      { transform: translate(-18px, 16px) scale(.95); }
}

.login-box {
  position: relative;
  z-index: 1;
  width: 380px;
  max-width: 100%;
  padding: 40px 34px 28px;
  border-radius: var(--r-lg);
  /* 글래스모피즘 — 뒤 색 덩어리가 은은하게 비친다 */
  background: rgba(255, 255, 255, .82);
  backdrop-filter: blur(18px) saturate(150%);
  -webkit-backdrop-filter: blur(18px) saturate(150%);
  border: 1px solid rgba(255, 255, 255, .9);
  box-shadow: var(--shadow-lg);
  animation: rise .5s cubic-bezier(.2, .8, .3, 1);
}

@keyframes rise {
  from { opacity: 0; transform: translateY(16px) scale(.98); }
  to   { opacity: 1; transform: none; }
}

.login-head { text-align: center; margin-bottom: 26px; }
.login-head .logo { width: 190px; max-width: 70%; height: auto; }

.brand-sub {
  margin-top: 8px;
  color: var(--muted);
  font-size: 13px;
  letter-spacing: .3px;
}

.login-box label {
  display: block;
  margin: 16px 0 6px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--muted);
  letter-spacing: .2px;
}

.login-box input {
  width: 100%;
  padding: 12px 14px;
  border: 1.5px solid var(--line);
  border-radius: 12px;
  background: rgba(255, 255, 255, .9);
  font-size: 14.5px;
  font-family: inherit;
  transition: border-color .18s, box-shadow .18s, transform .18s;
}

.login-box input:focus {
  outline: none;
  border-color: var(--gold);
  box-shadow: 0 0 0 4px rgba(240, 193, 85, .28);
  transform: translateY(-1px);
}

.login-box button {
  width: 100%;
  margin-top: 24px;
  padding: 13px;
  border: 0;
  border-radius: 12px;
  background: linear-gradient(135deg, var(--gold) 0%, var(--gold-deep) 100%);
  color: var(--ink);
  font-size: 15px;
  font-weight: 700;
  font-family: inherit;
  letter-spacing: .3px;
  cursor: pointer;
  box-shadow: 0 6px 18px rgba(217, 162, 47, .34);
  transition: transform .16s, box-shadow .16s, filter .16s;
}

.login-box button:hover:not(:disabled) {
  transform: translateY(-2px);
  box-shadow: 0 10px 26px rgba(217, 162, 47, .42);
  filter: brightness(1.04);
}
.login-box button:active:not(:disabled) { transform: translateY(0); }
.login-box button:disabled { opacity: .55; cursor: default; box-shadow: none; }

.msg { margin-top: 14px; min-height: 20px; font-size: 13px; text-align: center; font-weight: 500; }
.msg.error { color: var(--crit); animation: shake .35s; }
.msg.warn { color: var(--warn); }

@keyframes shake {
  0%, 100% { transform: translateX(0); }
  25%      { transform: translateX(-5px); }
  75%      { transform: translateX(5px); }
}

.login-foot {
  margin-top: 22px;
  padding-top: 16px;
  border-top: 1px dashed var(--line);
  font-size: 12px;
  color: var(--muted);
  text-align: center;
}

/* ============================================================
   상단바
   ============================================================ */

.topbar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 0 22px;
  height: 62px;
  background: rgba(255, 255, 255, .88);
  backdrop-filter: blur(14px) saturate(150%);
  -webkit-backdrop-filter: blur(14px) saturate(150%);
  border-bottom: 1px solid var(--line);
}

/* 브랜드 골드 라인 — 얇게 한 줄 깔아 로고 색과 이어지게 */
.topbar::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: -1px;
  height: 2px;
  background: linear-gradient(90deg,
    var(--gold), var(--c1), var(--c3), var(--c2), var(--gold));
  background-size: 300% 100%;
  animation: slide 12s linear infinite;
}

@keyframes slide {
  from { background-position: 0% 50%; }
  to   { background-position: 300% 50%; }
}

.topbar-left { display: flex; align-items: center; gap: 12px; min-width: 0; }
.topbar-left .logo { height: 26px; width: auto; }

.brand-tag {
  padding: 3px 10px;
  border-radius: 999px;
  background: var(--gold-soft);
  color: var(--gold-deep);
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: .2px;
  white-space: nowrap;
}

.topbar-right { display: flex; align-items: center; gap: 12px; }

.pcinfo {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 11px;
  border-radius: 999px;
  background: #F3F7FF;
  color: #4A6FA5;
  font-size: 11.5px;
  font-weight: 600;
  white-space: nowrap;
}

/* 등록된 PC 임을 알리는 초록 점. 살아 있다는 신호. */
.pcinfo::before {
  content: '';
  width: 7px; height: 7px;
  border-radius: 50%;
  background: #2FBF71;
  box-shadow: 0 0 0 0 rgba(47, 191, 113, .6);
  animation: pulse 2.2s infinite;
}

@keyframes pulse {
  0%   { box-shadow: 0 0 0 0 rgba(47, 191, 113, .6); }
  70%  { box-shadow: 0 0 0 7px rgba(47, 191, 113, 0); }
  100% { box-shadow: 0 0 0 0 rgba(47, 191, 113, 0); }
}

.user { display: flex; align-items: center; gap: 9px; }

.avatar {
  width: 34px; height: 34px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: linear-gradient(135deg, var(--gold) 0%, var(--c1) 100%);
  color: #fff;
  font-weight: 800;
  font-size: 14px;
  box-shadow: 0 4px 12px rgba(240, 193, 85, .45);
  flex-shrink: 0;
}

.user-name { font-weight: 600; font-size: 13.5px; white-space: nowrap; }
.user-id { color: var(--muted); font-size: 11.5px; }

.role-badge {
  padding: 2px 9px;
  border-radius: 999px;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: .4px;
  color: #fff;
}
.role-badge.super  { background: var(--role-super); }
.role-badge.admin  { background: var(--role-admin); }
.role-badge.issuer { background: var(--role-issuer); }
/* ★ CUSTOMER. 종전에는 ISSUER 와 같은 보라로 나왔다 - 등급이 넷인데 색이
   셋이면 상단바만 보고 어느 자리인지 알 수 없다(v41). */
.role-badge.cust   { background: var(--role-cust-bg); color: var(--role-cust-fg); }

/* 세션 타이머 */
.session-timer {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 7px 14px;
  border: 1.5px solid var(--line);
  border-radius: 999px;
  background: #fff;
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  font-size: 13.5px;
  cursor: pointer;
  user-select: none;
  transition: transform .16s, box-shadow .16s, background .16s, border-color .16s;
}

.session-timer:hover {
  transform: translateY(-1px);
  box-shadow: var(--shadow);
  border-color: var(--gold);
}
.session-timer .clock { transition: transform .3s; }
.session-timer:hover .clock { transform: rotate(-20deg) scale(1.1); }

.session-timer.warn {
  background: var(--warn-bg);
  border-color: #F2D9A0;
  color: var(--warn);
}

.session-timer.crit {
  background: var(--crit-bg);
  border-color: #F6BDBD;
  color: var(--crit);
  animation: blink 1.1s steps(2, start) infinite;
}

.session-timer.extending {
  background: #EAF9F7;
  border-color: #A7E4DE;
  color: #14958B;
}

@keyframes blink { 50% { opacity: .5; } }

.logout {
  padding: 7px 14px;
  border: 1.5px solid var(--line);
  border-radius: 999px;
  background: #fff;
  font-size: 13px;
  font-weight: 600;
  font-family: inherit;
  color: var(--muted);
  cursor: pointer;
  transition: all .16s;
}

.logout:hover {
  background: var(--ink);
  border-color: var(--ink);
  color: #fff;
  transform: translateY(-1px);
}

/* ============================================================
   탭
   ============================================================ */

.tabs {
  display: flex;
  gap: 8px;
  padding: 14px 22px 0;
  flex-wrap: wrap;
}

/* ★ 탭은 <a> 다. 서버가 그리는 화면이라 주소가 곧 상태이기 때문이다.
   버튼이던 시절에는 없던 밑줄이 붙으므로 여기서 끈다. */
.tab {
  text-decoration: none;
  padding: 9px 18px;
  border: 1.5px solid transparent;
  border-radius: 999px;
  background: #fff;
  font-size: 13.5px;
  font-weight: 600;
  font-family: inherit;
  color: var(--muted);
  cursor: pointer;
  box-shadow: var(--shadow-sm);
  transition: all .16s;
}

.tab:hover { color: var(--text); transform: translateY(-1px); }

.tab.active {
  background: linear-gradient(135deg, var(--gold) 0%, var(--gold-deep) 100%);
  color: var(--ink);
  box-shadow: 0 6px 16px rgba(217, 162, 47, .34);
}

/* ============================================================
   본문
   ============================================================ */

main { padding: 24px 22px 48px; max-width: 1180px; }

h2 {
  margin: 0 0 6px;
  font-size: 21px;
  font-weight: 800;
  letter-spacing: -.3px;
}

.hint { color: var(--muted); margin: 0 0 22px; }

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 16px;
}

.card {
  position: relative;
  overflow: hidden;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: 22px 18px 18px;
  text-align: center;
  box-shadow: var(--shadow-sm);
  transition: transform .2s cubic-bezier(.2, .8, .3, 1), box-shadow .2s;
}

/* 카드마다 위쪽에 다른 색 띠. 한눈에 구분된다. */
.card::before {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 4px;
  background: var(--accent, var(--gold));
}

.card:nth-child(1) { --accent: linear-gradient(90deg, var(--gold), var(--gold-deep)); }
.card:nth-child(2) { --accent: linear-gradient(90deg, var(--c2), #2BA89F); }
.card:nth-child(3) { --accent: linear-gradient(90deg, var(--c1), #F26A3D); }
.card:nth-child(4) { --accent: linear-gradient(90deg, var(--c3), #5A4BD8); }

.card:hover { transform: translateY(-5px); box-shadow: var(--shadow); }

.card-num {
  font-size: 32px;
  font-weight: 800;
  letter-spacing: -1px;
  line-height: 1.2;
  background: var(--accent, var(--gold));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.card-label {
  color: var(--muted);
  font-size: 12.5px;
  font-weight: 600;
  margin-top: 4px;
}

/* ============================================================
   모달
   ============================================================ */

.modal-back {
  position: fixed;
  inset: 0;
  background: rgba(35, 24, 21, .45);
  backdrop-filter: blur(4px);
  display: grid;
  place-items: center;
  z-index: 100;
  animation: fade .2s;
}

@keyframes fade { from { opacity: 0; } to { opacity: 1; } }

.modal {
  width: 380px;
  max-width: calc(100% - 32px);
  background: #fff;
  border-radius: var(--r-lg);
  padding: 30px 28px 22px;
  text-align: center;
  box-shadow: var(--shadow-lg);
  animation: rise .28s cubic-bezier(.2, .8, .3, 1);
}

.modal h3 {
  margin: 0 0 14px;
  color: var(--crit);
  font-size: 18px;
  font-weight: 800;
}

.modal p { margin: 0 0 10px; color: var(--muted); }
.modal-time { font-size: 15px; color: var(--text) !important; }
.modal-time strong {
  font-variant-numeric: tabular-nums;
  color: var(--crit);
  font-size: 20px;
  font-weight: 800;
}

.modal-btns { display: flex; gap: 10px; margin-top: 20px; }

.modal-btns button {
  flex: 1;
  padding: 11px;
  border-radius: 12px;
  font-size: 14px;
  font-weight: 700;
  font-family: inherit;
  cursor: pointer;
  transition: all .16s;
}

.btn-ghost { background: #fff; border: 1.5px solid var(--line); color: var(--muted); }
.btn-ghost:hover { background: #F5F3EE; color: var(--text); }

.btn-main {
  border: 0;
  background: linear-gradient(135deg, var(--gold), var(--gold-deep));
  color: var(--ink);
  box-shadow: 0 6px 16px rgba(217, 162, 47, .34);
}
.btn-main:hover { transform: translateY(-2px); filter: brightness(1.05); }

/* ★ 되돌릴 수 없는 동작. 골드(btn-main)를 쓰면 "확인" 과 같은 색이 되어
   손이 먼저 나간다. 목록의 .btn-sm-danger 와 같은 붉은 계열로 둔다 -
   같은 뜻이면 같은 색이어야 자리가 달라도 뜻이 이어진다. */
.btn-danger {
  border: 0;
  background: linear-gradient(135deg, #E14B4B, #C43333);
  color: #fff;
  box-shadow: 0 6px 16px rgba(225, 75, 75, .32);
}
.btn-danger:hover { transform: translateY(-2px); filter: brightness(1.05); }

/* ============================================================
   좁은 화면
   ============================================================ */

@media (max-width: 720px) {
  .topbar { height: auto; padding: 12px 16px; flex-wrap: wrap; }
  .pcinfo, .user-id { display: none; }
  .tabs { padding: 12px 16px 0; }
  main { padding: 20px 16px 40px; }
}

/* ============================================================
   제품 선택 (상단바)

   화면 전체에 걸리는 값이라 항상 보여야 한다. 제품을 잘못 고르면 봉인이
   다른 키로 만들어져, 발급은 성공한 것처럼 보이는데 고객 서버에서만 실패한다.
   ============================================================ */

.prodsel { position: relative; }

.prod-btn {
  display: flex; align-items: center; gap: 7px;
  padding: 7px 12px;
  border: 1.5px solid transparent;
  border-radius: 999px;
  background: linear-gradient(135deg, var(--gold-soft), #FFFBF0);
  color: var(--ink);
  font: inherit; font-size: 12.5px; font-weight: 700;
  cursor: pointer;
  box-shadow: var(--shadow-sm);
  transition: transform .18s, box-shadow .18s, border-color .18s;
}
.prod-btn:hover { transform: translateY(-1px); box-shadow: var(--shadow); }
.prod-btn.single { cursor: default; }
.prod-btn.single:hover { transform: none; box-shadow: var(--shadow-sm); }
.prod-btn.single .prod-caret { display: none; }

.prod-dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: linear-gradient(135deg, var(--gold), var(--gold-deep));
  box-shadow: 0 0 0 3px rgba(240, 193, 85, .25);
  flex: none;
}
/* 이력의 화살표와 같은 이유로 글자가 아니라 테두리로 그린다. */
.prod-caret {
  width: 0; height: 0;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 5px solid var(--muted);
}

.prod-menu {
  position: absolute; top: calc(100% + 8px); right: 0; z-index: 40;
  min-width: 240px; padding: 6px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r);
  box-shadow: var(--shadow-lg);
}
/* 제품·회사 메뉴 항목도 <a> 다. 고르면 그 주소로 간다. */
.prod-item {
  text-decoration: none; color: inherit;
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  width: 100%; padding: 10px 12px;
  border: 0; border-radius: 10px; background: transparent;
  font: inherit; text-align: left; cursor: pointer;
  transition: background .15s;
}
.prod-item:hover { background: var(--gold-soft); }
.prod-item-name { font-weight: 600; font-size: 13.5px; }
.prod-item-code {
  font-size: 11px; font-weight: 700; letter-spacing: .04em;
  color: var(--muted);
  padding: 2px 7px; border-radius: 999px; background: #F5F3EE;
}

/* ============================================================
   발급 화면
   ============================================================ */

.issue-grid {
  display: grid;
  grid-template-columns: minmax(360px, 460px) minmax(0, 1fr);
  gap: 20px;
  align-items: start;
}
@media (max-width: 960px) { .issue-grid { grid-template-columns: 1fr; } }

.panel {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: 22px;
  box-shadow: var(--shadow-sm);
}

/* 파일 / 코드 전환 */
.seg {
  display: flex; gap: 6px; padding: 5px;
  background: #F5F3EE; border-radius: 999px; margin-bottom: 18px;
}
.seg-btn {
  flex: 1; padding: 9px 12px;
  border: 0; border-radius: 999px; background: transparent;
  font: inherit; font-size: 13px; font-weight: 600; color: var(--muted);
  cursor: pointer; transition: all .18s;
}
.seg-btn:hover { color: var(--text); }
.seg-btn.active {
  background: linear-gradient(135deg, var(--gold), var(--gold-deep));
  color: var(--ink); box-shadow: var(--shadow-sm);
}

/* 끌어다 놓기 */
.drop {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  padding: 30px 18px;
  border: 2px dashed var(--line); border-radius: var(--r);
  background: linear-gradient(160deg, #FEFDFA, #FBF8F1);
  cursor: pointer; text-align: center;
  transition: border-color .18s, background .18s, transform .18s;
}
.drop:hover { border-color: var(--gold); transform: translateY(-2px); }
.drop.over {
  border-color: var(--c2);
  background: linear-gradient(160deg, #EFFBFA, #E4F7F5);
  transform: scale(1.01);
}
.drop-ico { font-size: 30px; line-height: 1; }
.drop-main { font-size: 13.5px; font-weight: 600; }
.drop-sub { font-size: 11.5px; color: var(--muted); }

.picked {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  margin-top: 12px; padding: 11px 14px;
  border-radius: var(--r);
  background: linear-gradient(135deg, #EFFBFA, #E4F7F5);
  border: 1px solid #BFEDE8;
}
.picked-name {
  font-size: 12.5px; font-weight: 600; word-break: break-all;
}
.picked-x {
  flex: none; width: 24px; height: 24px;
  border: 0; border-radius: 50%; background: rgba(35, 24, 21, .08);
  color: var(--text); font-size: 16px; line-height: 1; cursor: pointer;
  transition: background .15s;
}
.picked-x:hover { background: var(--crit); color: #fff; }

/* 입력칸 */
.field { margin-top: 18px; }
.field label {
  display: block; margin-bottom: 7px;
  font-size: 12.5px; font-weight: 700; letter-spacing: .01em;
}
.field .dim { color: var(--muted); font-weight: 500; }
.field input, #paneCode textarea {
  width: 100%; padding: 12px 14px;
  border: 1.5px solid var(--line); border-radius: var(--r);
  background: #FEFDFA; color: var(--text);
  font: inherit; font-size: 13.5px;
  transition: border-color .18s, box-shadow .18s;
}
#paneCode textarea {
  font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
  font-size: 12px; line-height: 1.55; resize: vertical; word-break: break-all;
}
.field input:focus, #paneCode textarea:focus {
  outline: 0; border-color: var(--gold);
  box-shadow: 0 0 0 4px rgba(240, 193, 85, .18);
}
.field-help { margin: 7px 0 0; font-size: 11.5px; color: var(--muted); }

.btn-issue {
  width: 100%; margin-top: 22px; padding: 14px;
  border: 0; border-radius: var(--r);
  background: linear-gradient(135deg, var(--gold), var(--gold-deep));
  color: var(--ink);
  font: inherit; font-size: 15px; font-weight: 800; letter-spacing: .01em;
  cursor: pointer;
  box-shadow: 0 6px 18px rgba(217, 162, 47, .30);
  transition: transform .18s, box-shadow .18s, filter .18s;
}
.btn-issue:hover:not(:disabled) {
  transform: translateY(-2px); filter: brightness(1.04);
  box-shadow: 0 10px 26px rgba(217, 162, 47, .38);
}
.btn-issue:active:not(:disabled) { transform: translateY(0); }
.btn-issue:disabled { opacity: .55; cursor: default; box-shadow: none; }


/* ============================================================
   발급 결과
   ============================================================ */

.result { position: relative; overflow: hidden; }
.result::before {
  content: ''; position: absolute; inset: 0 0 auto 0; height: 5px;
  background: linear-gradient(90deg, var(--c2), var(--gold), var(--c1), var(--c3));
}

.res-head { display: flex; align-items: center; gap: 11px; margin: 6px 0 18px; }
.res-head h3 { margin: 0; font-size: 18px; font-weight: 800; }
.res-ico {
  display: grid; place-items: center;
  width: 30px; height: 30px; border-radius: 50%;
  background: linear-gradient(135deg, var(--c2), #2BA89F);
  color: #fff; font-size: 15px; font-weight: 900;
  box-shadow: 0 4px 12px rgba(78, 205, 196, .40);
}

/* 담당자가 반드시 봐야 하는 것들. 색으로 갈라 둔다. */
.flag {
  display: flex; flex-direction: column; gap: 4px;
  margin-bottom: 12px; padding: 13px 16px;
  border-radius: var(--r); border-left: 4px solid;
  font-size: 12.5px; line-height: 1.6;
}
.flag strong { font-size: 12px; font-weight: 800; letter-spacing: .03em; }
.flag-sub { color: var(--muted); font-size: 11.5px; }

.flag-re   { background: #FFF0E9; border-color: var(--c1); }
.flag-re strong { color: #D2541F; }
.flag-seal { background: var(--warn-bg); border-color: var(--warn); }
.flag-seal strong { color: var(--warn); }
.flag-warn { background: var(--crit-bg); border-color: var(--crit); }
.flag-warn strong { color: var(--crit); }

.kv {
  display: grid; grid-template-columns: 92px minmax(0, 1fr);
  gap: 9px 14px; margin: 18px 0 4px;
  font-size: 12.5px;
}
.kv-k { color: var(--muted); font-weight: 600; }
.kv-v { word-break: break-all; }
.kv-v.mono {
  font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
  font-size: 11.5px; color: #55504A;
}

.res-btns { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 22px; }
.res-btns a {
  flex: 1 1 190px; padding: 12px 16px;
  border-radius: var(--r); text-align: center; text-decoration: none;
  font-size: 13.5px; font-weight: 700;
  transition: transform .18s, filter .18s;
}
.res-btns a:hover { transform: translateY(-2px); filter: brightness(1.04); }
.res-btns .btn-main {
  background: linear-gradient(135deg, var(--c3), #5A4BD8); color: #fff;
  box-shadow: 0 6px 18px rgba(124, 107, 245, .32);
}
.res-btns .btn-ghost {
  background: #fff; border: 1.5px solid var(--line); color: var(--muted);
}
.res-btns .btn-ghost:hover { background: #F5F3EE; color: var(--text); }

.res-foot {
  margin: 18px 0 0; padding-top: 16px;
  border-top: 1px dashed var(--line);
  font-size: 11.5px; line-height: 1.7; color: var(--muted);
}

/* ============================================================
   발급 이력

   목록에는 한눈에 읽히는 것만 둔다. 지문 상세는 100자가 넘어서 그대로 두면
   표가 무너진다. 한 줄을 눌렀을 때 아래로 편다.
   ============================================================ */

.filters {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  margin-bottom: 18px;
}

.search { position: relative; flex: 1 1 320px; max-width: 460px; }
.search-ico {
  position: absolute; left: 14px; top: 50%; transform: translateY(-50%);
  font-size: 13px; opacity: .5; pointer-events: none;
}
.search input {
  width: 100%; padding: 11px 38px 11px 40px;
  border: 1.5px solid var(--line); border-radius: 999px;
  background: var(--panel); color: var(--text);
  font: inherit; font-size: 13.5px;
  transition: border-color .18s, box-shadow .18s;
}
.search input:focus {
  outline: 0; border-color: var(--gold);
  box-shadow: 0 0 0 4px rgba(240, 193, 85, .18);
}
/* 검색어 지우기. 폼을 GET 으로 내면서 <a> 가 됐다. */
.search-x {
  text-decoration: none;
  display: inline-flex; align-items: center; justify-content: center;
  position: absolute; right: 12px; top: 50%; transform: translateY(-50%);
  width: 22px; height: 22px;
  border: 0; border-radius: 50%; background: rgba(35, 24, 21, .08);
  color: var(--text); font-size: 15px; line-height: 1; cursor: pointer;
}
.search-x:hover { background: var(--crit); color: #fff; }

.pick {
  padding: 11px 14px;
  border: 1.5px solid var(--line); border-radius: 999px;
  background: var(--panel); color: var(--text);
  font: inherit; font-size: 13px; cursor: pointer;
}
.pick:focus { outline: 0; border-color: var(--gold); }

.count { margin-left: auto; font-size: 12.5px; color: var(--muted); font-weight: 600; }

/* --- 한 줄 --- */

.hist { display: flex; flex-direction: column; gap: 9px; }

.hist-empty {
  padding: 44px 20px; text-align: center;
  color: var(--muted); font-size: 13.5px;
  background: var(--panel); border: 1px dashed var(--line); border-radius: var(--r-lg);
}

.hrow {
  background: var(--panel);
  border: 1px solid var(--line); border-radius: var(--r);
  overflow: hidden;
  /* ★ v2.15 - is-live 의 빛이 이 안에서만 훑고 지나가게 하는 바탕. */
  position: relative;
  transition: box-shadow .18s, border-color .18s, transform .18s;
}
.hrow:hover { box-shadow: var(--shadow); transform: translateY(-1px); }
.hrow.open { border-color: var(--gold); box-shadow: var(--shadow); }

/* 재발급 건은 왼쪽에 코랄 띠를 둔다. 목록에서 바로 눈에 띄어야 한다. */
/* ★ v2.10 — 왼쪽 줄의 뜻이 바뀌었다.
   종전 .hrow.is-re 는 "재발급" 이었는데, 그것은 옆의 [재발급] 태그가 이미
   말하고 있었다. 같은 사실이 두 벌이면서 정작 "지금 나가 있는 키인가" 에는
   표시가 없었다. 이제 금색 줄이 곧 받을 수 있는 줄이다.
   ★ .is-re 는 붙이는 쪽이 없어졌으므로 함께 걷었다 - 남겨 두면 다음 사람이
     다시 쓴다(v33·v40·v51 에서 같은 이유로 걷었다). */
/* ★ v2.19 - 지금 나가 있는 키.
   ActionFlow FrontDesigner 의 버튼 프리셋 「프리즘 라인」(prismline)을
   원본 CSS 그대로 옮겼다. 차장님이 app.js 를 주셔서 꺼냈다.

   ★ v2.18 은 엉뚱한 것을 봤다.
     그때 꺼낸 것은 '그라데이션 테두리'(gradline)였고, prismline 은 cat 필드가
     붙은 다른 목록에 있어서 제 검색이 놓쳤다. 둘은 만드는 법도 색도 다르다.

       gradline    mask-composite 로 가운데를 도려낸다 · 2색 · 안 움직인다
       prismline   겹 세 장 · <b>5색</b> · background-position 이 흐른다

   ★ 만드는 법 — 겹 세 장이다. 도려내지 않는다.
       ::before  z-index:-2   줄 전체를 5색 그라디언트로 채운다
       ::after   z-index:-1   그보다 3px 안쪽을 바탕색으로 덮는다
       그 사이에 남는 3px 이 테두리로 보인다
     음수 z-index 는 줄의 <b>바탕색 위·글자 아래</b>에 깔린다.
     isolation:isolate 가 없으면 그 -2 가 바깥으로 새어 나가 가려진다.

   ★ 색이 흐르는 원리 — background-size:250% 로 그라디언트를 화면보다 넓게
     깔아 두고 background-position 만 옮긴다. 다시 그리는 것이 아니라
     <b>깔아 둔 것을 미는 것</b>이라 가볍다.

   ★ 원본은 inset:-2px 로 바깥에 두르는데 여기서는 inset:0 이다.
     .hrow 에 overflow:hidden 이 걸려 있어 바깥으로 나가면 잘린다.
     두께는 ::after 의 inset 으로 맞춘다. */
.hrow.is-live {
  position: relative;
  isolation: isolate;
  border-color: transparent;
}
.hrow.is-live::before {
  content: ''; position: absolute; z-index: -2; inset: 0;
  border-radius: var(--r); pointer-events: none;
  background: linear-gradient(115deg, #22D3EE, #6366F1, #EC4899, #F59E0B, #22D3EE);
  background-size: 250% 100%;
  background-position: 0% 50%;
  transition: background-position .55s ease;
}
.hrow.is-live::after {
  content: ''; position: absolute; z-index: -1; inset: 3px;
  border-radius: calc(var(--r) - 3px); pointer-events: none;
  background: var(--panel);
}

/* ★ 마우스를 올리면 색이 흐른다. 원본의 &:hover::before 그대로다. */
.hrow.is-live:hover::before { background-position: 100% 50%; }

/* ★ 펼치면(= 눌렀을 때) 커지면서 색이 흐른 자리에서 멈춘다.
     원본의 :active 는 scale(.96) 으로 <b>작아진다</b> - 손가락으로 누르는
     버튼이라 그렇다. 여기서는 차장님 말씀대로 <b>커지는</b> 쪽으로 뒤집었다.
     펼친 줄은 "지금 보고 있는 것" 이라 뒤로 물러날 이유가 없다.
   ★ 색은 마우스를 올렸을 때와 같은 자리(100%)에서 멈춘다.
   ★ 1.2% 만 키운다. 줄이 900px 쯤이라 이것만으로 11px 이 커진다. */
.hrow.is-live.open {
  border-color: transparent;
  transform: scale(1.012);
}
.hrow.is-live.open::before { background-position: 100% 50%; }

/* ★ 움직임을 줄여 달라는 설정을 켠 사람에게는 색을 안 흘린다.
     테두리와 색은 그대로 남으므로 표시가 사라지지는 않는다 -
     효과가 없어도 뜻은 전해져야 한다. */
@media (prefers-reduced-motion: reduce) {
  .hrow.is-live::before { transition: none; }
  .hrow.is-live.open { transform: none; }
}
/* 못 받는 줄. 흐리게 두어 눈이 받을 수 있는 줄로 먼저 가게 한다.
   ★ 감추지는 않는다. 왜 못 받는지가 그 줄에 적혀 있어야 한다. */
.hrow.is-off  { border-left: 3px solid var(--line); opacity: .62; }
.hrow.is-off:hover { opacity: 1; }
/* 무엇으로 갈렸는지. 지문 옆에 한 마디로 붙는다 */
.h-repl { color: var(--muted); font-size: 11.5px; }

.hrow-head {
  display: flex; align-items: center; gap: 14px;
  width: 100%; padding: 14px 18px;
  border: 0; background: transparent;
  font: inherit; text-align: left; cursor: pointer;
}

.h-serial {
  flex: none; min-width: 44px;
  font-size: 13px; font-weight: 800; color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.h-mid { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.h-subject {
  font-size: 14px; font-weight: 700;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.h-sub {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  font-size: 11.5px; color: var(--muted);
}
.h-fp {
  font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
  letter-spacing: .02em;
}
.h-dot { opacity: .45; }

.h-tags { display: flex; flex-wrap: wrap; gap: 5px; flex: none; }
.tag {
  padding: 3px 9px; border-radius: 999px;
  font-size: 10.5px; font-weight: 800; letter-spacing: .03em; color: #fff;
  /* ★ v2.11 - 태그 높이를 하나로 맞춘다. 종전에는 글자에 따라 한두 픽셀씩
     달라져 한 줄에 여럿이 붙으면 들쭉날쭉해 보였다. */
  line-height: 16px; white-space: nowrap;
}
.tag-prod { background: linear-gradient(135deg, var(--c4), #2E7FD4); }
.tag-re   { background: linear-gradient(135deg, var(--c1), #EE5A24); }
/* ★ .tag-seal 은 이력에서는 안 쓰게 됐지만 <b>걷으면 안 된다.</b>
     audit.jsp 의 [거부] 배지가 이것을 쓴다 - 지우면 로그 화면의 거부 표시
     색이 통째로 사라진다. v51 의 .chip-super 와 같은 자리다:
     "이 화면에서 안 쓴다" 는 "안 쓰인다" 가 아니다. */
.tag-seal { background: linear-gradient(135deg, var(--gold), var(--gold-deep)); color: var(--ink); }

/* ★ v2.12 - 지문·슬롯 태그. 늘 보이고 색으로 갈린다.
   ★★ 이 셋은 화면이 이름을 EL 로 조립한다 - history.jsp 의
      class="tag tag-fp-${r.sealLevel}". 그래서 소스를 글자로 훑으면
      "tag-fp-ok" 라는 낱말이 어디에도 안 나와 죽은 것으로 보인다.
      <b>지우면 지문 태그 색이 전부 사라진다.</b> v51 의 .chip-super 와 같다:
      글자로 훑는 검사는 "쓰인다" 는 말만 믿을 수 있다.
     ok    지문 3 이상 - 하나가 바뀌어도 다른 짝으로 풀린다
     warn  지문 2      - 부품 하나만 갈아도 재발급이 필요하다 (주황)
     bad   못 읽음     - 슬롯이 비었거나 값이 이상하다. 눈으로 봐야 한다 */
.tag-fp-ok   { background: linear-gradient(135deg, #2FBF71, #1E9E58); }
/* ★ v2.14 - 형광 주황(#FF7F00)으로. v2.13 의 #F5A623 은 R245·G166 이라 두 값이
   붙어 있어 노란기가 남았다 - 앰버·황토 쪽이지 주황이 아니다.
   #FF7F00 은 R255·G127 로 초록이 절반이라 순수 주황으로 읽힌다.
   ★ --gold 계열을 안 쓰는 이유: 그 색은 이 화면의 강조색(선택된 탭·주요 버튼·
     #일련번호)이라 경고를 같은 색으로 두면 "고른 것" 처럼 읽힌다.
   ★ 글자는 <b>먹색</b>이다. 흰 글씨는 #FF7F00 위에서 2.53:1 밖에 안 나온다 -
     10.5px 굵은 글씨 기준(3.0)에도 못 미친다. 먹색이면 4.96:1 로 읽힌다.
     금색 태그(.tag-seal)가 이미 같은 방식이다.
   ★ 덤으로 재발급(.tag-re)과 갈리는 데도 도움이 된다. 배경끼리는 RGB 거리가
     54 로 가까운데, 한쪽은 흰 글씨 한쪽은 먹색 글씨라 눈이 먼저 구분한다.
     재발급 색은 옛 판에서 온 것이라 건드리지 않는다. */
.tag-fp-warn { background: linear-gradient(135deg, #FF9124, #FF7F00);
               color: var(--ink); }
.tag-fp-bad  { background: linear-gradient(135deg, #FF6B6B, #D93025); }
/* 회사 태그 - 제품(파랑)과 갈라야 한 줄에서 구분된다.
   ★ v2.9 에서 여기로 옮겼다. 480줄 아래에 혼자 있었고, 태그 색을 바꿀 때
     한쪽만 고치기 쉬운 모양이었다(v51 에서 .role-badge 넷을 모은 것과 같다). */
/* ★ v2.11 - 또렷하게 했다. 제품(진한 파랑) 옆에서 묻혔는데, 회사는 제품만큼
   자주 봐야 하는 값이다. 색은 그대로 파랑 계열이라 둘이 한 갈래로 읽힌다. */
.tag-cust { background: #D6E9FF; color: #1F5FA8; }
/* 못 받는 줄 - 물러난 서버, 그리고 더 최근 발급이 있는 옛 건.
   ★ 색으로 튀지 않는다. 경고가 아니라 "이건 아니다" 라는 표시다. */
.tag-off  { background: #EDE9E4; color: var(--muted); }
/* 받아 간 횟수. ★ 0 도 그린다 - "아직 안 받아 갔다" 가 확인해야 하는 상태다.
   0 은 회색으로 두어 셌다는 것만 알리고 눈을 안 끈다. */
/* 사내 발급분. ★ 배경을 반드시 준다 - .tag 는 글자색만 흰색이라 색갈래를
   안 주면 흰 글씨가 흰 바탕에 얹혀 안 보이면서 자리만 차지한다.
   회사(파랑)와 갈리도록 중립 회색으로 둔다. */
.tag-inhouse { background: #E9E5E0; color: #6B6259; }
.tag-dl   { background: #E6F6EE; color: #1B7A4B; }
.tag-dl0  { background: transparent; color: var(--muted);
            border: 1px solid var(--line); }

/* 글자(▾)로 두면 폰트에 따라 '-' 로 나온다. 테두리로 직접 그린다. */
.h-caret {
  flex: none;
  width: 0; height: 0;
  border-left: 5px solid transparent;
  border-right: 5px solid transparent;
  border-top: 6px solid var(--muted);
  transition: transform .22s, border-top-color .18s;
}
.hrow:hover .h-caret { border-top-color: var(--text); }
.hrow.open .h-caret { border-top-color: var(--gold-deep); }
.hrow.open .h-caret { transform: rotate(180deg); }

/* --- 펼친 안쪽 --- */

.hrow-body {
  padding: 4px 18px 18px 76px;
  border-top: 1px dashed var(--line);
  margin-top: -1px;
}
@media (max-width: 640px) { .hrow-body { padding-left: 18px; } }
.hrow-body .kv { margin-top: 14px; }

.hrow-btns { display: flex; flex-wrap: wrap; align-items: center; gap: 9px; margin-top: 16px; }
.btn-sm {
  padding: 8px 15px; border-radius: 999px; text-decoration: none;
  font-size: 12.5px; font-weight: 700;
  background: #fff; border: 1.5px solid var(--line); color: var(--muted);
  transition: transform .18s, filter .18s, background .18s;
}
.btn-sm:hover { transform: translateY(-1px); background: #F5F3EE; color: var(--text); }
.btn-sm-main {
  background: linear-gradient(135deg, var(--c3), #5A4BD8);
  border-color: transparent; color: #fff;
  box-shadow: 0 4px 12px rgba(124, 107, 245, .28);
}
.btn-sm-main:hover { background: linear-gradient(135deg, var(--c3), #5A4BD8); color: #fff; filter: brightness(1.05); }
.btn-none { font-size: 11.5px; color: var(--muted); }

/* --- 페이지 버튼 ---

   토닥 .a-pager 와 같은 모양. «  ‹  1  …  6 [7] 8  …  25  ›  »
   ------------------------------------------------------------ */

.pager {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: 6px; margin-top: 22px;
}
/* 버튼과 흐린 자리(span)가 같은 크기여야 한다. 크기가 다르면 경계 페이지에서
   줄이 움찔거린다. */
.pager .pg {
  text-decoration: none;
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 36px; height: 36px; padding: 0 11px; box-sizing: border-box;
  border: 1.5px solid var(--line); border-radius: 10px;
  background: var(--panel); color: var(--muted);
  font: inherit; font-size: 13px; font-weight: 700; line-height: 1;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  transition: background .15s, border-color .15s, color .15s, transform .15s;
}
/* ★ 종전에는 button.pg 였다. 페이저가 <a> 로 바뀌면서 마우스를 올려도
   아무 반응이 없었다. 흐린 자리(span.pg-off)에는 여전히 안 먹어야 한다. */
a.pg:hover,
button.pg:hover {
  border-color: var(--gold); color: var(--text);
  background: var(--gold-soft);
  transform: translateY(-2px);
}

/* 지금 페이지 */
.pager .pg-on {
  background: linear-gradient(135deg, var(--gold), var(--gold-deep));
  border-color: transparent; color: var(--ink);
  cursor: default;
  box-shadow: 0 4px 12px rgba(217, 162, 47, .30);
}

/* « ‹ › » */
.pager .pg-edge { font-size: 15px; font-weight: 600; }

/* 갈 곳이 없는 화살표. 숨기지 않고 자리를 지킨다 — 사라지면 옆 버튼이 밀린다. */
.pager .pg-off {
  opacity: .38; cursor: default;
  background: #FAF8F3;
}

/* 생략(…) — 테두리 없이 자리만 */
.pager .pg-gap {
  border: none; background: transparent; cursor: default;
  min-width: 20px; padding: 0 2px; color: #BDB5A6;
}

@media (max-width: 640px) {
  .pager .pg { min-width: 32px; height: 32px; padding: 0 8px; font-size: 12px; }
}

/* ============================================================
   대시보드 — 제품별
   ============================================================ */

.byprod { margin-top: 28px; }
.byprod-title { margin: 0 0 12px; font-size: 14px; font-weight: 800; }
.byprod-list { display: flex; flex-direction: column; gap: 8px; max-width: 520px; }
.byprod-item {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 16px;
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--r);
  transition: border-color .18s, box-shadow .18s;
}
/* 지금 보고 있는 제품은 골드로 표시한다. */
.byprod-item.on { border-color: var(--gold); box-shadow: var(--shadow-sm); }
.byprod-code {
  flex: none; padding: 3px 9px; border-radius: 999px;
  background: linear-gradient(135deg, var(--c4), #2E7FD4); color: #fff;
  font-size: 10.5px; font-weight: 800; letter-spacing: .03em;
}
.byprod-name { flex: 1 1 auto; font-size: 13.5px; font-weight: 600; }
.byprod-count {
  flex: none; font-size: 13px; font-weight: 800;
  font-variant-numeric: tabular-nums; color: var(--muted);
}

/* ============================================================
   관리 화면 (계정 · 발급 PC · 제품)
   ============================================================ */

.seg-admin { max-width: 460px; }
/* SUPER_ADMIN 은 설정 갈래가 하나 더 있다. 폭을 그대로 두면 다섯이 눌린다. */
.seg-admin.seg-admin-5 { max-width: 570px; }

/* ---- 설정 화면 : 왼쪽 세로 메뉴 + 칸 ---- */
.set-layout {
  display: grid; grid-template-columns: 200px minmax(0, 1fr);
  gap: 18px; align-items: start; margin-top: 18px;
}
.set-nav {
  position: sticky; top: 76px;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 16px; padding: 10px;
}
.set-nav-cap { font-size: 11px; font-weight: 800; letter-spacing: .06em;
               color: var(--muted); padding: 6px 10px 8px; }
.set-nav-item {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 12px; border-radius: 11px;
  font-size: 13.5px; font-weight: 600; color: var(--text); text-decoration: none;
  transition: background .15s, transform .15s;
}
.set-nav-item:hover { background: var(--gold-soft); transform: translateX(2px); }
.set-nav-item.on {
  background: linear-gradient(135deg, var(--gold), var(--gold-deep));
  color: var(--ink); font-weight: 800;
  box-shadow: 0 4px 14px rgba(217, 162, 47, .25);
}
.set-ico { width: 20px; text-align: center; font-size: 15px; opacity: .8; }
.set-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--c1); margin-left: auto; }

.set-main { display: flex; flex-direction: column; gap: 16px; min-width: 0; }

/* 한눈에 보기 - 카드(벤토). 누르면 그 칸으로 간다. */
.set-bento { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 12px; }
.set-tile {
  display: flex; flex-direction: column; gap: 6px; min-height: 110px;
  background: var(--panel); border: 1px solid var(--line); border-radius: 16px;
  padding: 16px 18px; color: var(--text); text-decoration: none;
  transition: transform .18s, border-color .18s, box-shadow .18s;
}
.set-tile:hover { transform: translateY(-2px); border-color: var(--gold);
                  box-shadow: 0 8px 22px rgba(35, 24, 21, .06); }
.set-tile-cap { font-size: 11.5px; font-weight: 700; color: var(--muted); }
.set-tile b { font-size: 20px; font-weight: 800; color: var(--ink); }
.set-tile b.set-ok { color: #1E9E58; }
.set-tile b.set-warn, .set-tile .set-warn { color: var(--c1); }
.set-tile-sub { font-size: 12px; color: var(--muted); word-break: break-all; }
.set-tile-line { display: flex; justify-content: space-between; font-size: 13px; }
.set-tile-line b { font-size: 14px; }
.set-tile-logo { max-width: 100%; max-height: 48px; object-fit: contain; align-self: flex-start; margin-top: 4px; }

/* 칸 하나 */
.set-panel { background: var(--panel); border: 1px solid var(--line); border-radius: 16px; padding: 18px 22px; }
.set-panel-head { display: flex; align-items: center; justify-content: space-between;
                  gap: 12px; flex-wrap: wrap; }
.set-panel-head h3 { margin: 0; font-size: 16px; font-weight: 800; }
.set-sub { margin: 8px 0 4px; font-size: 12.5px; line-height: 1.65; color: var(--muted); }
.set-form { max-width: 520px; width: auto; }
.set-form .modal-btns { margin-top: 16px; }
.set-pw { display: flex; gap: 8px; align-items: center; }
.set-pw input { flex: 1 1 auto; }
.set-pw input:disabled { background: #F2EFEA; color: var(--muted); cursor: not-allowed; }
.set-pw .btn-sm { flex: 0 0 auto; white-space: nowrap; }
.set-row { display: flex; gap: 14px; }
.set-row > div { flex: 1 1 0; }

/* 시험 발송 결과 - 입력 칸 바로 위, 폼 폭 그대로. 가운데 정렬 문구(.msg)는 이 자리에 안 맞는다. */
.set-result {
  display: flex; align-items: flex-start; gap: 8px;
  margin: 12px 0 2px; padding: 11px 14px; border-radius: 12px;
  font-size: 13px; line-height: 1.55; font-weight: 600; word-break: break-all;
  border: 1px solid transparent;
}
.set-result::before { flex: 0 0 auto; font-weight: 800; }
.set-result.ok   { background: #E6F6F4; border-color: #BCE6E1; color: #177A70; }
.set-result.ok::before   { content: '✓'; }
.set-result.err  { background: #FFF1EC; border-color: #FFD2C2; color: #C2410C; }
.set-result.err::before  { content: '!'; }
.set-result.busy { background: var(--gold-soft); border-color: #F3DCA8; color: #6B4E12; }
.set-result.busy::before {
  content: ''; width: 14px; height: 14px; margin-top: 2px; border-radius: 50%;
  border: 2px solid rgba(107, 78, 18, .25); border-top-color: #6B4E12;
  animation: set-spin .8s linear infinite;
}

/* 보내는 동안 버튼 - 막고, 도는 표시 */
.btn-main.is-busy, .btn-main:disabled { cursor: progress; filter: saturate(.6); opacity: .85; transform: none; }
.btn-main.is-busy::before {
  content: ''; display: inline-block; width: 13px; height: 13px; margin-right: 8px;
  vertical-align: -2px; border-radius: 50%;
  border: 2px solid rgba(35, 24, 21, .25); border-top-color: var(--ink);
  animation: set-spin .8s linear infinite;
}
@keyframes set-spin { to { transform: rotate(360deg); } }

/* 메일 양식 - 알약 · 판 · 편집기 | 미리보기 */
.set-pills { display: flex; gap: 6px; }
.set-pill {
  padding: 7px 16px; border-radius: 999px; border: 1px solid var(--line);
  font-size: 13px; font-weight: 700; color: var(--muted); text-decoration: none;
}
.set-pill.on { background: var(--ink); border-color: var(--ink); color: #fff; }
.set-vers { display: flex; gap: 5px; flex-wrap: wrap; }
.set-ver {
  padding: 4px 10px; border-radius: 8px; font-size: 12px; font-weight: 700;
  background: #F3F0EA; color: var(--muted); text-decoration: none;
}
.set-ver.cur { color: #1E9E58; }
.set-ver.on { background: var(--gold-soft); color: var(--ink); outline: 1.5px solid var(--gold); }
.set-tpl { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 16px; margin-top: 12px; }
.set-tpl label { display: block; font-size: 12px; font-weight: 700; color: var(--muted); margin: 8px 0 5px; }
.set-tpl input[type=text], .set-tpl textarea {
  width: 100%; box-sizing: border-box; border: 1.5px solid var(--line); border-radius: 10px;
  padding: 10px 12px; font: inherit; font-size: 13px; background: #fff; color: var(--text);
}
.set-tpl textarea { height: 380px; resize: vertical; font-family: ui-monospace, Consolas, monospace;
                    font-size: 12px; line-height: 1.55; }
.set-tpl input:focus, .set-tpl textarea:focus { outline: none; border-color: var(--gold); }
.set-frame { width: 100%; height: 470px; border: 1.5px solid var(--line); border-radius: 12px; background: #F6F3EE; }
.set-chips { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 8px; }
.set-chip {
  padding: 4px 9px; border-radius: 8px; border: 1px dashed var(--gold-deep);
  background: var(--gold-soft); color: #6B4E12; font: inherit; font-size: 11.5px; font-weight: 700;
  cursor: pointer;
}
.set-chip:hover { background: var(--gold); }
.set-tpl-btns { grid-column: 1 / -1; display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.set-tpl-act { margin-top: 12px; padding-top: 12px; border-top: 1px dashed var(--line); }
.set-grow { flex: 1 1 auto; }

/* 회사 로고 */
.set-upload { display: flex; gap: 10px; align-items: center; margin: 12px 0 16px; flex-wrap: wrap; }
.set-logos { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 12px; }
.set-logo { border: 1px solid var(--line); border-radius: 14px; overflow: hidden; background: #fff; }
.set-logo.cur { border-color: var(--gold); box-shadow: 0 0 0 2px var(--gold-soft); }
.set-logo-img { height: 110px; display: flex; align-items: center; justify-content: center;
                background: repeating-conic-gradient(#F3F0EA 0% 25%, #fff 0% 50%) 50% / 16px 16px; }
.set-logo-img img { max-width: 85%; max-height: 80px; object-fit: contain; }
.set-logo-meta { padding: 10px 12px 4px; display: flex; flex-direction: column; gap: 2px; }
.set-logo-meta b { font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.set-logo-meta span { font-size: 11.5px; color: var(--muted); }
.set-logo-btns { padding: 8px 12px 12px; display: flex; gap: 6px; align-items: center; }

@media (max-width: 900px) {
  .set-layout { grid-template-columns: 1fr; }
  .set-nav { position: static; display: flex; flex-wrap: wrap; gap: 4px; }
  .set-nav-cap { display: none; }
  .set-tpl { grid-template-columns: 1fr; }
}

.adm-head {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin: 22px 0 8px;
}
.adm-head h3 { margin: 0; font-size: 16px; font-weight: 800; }

.btn-add {
  padding: 9px 18px; border: 0; border-radius: 999px;
  background: linear-gradient(135deg, var(--gold), var(--gold-deep));
  color: var(--ink);
  font: inherit; font-size: 13px; font-weight: 800;
  cursor: pointer;
  box-shadow: 0 4px 14px rgba(217, 162, 47, .28);
  transition: transform .18s, filter .18s;
}
.btn-add:hover { transform: translateY(-2px); filter: brightness(1.04); }

.adm-note {
  margin: 0 0 16px; padding: 11px 15px;
  background: var(--gold-soft); border-left: 3px solid var(--gold);
  border-radius: 0 10px 10px 0;
  font-size: 12px; line-height: 1.65; color: #6B6154;
}

.adm-list { display: flex; flex-direction: column; gap: 9px; }

.adm-row {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  padding: 15px 18px;
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--r);
  transition: box-shadow .18s, transform .18s;
}
.adm-row:hover { box-shadow: var(--shadow); transform: translateY(-1px); }
/* 정지된 줄은 흐리게. 지운 것이 아니라 멈춘 것이라는 게 보여야 한다. */
.adm-row.off { background: #FAF8F3; opacity: .72; }

.adm-main { flex: 1 1 300px; min-width: 0; }
.adm-title {
  display: flex; flex-wrap: wrap; align-items: center; gap: 7px;
  font-size: 14.5px; font-weight: 700;
}
.adm-sub { margin-top: 4px; font-size: 11.5px; color: var(--muted); }
.adm-detail {
  margin-top: 6px; font-size: 11px; color: #8A8378;
  word-break: break-all; line-height: 1.55;
}
.adm-warn {
  margin-top: 9px; padding: 9px 12px;
  background: var(--warn-bg); border-left: 3px solid var(--warn);
  border-radius: 0 8px 8px 0;
  font-size: 11.5px; line-height: 1.6; color: #7A5A10;
}
.mono { font-family: ui-monospace, SFMono-Regular, Consolas, monospace; }

.adm-btns { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.adm-nobtn { font-size: 11px; color: var(--muted); }
.btn-sm-danger { border-color: #F3C9C9; color: var(--crit); }
.btn-sm-danger:hover { background: var(--crit-bg); color: var(--crit); }

/* 배지 */
.chip {
  padding: 2px 8px; border-radius: 999px;
  font-size: 10.5px; font-weight: 800; letter-spacing: .03em; color: #fff;
}
/* ★ 등급 색은 :root 의 --role-* 에서 온다. 상단바 배지(.role-badge.*)와
   같은 값을 써야 하는 자리라 한 곳에서만 정한다.
   ★ 이 셋은 users.jsp 가 class="chip chip-${...}" 로 이름을 조립해 쓴다.
     소스를 글자로 훑으면 안 잡히니 "안 쓰는 것" 으로 보고 지우지 말 것. */
.chip-admin  { background: var(--role-admin); }
/* SUPER_ADMIN - ADMIN 과 한눈에 갈려야 한다. 이 등급만 관리자 계정을 손댄다. */
.chip-super  { background: var(--role-super); }
.chip-issuer { background: var(--role-issuer); }
.chip-code   { background: linear-gradient(135deg, var(--c4), #2E7FD4); }
.chip-me     { background: linear-gradient(135deg, var(--c2), #2BA89F); }
.chip-off    { background: #B3ABA0; }
/* 빠진 것을 알리는 배지 둘. 하는 일이 같아 색을 나눠 쓴다 -
   따로 두면 한쪽만 손댔을 때 같은 뜻인데 다른 색이 된다. */
.chip-nokey,
.chip-nolink { background: linear-gradient(135deg, var(--warn), #C77A00); }

/* 입력 창 */
.modal-form { max-width: 520px; width: 92vw; text-align: left; }
.modal-form .field { margin-top: 16px; }
.modal-form .field:first-child { margin-top: 8px; }
.modal-form .field input, .modal-form .field select {
  width: 100%; padding: 11px 13px;
  border: 1.5px solid var(--line); border-radius: 10px;
  background: #FEFDFA; color: var(--text);
  font: inherit; font-size: 13.5px;
}
.modal-form .field input:focus, .modal-form .field select:focus {
  outline: 0; border-color: var(--gold);
  box-shadow: 0 0 0 4px rgba(240, 193, 85, .18);
}
.modal-form .field-help { line-height: 1.6; }
.modal-form .msg { text-align: left; white-space: pre-line; line-height: 1.6; }
/* v2.8 에서 .modal-form .ro 와 .pick-wide 를 걷었다.
   .ro 는 못 고치는 칸에 붙이던 것인데 readonly 칸이 한 군데도 없고,
   .pick-wide 는 바로 위 .modal-form .field select 가 이미 width:100% 라
   덧붙일 것이 없었다. 안 쓰는 것을 남겨 두면 다음 사람이 다시 쓴다(v33·v40). */

/* 잘 됐다는 안내. 빨강·노랑만 있으면 성공이 눈에 안 띈다. */
.msg.okmsg { color: #1E9E92; }

/* ============================================================
   v2.1 — 회사 축 (2026.09.04)

   회사 칩은 제품 칩과 나란히 선다. 두 축이 서로 다른 것이라 색을 갈랐다 —
   제품은 골드, 회사는 스카이. 같은 색이면 무엇을 고르는 자리인지 헷갈린다.
   ============================================================ */

.cust-btn {
  background: linear-gradient(135deg, #E8F2FF, #F7FBFF);
  border-color: #CFE2FA;
}
.cust-btn .prod-dot { background: var(--c4); }

/* 통합(전체 회사)일 때는 한 칸 물러나 보이게 한다. 고른 상태와 구분되어야 한다. */
.cust-btn.all {
  background: #FFFFFF;
  border-color: var(--line);
  color: var(--muted);
}
.cust-btn.all .prod-dot { background: var(--muted); }

/* ---- 대시보드 회사별 ---- */

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

.cust-row {
  border: 1px solid var(--line);
  border-radius: 14px;
  padding: 14px 16px;
  background: var(--panel);
  transition: border-color .15s, box-shadow .15s;
}
.cust-row.on { border-color: var(--gold); box-shadow: 0 0 0 3px var(--gold-soft); }
.cust-row.off { opacity: .55; }

.cust-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.cust-name { font-weight: 700; color: var(--text); }
.cust-code {
  font-size: 11px; letter-spacing: .04em;
  color: var(--muted); background: #F5F3EE;
  border-radius: 6px; padding: 2px 7px;
}
.cust-issues { margin-left: auto; font-size: 12px; color: var(--muted); }

/* ---- 몫 막대 ----
   칸을 개수만큼 그린다. 수량이 1~3대라 백분율로는 "몇 대 남았나" 가 안 읽힌다. */

.quota { display: flex; align-items: center; gap: 10px; margin-top: 10px; flex-wrap: wrap; }
.quota-bar { display: inline-flex; gap: 4px; }

.pip {
  width: 22px; height: 10px; border-radius: 5px;
  background: #EDE9E1; display: inline-block;
}
.pip-on { background: linear-gradient(90deg, var(--gold), var(--gold-deep)); }
.pip-over { background: #E4574B; }

.quota-text { font-size: 12.5px; color: var(--muted); }
.quota-text.full { color: var(--gold-deep); font-weight: 600; }
.quota-text.over { color: #C63A2E; font-weight: 700; }
.quota-none { font-size: 12.5px; color: #C63A2E; }

/* ---- 관리 화면 : 고객사 ---- */

.adm-row-block { align-items: flex-start; }
/* v2.8 에서 .adm-empty 를 걷었다. 관리 화면 넷이 전부 .empty 를 쓴다. */

.chip-cust { background: var(--role-cust-bg); color: var(--role-cust-fg); }

.qlist {
  margin-top: 12px; padding: 12px;
  border: 1px dashed var(--line); border-radius: 12px;
  background: #FCFBF8;
  display: flex; flex-direction: column; gap: 8px;
}

.qrow {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 8px 10px; border-radius: 10px; background: var(--panel);
  border: 1px solid var(--line);
}
.qrow.full { border-color: var(--gold); }
.qrow.over { border-color: #E4574B; background: #FFF6F5; }
.qrow.off { opacity: .55; }

.qprod { font-weight: 600; font-size: 13px; }
.qcode {
  font-size: 11px; color: var(--muted);
  background: #F5F3EE; border-radius: 6px; padding: 2px 6px;
}
.qtext { font-size: 12px; color: var(--muted); margin-left: auto; }

.btn-sm-wide { align-self: flex-start; }

/* ---- 관리 화면 : 서버 목록 ---- */

.slist {
  margin-top: 12px; display: flex; flex-direction: column; gap: 8px;
}
.slist-title {
  font-size: 12px; font-weight: 700; color: var(--muted);
  letter-spacing: .04em;
}

.srow {
  padding: 10px 12px; border: 1px solid var(--line);
  border-radius: 10px; background: var(--panel);
}
/* 물러난 서버는 한도에서 빠진 것이다. 살아 있는 것과 확실히 구분되어야 한다. */
.srow.retired { background: #FAF9F6; opacity: .7; }

.srow-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.smono {
  font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: 12px; color: var(--text);
  background: #F5F3EE; border-radius: 6px; padding: 2px 7px;
}
.sname { font-size: 13px; font-weight: 600; }
.srow-btns { margin-top: 8px; display: flex; gap: 6px; }

/* ============================================================
   v2.2 — 연결 안내와 발급 자리 (2026.09.04)
   ============================================================ */

/* 회사는 골랐는데 이은 제품이 없을 때. 화면 알맹이를 감추고 이것만 남긴다. */
.unlink {
  margin: 0 auto 18px;
  max-width: 1180px;
  padding: 16px 18px;
  border: 1px solid var(--gold);
  border-left: 5px solid var(--gold);
  border-radius: 14px;
  background: var(--gold-soft);
  display: flex; flex-direction: column; gap: 6px;
}
.unlink strong { color: var(--ink); font-size: 15px; }
.unlink span { color: #7A6A45; font-size: 13px; line-height: 1.7; }

/* 알맹이를 감춘다. 관리 화면은 남긴다 — 여기서 바로 연결하러 가야 한다. */
main.unlinked .cards,
main.unlinked .byprod,
main.unlinked .issue-grid,
main.unlinked .filters,
main.unlinked .hist,
main.unlinked .pager,

/* ---- 발급 화면 : 누구 몫으로 나가나 ----
   회사를 고르는 걸 잊고 사내로 내보내는 실수를 막는 자리다. */
.iscope {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 10px 14px; margin-bottom: 14px;
  border-radius: 12px;
  background: #E8F2FF; border: 1px solid #CFE2FA;
}
.iscope-tag {
  font-size: 11px; font-weight: 700; letter-spacing: .04em;
  color: #2F6FBF; background: #FFFFFF;
  border-radius: 6px; padding: 3px 8px;
}
.iscope-name { font-size: 13.5px; font-weight: 600; color: var(--text); }
.iscope-sub { font-size: 12px; color: var(--muted); margin-left: auto; }

/* 사내 발급은 색을 갈라 둔다. 파랑이면 회사 몫, 회색이면 사내다. */
.iscope-inhouse { background: #F5F3EE; border-color: var(--line); }
.iscope-inhouse .iscope-tag { color: var(--muted); }
.iscope-inhouse .iscope-name { color: var(--muted); font-weight: 500; }

/* ---- 발급 결과 : 한도 ---- */
.flag-quota { border-left-color: var(--c4); background: #F2F8FF; }
.flag-quota-full { border-left-color: var(--gold-deep); background: var(--gold-soft); }

/* ---- 발급 : 한도가 찼을 때 ----
   폼 대신 이것만 보인다. 무엇을 해야 하는지까지 적어야 담당자가 다음으로 간다. */
.blocked { display: flex; flex-direction: column; gap: 12px; }
.blk-title { font-size: 16px; font-weight: 700; color: var(--gold-deep); }
.blk-quota {
  font-size: 13px; color: var(--muted);
  background: var(--gold-soft); border-radius: 8px; padding: 8px 12px;
  align-self: flex-start;
}
.blk-list { display: flex; flex-direction: column; gap: 8px; }
.blk-item {
  font-size: 13px; color: var(--text); line-height: 1.7;
  padding-left: 14px; position: relative;
}
.blk-item::before {
  content: ''; position: absolute; left: 0; top: 9px;
  width: 5px; height: 5px; border-radius: 50%; background: var(--gold);
}
.blocked .btn-main { align-self: flex-start; }


/* ============================================================
   행위 로그 (v27 · 2026.09.07)

   ★ 표가 아니라 줄이다. 본문 길이가 들쭉날쭉해서 표로 두면 칸이 계속 흔들린다.
   ★ 거부(result='F')는 왼쪽 코랄 띠로 갈라 둔다. 숫자만으로는 안 보인다.
   ============================================================ */

.audit { display: flex; flex-direction: column; gap: 6px; margin-top: 4px; }

.au-head {
  display: grid;
  grid-template-columns: 150px 190px 150px 1fr 190px;
  gap: 10px;
  padding: 6px 14px;
  font-size: 12px; font-weight: 700; color: var(--muted);
  border-bottom: 1px solid var(--line);
}

.au-row {
  display: grid;
  grid-template-columns: 150px 190px 150px 1fr 190px;
  gap: 10px; align-items: center;
  background: var(--panel);
  border: 1px solid var(--line);
  border-left: 4px solid transparent;
  border-radius: 10px;
  padding: 9px 14px;
  font-size: 13px; color: var(--text);
}
.au-row:hover { border-color: var(--gold); }

/* 거부·실패 */
.au-row.is-fail { border-left-color: var(--c1); background: #FFF7F4; }

.au-when { color: var(--muted); font-size: 12px; }
.au-act { display: flex; gap: 4px; flex-wrap: wrap; }
.au-who { display: flex; flex-direction: column; line-height: 1.4; }
.au-name { font-weight: 600; }
.au-id { font-size: 11px; color: var(--muted); }
.au-none { color: var(--muted); }
.au-detail { line-height: 1.6; word-break: break-all; }
.au-meta { display: inline-flex; gap: 4px; margin-left: 6px; vertical-align: middle; }
.au-fp { font-size: 11px; color: var(--muted); }
.au-ip { color: var(--muted); font-size: 12px;
         display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }

/* 접속 구분. 해외만 진하게 - 매일 보는 사내·국내가 눈을 끌면 해외가 묻힌다. */
.tag-zone-foreign  { background: linear-gradient(135deg, #FF6B6B, #D93025); }
.tag-zone-domestic { background: #D6E9FF; color: #1F5FA8; }
.tag-zone-internal { background: #E9E5E0; color: #6B6259; }
.tag-zone-unknown  { background: #EDE9E4; color: var(--muted); }

/* 상단바 등급 배지는 .role-badge 가 있는 자리(위쪽)로 모았다 - v2.8.
   .admin·.issuer 는 여기서 700줄쯤 위에 있었고 .super·.cust 는 여기 있었다.
   등급 색을 바꿀 때 한쪽만 고치기 쉬운 모양이었다. */

/* 기간 사이 물결 */
.date-tilde { color: var(--muted); padding: 0 2px; }

/* 좁은 화면에서는 칸을 접는다. 그대로 두면 본문이 한 글자씩 끊긴다. */
@media (max-width: 1100px) {
  .au-head { display: none; }
  .au-row {
    grid-template-columns: 1fr;
    gap: 4px;
  }
  .au-ip::before { content: '접속 '; }
}


/* ============================================================
   관리 목록 — 거르기 막대 (v30 · 2026.09.07)

   ★ 페이저는 이력 화면의 .pager .pg 를 그대로 쓴다. 모양이 갈리면
     같은 조작인데 다른 것처럼 보인다.
   ============================================================ */

.adm-bar {
  display: flex; align-items: center; gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 12px;
}
.adm-bar .search { flex: 1 1 260px; max-width: 420px; }
.adm-bar .count { color: var(--muted); font-size: 12px; white-space: nowrap; }

.adm-showall {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 13px; color: var(--text); cursor: pointer;
  user-select: none; white-space: nowrap;
}
.adm-showall input { width: 15px; height: 15px; accent-color: var(--gold-deep); }

/* 숨은 건수 안내. 안 알려주면 "계정이 사라졌다" 가 된다. */
.adm-hidden {
  font-size: 12px; color: var(--gold-deep);
  background: var(--gold-soft);
  border-radius: 999px; padding: 3px 10px;
  white-space: nowrap;
}

/* 관리 목록의 페이저는 목록 아래에 붙는다 */
.adm-list + .pager { margin-top: 14px; }

/* ============================================================================
   계정 갈래 이관에서 더한 것 (2026.09.08 · v15 에서 정리)

   ★ 있던 규칙은 한 줄도 안 고쳤다. 새로 쓰는 클래스만 뒤에 더한다.

   ★ v13 에서 더했던 .off-badge · .me-badge · .cust-chip · .role-badge.cust ·
     .locked 는 전부 걷어냈다. 같은 일을 하는 .chip-off · .chip-me ·
     .chip-cust · .adm-nobtn 이 이미 있는데 내가 새 이름을 만들었던 것이다.
     화면도 옛 이름을 쓰도록 되돌렸다.

   ★ v16 에서 .chk 도 걷어냈다. 검색막대를 옛 구조(.adm-bar + .adm-showall)로
     되돌리면서 쓸 자리가 없어졌다. 남은 것은 정말 없던 넷뿐이다.
   ============================================================================ */

/* 상단바의 로그아웃이 폼이 됐다. GET 으로 두면 남의 사이트에 심어 둔
   이미지 한 장으로 로그아웃시킬 수 있어 POST 로 바꿨고, 폼이 줄바꿈을
   만들지 않게 자리만 잡아 준다. */
.logout-form { display: inline-flex; margin: 0; }

/* 목록 줄 안의 폼(정지 · 다시 쓰기). 버튼 하나짜리라 자리를 안 먹어야 한다. */
.inline-form { display: inline; margin: 0; }

/* 관리 화면의 갈래 하나 */
.adm-pane { margin-top: 14px; }

/* 걸린 것이 없을 때. 빈 화면을 그냥 두면 "고장난 건가" 가 된다. */
.empty {
  margin: 0;
  padding: 28px 18px;
  border: 1px dashed var(--line);
  border-radius: var(--r);
  text-align: center;
  font-size: 13px;
  color: var(--muted);
}

/* ★ 세그가 <button> 에서 <a> 가 됐다(서버 렌더링이라 주소가 곧 상태).
   버튼이던 시절에는 없던 밑줄이 붙고, 버튼의 기본 가운데 정렬도 사라진다.
   .tab 에 이미 같은 자리가 있다 - 그때와 같은 이유다. */
.seg-btn {
  text-decoration: none;
  text-align: center;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* ============================================================
   v2.3 — 잠깐 보였다 사라지는 안내 (2026.09.09)

   로그인 화면이 "왜 여기로 왔는지" 를 한 줄로 알려 주는 자리다. 읽고 나면
   할 일이 끝나는 문구라 스스로 스러진다 — 지우는 시점은 login.js 가 잡고,
   여기서는 어떻게 보일지만 정한다.

   ★ 글자만 바꾸지 않고 알약 모양 칩으로 세운 이유 — 바로 아래가 JS 가 쓰는
     같은 자리다. 색만 다르면 "서버가 알려 준 것" 과 "지금 누른 결과" 가
     같아 보인다. 칩은 스러지고 글자는 남는다.

   ★ width:fit-content + margin auto 로 가운데 세운다. inline-flex 로 두면
     부모가 가운데 정렬을 해 줘야 하는데, .login-box 는 그런 자리가 아니다.
   ============================================================ */

.msg.flash {
  display: flex;
  align-items: center;
  gap: 8px;
  width: fit-content;
  max-width: 100%;
  margin: 14px auto 0;
  padding: 9px 15px;
  border: 1px solid transparent;
  border-radius: 999px;
  font-size: 12.5px;
  font-weight: 600;
  line-height: 1.5;
  text-align: left;
  box-shadow: var(--shadow-sm);
  animation: flash-in .34s cubic-bezier(.22, 1, .36, 1) both;
  transition: opacity .5s ease, transform .5s ease;
}

/* 앞의 점. 아이콘 글꼴을 안 쓰고 색만으로 종류를 말한다. */
.msg.flash::before {
  content: "";
  flex: none;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
  opacity: .8;
}

.msg.flash.warn   { background: var(--warn-bg); border-color: #F3DCA8; color: var(--warn); }
.msg.flash.okmsg  { background: #E6F6F4;        border-color: #BCE6E1; color: #1E9E92; }

/* 스러지는 상태. login.js 가 이 클래스를 붙이고 FADE_MS 뒤에 자리를 비운다. */
.msg.flash.out { opacity: 0; transform: translateY(-6px); }

@keyframes flash-in {
  from { opacity: 0; transform: translateY(-9px) scale(.96); }
  to   { opacity: 1; transform: none; }
}

/* 움직임을 줄여 달라고 한 브라우저에서는 뜨고 지는 동작만 없앤다.
   문구 자체는 그대로 보이고 그대로 사라진다. */
@media (prefers-reduced-motion: reduce) {
  .msg.flash { animation: none; transition: opacity .2s linear; }
  .msg.flash.out { transform: none; }
}

/* ============================================================
   v2.4 — 한도 안내의 버튼 (2026.09.09)

   .blk-* 와 .blocked .btn-main 은 옛 판에서 통째로 옮겨져 있었는데 붙이는
   쪽(issue.jsp)이 안 옮겨져 죽어 있던 규칙이다. .msg.okmsg(v30) ·
   .prod-btn.single(v36) 에 이은 세 번째다.

   ★ 여기서는 button 이 아니라 <a> 다. 서버가 그리는 화면이라 주소가 곧
     상태이기 때문이다. 그런데 .btn-main 의 크기(여백·모서리)는 그 위의
     `.modal-btns button` 에서 오므로, 링크로 쓰면 색만 있고 크기가 없다.
     .seg-btn 이 같은 이유로 이미 이렇게 되어 있다 - 그때와 같은 자리다.
   ============================================================ */

.blocked a.btn-main {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 11px 18px;
  border-radius: 12px;
  font-size: 14px;
  font-weight: 700;
  text-decoration: none;
  cursor: pointer;
  transition: all .16s;
}

/* ============================================================
   v2.5 — 경고 안의 링크 (2026.09.09)

   "쓰는 회사 없음" 경고에서 이어 주러 가는 길이다. 경고 상자는 글자색이
   따로 있어(#7A5A10) 링크가 파란 밑줄로 튀면 안 읽힌다.
   ============================================================ */

.adm-warn a {
  color: inherit;
  font-weight: 800;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.adm-warn a:hover { color: var(--gold-deep); }

/* 고를 것을 세로로 쌓는 자리. 계정 창의 "맡을 제품" 이 쓴다.
   제품이 늘어도 창이 길어지지 않게 여기서만 스크롤한다. */
.pick-list {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 6px 8px;
    border: 1px solid var(--line);
    border-radius: 8px;
    background: #fff;
    max-height: 168px;
    overflow-y: auto;
}

/* 체크박스 한 줄. 코드를 고정폭으로 왼쪽에 맞추면 목록이 표처럼 읽힌다. */
.pick-row {
    display: flex;
    align-items: baseline;
    gap: 8px;
    padding: 4px 6px;
    border-radius: 6px;
    font-size: 13px;
    color: var(--text);
    cursor: pointer;
    user-select: none;
}
.pick-row:hover { background: var(--paper, #f7f8fa); }
.pick-row input {
    width: 15px; height: 15px;
    accent-color: var(--gold-deep);
    flex: 0 0 auto;
    align-self: center;
}
/* 코드는 자리를 고정한다. 이름 길이가 제각각이어도 세로로 줄이 맞는다. */
.pick-row b {
    flex: 0 0 88px;
    font-family: "D2Coding", "Consolas", monospace;
    font-weight: 700;
}
/* 이름이 길면 줄바꿈 대신 잘라 낸다. 한 줄에 하나가 이 목록의 규칙이다. */
.pick-row .dim {
    flex: 1 1 auto;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ============================================================
   ★ .field 안에서는 이미 걸린 규칙이 더 세다 (v2.22)

     .field label  (0,1,1)   display:block · margin-bottom:7px
     .field input  (0,1,1)   width:100% · padding:12px 14px · border:1.5px

   그래서 .pick-row 만으로는 안 먹는다. .field 로 감싸 (0,2,1) 로 올리고,
   체크박스가 물려받은 상자 모양을 되돌린다.
   ============================================================ */

.field .pick-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0;
  padding: 7px 8px;
  border-radius: 8px;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0;
  color: var(--text);
  cursor: pointer;
  user-select: none;
  transition: background .14s;
}
.field .pick-row:hover { background: rgba(240, 193, 85, .12); }

/* 체크박스를 다시 체크박스로. .field input 의 상자 모양을 전부 되돌린다. */
.field .pick-row input[type="checkbox"] {
  width: 16px; height: 16px;
  flex: 0 0 16px;
  margin: 0; padding: 0;
  border: 0; border-radius: 0;
  background: none; box-shadow: none;
  accent-color: var(--gold-deep);
  cursor: pointer;
}
.field .pick-row input[type="checkbox"]:focus {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
  box-shadow: none;
}

/* 코드는 자리를 고정해 세로로 줄을 맞춘다. */
.field .pick-row b {
  flex: 0 0 84px;
  font-family: ui-monospace, "D2Coding", Consolas, monospace;
  font-weight: 800;
  font-size: 12.5px;
  color: var(--text);
}
/* 이름이 길면 잘라 낸다. 한 줄에 하나가 이 목록의 규칙이다. */
.field .pick-row .dim {
  flex: 1 1 auto;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* 고른 줄은 옅은 금색으로 남는다 - 무엇을 골랐는지 스크롤해도 보인다. */
.field .pick-row:has(input:checked) {
  background: rgba(240, 193, 85, .18);
}

/* 콤보박스. 종전에는 규칙이 없어 칸들과 결이 안 맞았다. */
.field select {
  width: 100%; padding: 12px 14px;
  border: 1.5px solid var(--line); border-radius: var(--r);
  background: #FEFDFA; color: var(--text);
  font: inherit; font-size: 13.5px;
  cursor: pointer;
  transition: border-color .18s, box-shadow .18s;
}
.field select:focus {
  outline: 0; border-color: var(--gold);
  box-shadow: 0 0 0 4px rgba(240, 193, 85, .18);
}

/* ============================================================
   비밀번호 칸 — 토닥 회원가입과 같은 구성
   ============================================================ */

.pwd-wrap { position: relative; }
/* 눈 아이콘이 글자를 덮지 않게 오른쪽을 비운다. */
.field .pwd-wrap input { padding-right: 44px; }

.pwd-toggle {
  position: absolute; top: 50%; right: 8px;
  transform: translateY(-50%);
  display: flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; padding: 0;
  border: 0; border-radius: 8px;
  background: none; color: var(--muted);
  cursor: pointer;
  transition: color .14s, background .14s;
}
.pwd-toggle:hover { background: rgba(0, 0, 0, .05); color: var(--text); }
.pwd-toggle svg { fill: currentColor; display: block; }
/* 보이는 동안에는 아이콘이 켜져 있다는 것을 색으로 알린다. */
.pwd-toggle.on { color: var(--gold-deep); }

/* 세기 막대 셋. 점수만큼 색이 찬다. */
.pwd-strength { display: flex; gap: 5px; margin-top: 8px; }
.pwd-strength-bar {
  flex: 1 1 0; height: 4px;
  border-radius: 999px;
  background: var(--line);
  transition: background .18s;
}
.pwd-strength-bar.active-weak   { background: var(--danger, #D1462F); }
.pwd-strength-bar.active-medium { background: #FF7F00; }
.pwd-strength-bar.active-strong { background: var(--ok, #1F8F57); }

.pwd-strength-text {
  margin-top: 6px;
  font-size: 11.5px; font-weight: 600;
  color: var(--muted);
}
.pwd-strength-text.weak   { color: var(--danger, #D1462F); }
.pwd-strength-text.medium { color: #B25E00; }
.pwd-strength-text.strong { color: var(--ok, #1F8F57); }

/* 일치 여부. 빈 문자열이면 자리를 안 차지한다. */
.form-feedback {
  margin-top: 7px;
  font-size: 11.5px; font-weight: 600;
  min-height: 1em;
}
.form-feedback.valid   { color: var(--ok, #1F8F57); }
.form-feedback.invalid { color: var(--danger, #D1462F); }

/* ============================================================================
   서버 보드 (v2.23)

   숫자 넷이 "얼마나" 를 말하면, 여기는 "어디에 무엇이" 를 그린다.
   ============================================================================ */

.board-wrap { margin-top: 34px; }

.board-head {
  display: flex; align-items: baseline; gap: 10px;
  flex-wrap: wrap; margin-bottom: 14px;
}
.board-title { margin: 0; font-size: 15px; font-weight: 800; letter-spacing: -.01em; }
.board-sub   { margin: 0; font-size: 12px; color: var(--muted); }

/* 손봐야 할 것을 먼저 알리는 한 줄. 오른쪽 끝에 붙여 눈이 마지막에 닿게 한다. */
.board-flag {
  margin-left: auto;
  padding: 5px 12px; border-radius: 999px;
  font-size: 12px; font-weight: 700;
}
.board-flag.warn { background: rgba(255, 127, 0, .13); color: #B25E00; }
.board-flag.ok   { background: rgba(31, 143, 87, .12); color: #1F8F57; }

/* 벤토 격자. 폭에 맞춰 알아서 접힌다 - 화면 크기를 숫자로 못 박지 않는다. */
.board-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(228px, 1fr));
  gap: 12px;
}

.svcard {
  position: relative;
  padding: 15px 16px 13px;
  border: 1px solid var(--line);
  border-radius: 16px;
  background: #fff;
  box-shadow: 0 1px 2px rgba(16, 32, 44, .04);
  transition: transform .16s ease, box-shadow .16s ease, border-color .16s ease;
}
/* ★ 2px 만 뜬다. 크게 움직이면 격자가 출렁여 읽던 자리를 잃는다. */
.svcard:hover {
  transform: translateY(-2px);
  box-shadow: 0 8px 20px rgba(16, 32, 44, .09);
  border-color: rgba(240, 193, 85, .55);
}

/* 신호등은 카드 위쪽 띠 하나로. 카드 전체를 물들이면 글자가 안 읽힌다. */
.svcard::before {
  content: "";
  position: absolute; left: 16px; right: 16px; top: 0;
  height: 3px; border-radius: 0 0 3px 3px;
  background: var(--line);
}
.svcard.tone-ok::before   { background: linear-gradient(90deg, #1F8F57, #46C08A); }
.svcard.tone-warn::before { background: linear-gradient(90deg, #C77A00, #FF9E2C); }
.svcard.tone-bad::before  { background: linear-gradient(90deg, #B8371F, #E8674E); }
.svcard.tone-off::before  { background: var(--line); }

/* 물러난 서버는 흐리게. 지우지 않는 이유는 교체가 있었다는 사실을 남기려는 것. */
.svcard.tone-off { background: #FAFAFA; }
.svcard.tone-off .svcard-name,
.svcard.tone-off .svcard-fp { color: var(--muted); }

.svcard-top {
  display: flex; align-items: center; gap: 8px;
  margin-bottom: 10px;
}
.svcard-name {
  flex: 1 1 auto;
  font-size: 13.5px; font-weight: 800; letter-spacing: -.01em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* 지문을 점 셋으로. 찬 점이 안정 지문 개수다. */
.svcard-dots { display: flex; align-items: center; gap: 5px; margin-bottom: 8px; }
.svcard-dots .dot {
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--line);
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .04);
}
.tone-ok   .dot.on { background: #1F8F57; }
.tone-warn .dot.on { background: #FF7F00; }
.tone-bad  .dot.on { background: #D1462F; }
.tone-off  .dot.on { background: #B9C2CA; }
.svcard-fpn { margin-left: 4px; font-size: 11px; font-weight: 700; color: var(--muted); }

.svcard-fp {
  font-family: ui-monospace, "D2Coding", Consolas, monospace;
  font-size: 12px; color: var(--steel); letter-spacing: .02em;
}

/* ★ 줄바꿈을 안 한다. 뱃지가 하나 늘면 다음 줄로 넘어가 카드 높이가 들쭉날쭉
     해지고, 격자에서 그게 제일 지저분하다. */
.svcard-meta {
  display: flex; align-items: center; gap: 6px;
  margin-top: 9px; font-size: 11px; color: var(--muted);
}
/* 날짜는 언제나 오른쪽 끝. 뱃지가 있든 없든 세로로 줄이 맞는다. */
.svcard-when { margin-left: auto; flex: 0 0 auto; white-space: nowrap; }
.svcard-serial {
  padding: 2px 7px; border-radius: 6px;
  background: rgba(16, 32, 44, .06);
  font-family: ui-monospace, Consolas, monospace; font-weight: 700;
  color: var(--text);
}
/* 여러 번 나간 서버. 잦으면 그 자리를 한 번 들여다볼 이유가 된다. */
.svcard-reissue {
  padding: 2px 7px; border-radius: 6px;
  background: rgba(122, 90, 16, .09); color: #7A5A10; font-weight: 700;
}

.svcard-foot {
  margin-top: 10px; padding-top: 9px;
  border-top: 1px dashed var(--line);
  font-size: 11.5px; font-weight: 600;
}
.svcard-ok    { color: var(--steel); }
/* 사내 발급분. 고객사 이름과 눈으로 갈리게 둔다. */
.svcard-ok.inhouse { color: var(--muted); font-weight: 500; }
.svcard-warn  { color: #B25E00; }
.svcard-moved { color: var(--muted); }
.svcard-moved .arrow { margin: 0 3px; color: var(--gold-deep); font-weight: 800; }

/* 좁은 화면에서는 한 줄에 하나. 카드를 억지로 줄이면 지문이 잘린다. */
@media (max-width: 520px) {
  .board-grid { grid-template-columns: 1fr; }
  .board-flag { margin-left: 0; }
}

/* ============================================================
   오류 화면 (403 · 404 · 500)
   ============================================================ */
.err-brand { padding: 18px 24px; }
.err-brand .logo { height: 28px; }
.err-main {
  display: flex; justify-content: center; align-items: center;
  max-width: none; min-height: calc(100vh - 170px);
  padding: 28px 16px 60px; box-sizing: border-box;
}

.err-stage {
  --err-a: var(--gold); --err-b: var(--gold-deep); --err-soft: var(--gold-soft);
  position: relative; width: 100%; max-width: 620px;
  display: flex; justify-content: center; padding: 40px 0;
}
.err-denied   { --err-a: #FF8A5B; --err-b: #EE5A24; --err-soft: #FFE9E0; }
.err-csrf     { --err-a: #F0C155; --err-b: #D9A22F; --err-soft: #FFF3D6; }
.err-notfound { --err-a: #4FD1C5; --err-b: #1E9E92; --err-soft: #E3F7F5; }
.err-server   { --err-a: #9F7AEA; --err-b: #6B46C1; --err-soft: #F0EAFE; }

/* 떠다니는 빛 방울 */
.err-blob {
  position: absolute; border-radius: 50%; filter: blur(42px); opacity: .55; z-index: 0;
  animation: err-float 9s ease-in-out infinite;
}
.err-blob.b1 { width: 260px; height: 260px; background: var(--err-a); top: -10px; left: -30px; }
.err-blob.b2 { width: 220px; height: 220px; background: var(--err-soft); bottom: -20px; right: -20px;
               animation-delay: -4s; opacity: .9; }
@keyframes err-float {
  0%, 100% { transform: translate(0, 0) scale(1); }
  50%      { transform: translate(18px, -14px) scale(1.06); }
}

/* 유리 카드 */
.err-card {
  position: relative; z-index: 1; width: 100%;
  background: rgba(255, 255, 255, .78);
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
  border: 1px solid rgba(255, 255, 255, .9);
  border-radius: 28px; padding: 34px 34px 28px; text-align: center;
  box-shadow: 0 24px 60px rgba(35, 24, 21, .10), 0 2px 0 rgba(255, 255, 255, .8) inset;
}

.err-code span {
  display: inline-block;
  font-size: clamp(72px, 16vw, 112px); font-weight: 900; line-height: 1; letter-spacing: -.04em;
  background: linear-gradient(135deg, var(--err-a), var(--err-b));
  -webkit-background-clip: text; background-clip: text; color: transparent;
  animation: err-pop .7s cubic-bezier(.2, 1.4, .4, 1) both;
}
@keyframes err-pop {
  0%   { transform: translateY(14px) scale(.9); opacity: 0; }
  100% { transform: translateY(0) scale(1); opacity: 1; }
}
.err-emoji {
  display: inline-grid; place-items: center; width: 58px; height: 58px; margin: 6px 0 4px;
  border-radius: 18px; background: var(--err-soft); font-size: 30px;
  animation: err-wiggle 2.6s ease-in-out 0.8s infinite;
}
@keyframes err-wiggle {
  0%, 86%, 100% { transform: rotate(0); }
  90% { transform: rotate(-10deg); }
  95% { transform: rotate(8deg); }
}
.err-card h1 { margin: 10px 0 8px; font-size: 22px; font-weight: 900; color: var(--ink); }
.err-lead { margin: 0 0 10px; font-size: 14.5px; line-height: 1.75; color: #4A4038; }
.err-lead b { color: var(--err-b); }
.err-tip  { margin: 0 auto 4px; max-width: 420px; font-size: 12.5px; line-height: 1.6; color: var(--muted); }

.err-chips { display: flex; flex-wrap: wrap; justify-content: center; gap: 6px; margin: 18px 0 4px; }
.err-chip {
  display: inline-flex; align-items: center; gap: 6px; max-width: 100%;
  padding: 5px 12px; border-radius: 999px; background: #F3F0EA;
  font-size: 12px; color: #4A4038; word-break: break-all;
}
.err-chip i { font-style: normal; font-weight: 800; color: var(--muted); }

.err-id {
  display: inline-flex; align-items: center; gap: 8px; margin: 6px 0 2px;
  padding: 8px 14px; border-radius: 12px; border: 1.5px dashed var(--err-b);
  background: var(--err-soft); color: var(--ink); font: inherit; font-size: 13px; cursor: pointer;
}
.err-id b { font-family: ui-monospace, Consolas, monospace; letter-spacing: .06em; }
.err-copy { font-size: 11px; font-weight: 800; color: var(--err-b); }
.err-id.copied { border-style: solid; }

.err-btns { display: flex; justify-content: center; gap: 10px; flex-wrap: wrap; margin-top: 22px; }
.err-btns .btn-main, .err-btns .btn-ghost {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 150px; padding: 12px 22px; border-radius: 14px; text-decoration: none; font-weight: 800;
}
.err-btns .btn-main { background: linear-gradient(135deg, var(--err-a), var(--err-b)); color: #fff; }
.err-csrf .err-btns .btn-main { color: var(--ink); }

@media (prefers-reduced-motion: reduce) {
  .err-blob, .err-code span, .err-emoji { animation: none; }
}


/* ============================================================
   켜고 끄는 조건 (v2.31 · 2026.09.16)

   ★ 검색 줄의 조건 하나 = 스위치 알약(.fx) + 딸린 칸(.fx-body).
     켜면 칸이 나오고, 끄면 칸이 사라지며 그 안의 입력이 막혀 조건이 안 실린다
     (common.js 의 Neosgen.wireToggles). 스위치 자체에는 name 이 없다.
   ★ 색은 변수만 바꾼다. 새 색이 필요하면 .fx-mint 같은 한 줄을 더한다.
   ============================================================ */

.fx,
.fx-body,
.fx-mint  { --fx-a: #3CC9A0; --fx-soft: #E8FBF3; --fx-line: #A6E6D0; --fx-ink: #17865F; --fx-glow: rgba(60, 201, 160, .22); }
.fx-lilac { --fx-a: #9B87F5; --fx-soft: #F2EFFF; --fx-line: #CFC5FB; --fx-ink: #5A47C9; --fx-glow: rgba(155, 135, 245, .22); }
.fx-peach { --fx-a: #FF9E7A; --fx-soft: #FFF1EB; --fx-line: #FFCBB8; --fx-ink: #C2552D; --fx-glow: rgba(255, 158, 122, .22); }
.fx-sky   { --fx-a: #6BB5F5; --fx-soft: #EBF5FF; --fx-line: #B9DAFA; --fx-ink: #2A6FB0; --fx-glow: rgba(107, 181, 245, .22); }

.fx {
  position: relative;
  display: inline-flex; align-items: center; gap: 8px;
  padding: 8px 15px 8px 9px;
  border: 1.5px solid var(--line); border-radius: 999px;
  background: var(--panel); color: var(--muted);
  font-size: 13px; font-weight: 800; white-space: nowrap;
  cursor: pointer; user-select: none;
  transition: background .2s, border-color .2s, color .2s, box-shadow .2s, transform .12s;
}
.fx:hover  { border-color: var(--fx-line); color: var(--fx-ink); }
.fx:active { transform: scale(.96); }

/* 진짜 체크박스는 감춘다. 키보드(스페이스)로는 그대로 눌린다. */
.fx input {
  position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0; pointer-events: none;
}

.fx-sw {
  position: relative; flex: none;
  width: 30px; height: 18px; border-radius: 999px;
  background: #DDD7CE;
  transition: background .2s;
}
.fx-sw::after {
  content: '';
  position: absolute; top: 2px; left: 2px;
  width: 14px; height: 14px; border-radius: 50%;
  background: #fff; box-shadow: 0 1px 3px rgba(35, 24, 21, .25);
  transition: transform .25s cubic-bezier(.34, 1.56, .64, 1);
}

.fx.is-on,
.fx:has(> input:checked) {
  background: var(--fx-soft); border-color: var(--fx-line); color: var(--fx-ink);
  box-shadow: 0 4px 14px var(--fx-glow);
}
.fx.is-on .fx-sw,
.fx:has(> input:checked) .fx-sw { background: var(--fx-a); }
.fx.is-on .fx-sw::after,
.fx:has(> input:checked) .fx-sw::after { transform: translateX(12px); }

.fx input:focus-visible + .fx-sw { outline: 2px solid var(--fx-a); outline-offset: 2px; }

/* 딸린 칸. 켠 스위치 바로 옆에 같은 색 점선 상자로 붙는다. */
.fx-body {
  display: inline-flex; align-items: center; flex-wrap: wrap; gap: 6px;
  padding: 4px;
  border: 1.5px dashed var(--fx-line); border-radius: 999px;
  background: var(--fx-soft);
  animation: fx-pop .22s ease-out;
}
.fx-body[hidden] { display: none; }
.fx-body .pick { padding: 7px 12px; border-color: transparent; }
.fx-body .pick:focus { border-color: var(--fx-a); }
.fx-body .date-tilde { color: var(--fx-ink); }

/* 딸린 칸 안의 고르기 알약(라디오) */
.fx-opt { position: relative; cursor: pointer; }
.fx-opt input {
  position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0; pointer-events: none;
}
.fx-opt span {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 6px 13px; border-radius: 999px;
  background: var(--panel); color: var(--fx-ink);
  font-size: 12.5px; font-weight: 700; white-space: nowrap;
  transition: background .18s, color .18s, box-shadow .18s, transform .12s;
}
.fx-opt:hover span  { box-shadow: inset 0 0 0 1.5px var(--fx-line); }
.fx-opt:active span { transform: scale(.95); }
.fx-opt input:checked + span {
  background: var(--fx-a); color: #fff;
  box-shadow: 0 4px 12px var(--fx-glow);
}
.fx-opt input:focus-visible + span { outline: 2px solid var(--fx-a); outline-offset: 2px; }
.fx-opt small {
  padding: 0 6px; border-radius: 999px;
  background: var(--fx-soft); color: var(--fx-ink);
  font-size: 10px; font-weight: 800;
}
.fx-opt input:checked + span small { background: rgba(255, 255, 255, .28); color: #fff; }

@keyframes fx-pop {
  from { opacity: 0; transform: translateX(-6px) scale(.96); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .fx, .fx-sw, .fx-sw::after, .fx-opt span { transition: none; }
  .fx-body { animation: none; }
}

/* 로그 줄 - 시각 아래에 남긴 자리 */
.au-row .au-when { display: flex; flex-direction: column; align-items: flex-start; gap: 3px; }
.au-site {
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  padding: 1px 8px; border-radius: 999px;
  background: #F2EFFF; color: #5A47C9;
  font-size: 10.5px; font-weight: 700;
}

/* ============================================================
   설정 → 공휴일 관리 (v2.32 · 2026.09.17)
   ============================================================ */
.hol-bar { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; margin: 14px 0 4px; }
.hol-note { color: var(--muted); font-size: 12.5px; }
.hol-preview {
  margin-top: 14px; padding: 14px 16px;
  border: 1.5px dashed #CFC5FB; border-radius: 16px; background: #F7F5FF;
}
.hol-preview-head { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 14px; margin-bottom: 10px; }
.hol-count { font-size: 12.5px; font-weight: 700; color: #5A47C9; }
.hol-table-wrap { overflow-x: auto; }
.hol-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.hol-table th {
  text-align: left; padding: 8px 10px; font-size: 12px; color: var(--muted);
  border-bottom: 1px solid var(--line); white-space: nowrap;
}
.hol-table td { padding: 7px 10px; border-bottom: 1px solid var(--line); vertical-align: middle; }
.hol-table td.mono { white-space: nowrap; }
.hol-table td.empty { text-align: center; color: var(--muted); padding: 22px 10px; }
.hol-we { color: #D2553A; }
.hol-old { margin-left: 6px; color: var(--muted); font-size: 12px; }
.hol-free td { color: var(--muted); }
.hol-free .hol-name { text-decoration: line-through; }
.hol-REMOVE td { color: #B04A30; }
.hol-SAME td, .hol-KEEP td { color: var(--muted); }
.hol-chip-new  { background: #E8FBF3; color: #17865F; }
.hol-chip-upd  { background: #FFF1EB; color: #C2552D; }
.hol-chip-del  { background: #FDEAEA; color: #B03030; }
.hol-chip-keep { background: #F2EFFF; color: #5A47C9; }
.hol-chip-same { background: #EEF0F2; color: #6B7280; }
.hol-years { display: flex; flex-wrap: wrap; gap: 6px; }
.hol-year {
  padding: 5px 12px; border-radius: 999px; font-size: 12.5px; font-weight: 700;
  background: var(--panel); border: 1.5px solid var(--line); color: var(--muted); text-decoration: none;
}
.hol-year.on { background: #EBF5FF; border-color: #B9DAFA; color: #2A6FB0; }
.hol-add, .hol-edit { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
.hol-add { margin: 6px 0 12px; }
.hol-name { min-width: 180px; flex: 1 1 180px; }
.hol-off { display: inline-flex; align-items: center; gap: 4px; font-size: 12.5px; font-weight: 700; white-space: nowrap; }
.hol-who { font-size: 12px; color: var(--muted); white-space: nowrap; }
.hol-btns { white-space: nowrap; text-align: right; }
.hol-btns .inline-form { display: inline-block; margin-left: 4px; }

/* ============================================================
   설정 → 접속 시간 제한 · 세션 (v2.40 · 2026.09.18)
   ★ 카드는 한 줄에 하나(넓게). 제한 스위치가 꺼지면 .acc-when 타일을 흐리게 -
     :has 로 그린다(값은 그대로 편집되고 실린다).
   ============================================================ */
.acc-intro { display: flex; flex-wrap: wrap; gap: 8px; margin: 4px 0 14px; }
.acc-pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 12px; border-radius: 999px;
  background: #F2EFFF; color: #5A47C9; font-size: 12.5px; font-weight: 700;
}
.acc-pill b { color: #3F2FA8; }
.acc-pill a { color: #5A47C9; }
.acc-pill-soft { background: #F4F2EE; color: var(--muted); font-weight: 600; }

.acc-list { display: flex; flex-direction: column; gap: 14px; }
.acc-card {
  --acc-a: #9B87F5; --acc-soft: #F2EFFF; --acc-line: #DDD6FB;
  border-radius: 20px; border: 1.5px solid var(--acc-line);
  background: linear-gradient(180deg, var(--acc-soft) 0, var(--panel) 64px);
  padding: 14px 18px 16px;
  transition: box-shadow .2s, border-color .2s;
}
.acc-SUPER_ADMIN { --acc-a: #FF9E7A; --acc-soft: #FFF1EB; --acc-line: #FFD9CB; }
.acc-ADMIN       { --acc-a: #9B87F5; --acc-soft: #F2EFFF; --acc-line: #DDD6FB; }
.acc-ISSUER      { --acc-a: #6BB5F5; --acc-soft: #EBF5FF; --acc-line: #CBE3FA; }
.acc-CUSTOMER    { --acc-a: #3CC9A0; --acc-soft: #E8FBF3; --acc-line: #BDEBDA; }
.acc-card:has(.acc-switch input:checked) { border-color: var(--acc-a); box-shadow: 0 6px 18px rgba(35, 24, 21, .06); }

.acc-head { display: flex; align-items: center; gap: 12px; }
.acc-emoji {
  width: 40px; height: 40px; flex: none; border-radius: 12px;
  display: grid; place-items: center; font-size: 20px;
  background: var(--panel); box-shadow: 0 2px 6px rgba(35, 24, 21, .08);
}
.acc-title { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; }
.acc-title b { font-size: 16px; }
.acc-now { font-size: 12px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.acc-switch { --fx-a: var(--acc-a); --fx-soft: var(--panel); --fx-line: var(--acc-line); --fx-ink: #3a3a3a; flex: none; }

.acc-warn {
  margin: 10px 0 0; padding: 8px 12px; border-radius: 12px;
  background: #FFF1EB; color: #C2552D; font-size: 12.5px; font-weight: 600;
}

.acc-tiles {
  display: grid; gap: 10px; margin-top: 12px;
  grid-template-columns: repeat(6, minmax(0, 1fr));
}
/* 한 줄 : 허용 시간(3) · 요일(3) / 두 줄 : 공휴일(2) · 한 번 최대(2) · 무활동(2) */
.acc-t-time, .acc-t-days { grid-column: span 3; }
.acc-t-hol, .acc-t-max, .acc-t-idle { grid-column: span 2; }
/* v2.40 — 접속 시간 제한은 타일 셋(2·2·2), 세션은 타일 둘(3·3)이 한 줄에 온다. */
.acc-tiles-3 > .acc-t-time, .acc-tiles-3 > .acc-t-days, .acc-tiles-3 > .acc-t-hol { grid-column: span 2; }
.acc-tiles-2 > .acc-t-max, .acc-tiles-2 > .acc-t-idle { grid-column: span 3; }
@media (max-width: 760px) {
  .acc-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .acc-t-time, .acc-t-days { grid-column: span 2; }
  .acc-t-hol { grid-column: span 2; }
  .acc-t-max, .acc-t-idle { grid-column: span 1; }
  .acc-tiles-3 > .acc-t-time, .acc-tiles-3 > .acc-t-days, .acc-tiles-3 > .acc-t-hol { grid-column: span 2; }
  .acc-tiles-2 > .acc-t-max, .acc-tiles-2 > .acc-t-idle { grid-column: span 2; }
}
/* 스위치가 없는 카드(세션) - 늘 걸리는 값이라 언제나 또렷하게. */
.acc-always { border-color: var(--acc-a); box-shadow: 0 6px 18px rgba(35, 24, 21, .06); }
.acc-tag {
  flex: none; padding: 5px 11px; border-radius: 999px;
  background: var(--acc-a); color: #fff; font-size: 12px; font-weight: 800;
}
.acc-hint { font-size: 11.5px; color: var(--muted); font-weight: 600; }
.acc-tile {
  display: flex; flex-direction: column; gap: 8px;
  padding: 10px 12px; border-radius: 14px;
  background: var(--panel); border: 1px solid var(--line);
  transition: opacity .2s;
}
.acc-card:not(:has(.acc-switch input:checked)) .acc-when { opacity: .45; }
.acc-cap { font-size: 11.5px; font-weight: 800; color: var(--muted); letter-spacing: .02em; }

.acc-range { display: inline-flex; align-items: center; gap: 6px; }
.acc-time, .acc-num {
  font: inherit; font-size: 14px; font-weight: 700;
  padding: 6px 8px; border-radius: 10px;
  border: 1.5px solid var(--line); background: #FBFAF8; color: inherit;
}
.acc-time { width: 132px; min-width: 0; }
.acc-time:focus, .acc-num:focus { outline: none; border-color: var(--acc-a); background: #fff; }
.acc-arrow { color: var(--acc-a); font-weight: 800; }

.acc-days { display: flex; flex-wrap: wrap; gap: 6px; }
.acc-day { position: relative; cursor: pointer; }
.acc-day input { position: absolute; opacity: 0; width: 1px; height: 1px; pointer-events: none; }
.acc-day span {
  display: grid; place-items: center; width: 34px; height: 34px; border-radius: 50%;
  background: #F4F2EE; color: #6B6B6B; font-size: 13px; font-weight: 800;
  transition: background .15s, color .15s, transform .12s;
}
.acc-day.we span { color: #D2553A; }
.acc-day input:checked + span { background: var(--acc-a); color: #fff; box-shadow: 0 3px 10px rgba(35, 24, 21, .12); }
.acc-day:active span { transform: scale(.92); }
.acc-day input:focus-visible + span { outline: 2px solid var(--acc-a); outline-offset: 2px; }

.acc-mini { align-self: flex-start; padding: 6px 12px 6px 7px; }

.acc-unit { display: inline-flex; align-items: center; gap: 6px; }
.acc-unit em { font-style: normal; font-size: 13px; color: var(--muted); font-weight: 700; }
.acc-num { width: 84px; text-align: right; }

.acc-save { margin-top: 16px; }

@media (prefers-reduced-motion: reduce) { .acc-day span, .acc-tile, .acc-card { transition: none; } }

/* ============================================================
   바뀐 것이 없어 막힌 저장 버튼 (v2.35 · 2026.09.17)
   ★ common.js wireDirty 가 붙인다. 모양만 흐리게 - 눌리지 않는 것은 disabled 가 한다.
   ============================================================ */
button.is-clean:disabled {
  opacity: .42; filter: grayscale(.35);
  cursor: not-allowed; box-shadow: none; transform: none;
}
button.is-clean:disabled:hover { transform: none; filter: grayscale(.35); }

/* ============================================================
   알약 버튼 (v2.36 · 2026.09.17)
   ★ 화면 곳곳의 작은 동작 버튼을 한 벌로. 모양만 정하고 폭은 내용만큼.
   ============================================================ */
.pbtn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 9px 18px; border-radius: 999px;
  font: inherit; font-size: 13px; font-weight: 800; line-height: 1.2; white-space: nowrap;
  border: 1.5px solid transparent; cursor: pointer; text-decoration: none;
  transition: transform .15s, box-shadow .2s, background .2s, filter .2s, opacity .2s;
}
.pbtn:hover:not(:disabled) { transform: translateY(-1px); }
.pbtn:active:not(:disabled) { transform: scale(.97); }
.pbtn:focus-visible { outline: 2px solid #9B87F5; outline-offset: 2px; }
.pbtn b { display: inline-grid; place-items: center; min-width: 20px; height: 20px; padding: 0 6px;
  border-radius: 999px; background: rgba(255, 255, 255, .6); font-size: 11.5px; }
.pbtn-sm { padding: 6px 12px; font-size: 12px; }

.pbtn-main {
  background: linear-gradient(135deg, var(--gold), var(--gold-deep)); color: var(--ink);
  box-shadow: 0 6px 16px rgba(217, 162, 47, .30);
}
.pbtn-main:hover:not(:disabled) { filter: brightness(1.05); box-shadow: 0 8px 20px rgba(217, 162, 47, .36); }
.pbtn-soft { background: #F2EFFF; color: #5A47C9; border-color: #DDD6FB; }
.pbtn-soft:hover:not(:disabled) { background: #E9E4FF; box-shadow: 0 4px 12px rgba(155, 135, 245, .20); }
.pbtn-ghost { background: var(--panel); color: #6B6B6B; border-color: var(--line); }
.pbtn-ghost:hover:not(:disabled) { background: #F5F3EE; color: var(--text); }
.pbtn-danger { background: #FFF0F0; color: #C23B3B; border-color: #F7CFCF; }
.pbtn-danger:hover:not(:disabled) { background: #FFE4E4; box-shadow: 0 4px 12px rgba(194, 59, 59, .16); }
.pbtn-danger b { background: #fff; color: #C23B3B; }

.pbtn:disabled { opacity: .42; filter: grayscale(.35); cursor: not-allowed; box-shadow: none; transform: none; }

/* 메일 양식 버튼 줄 */
.set-tpl-btns { gap: 10px; }
.set-tpl-act { gap: 10px; }

/* ============================================================
   파일 올리기 (v2.36)
   ============================================================ */
.up-box {
  display: flex; align-items: center; flex-wrap: wrap; gap: 10px;
  padding: 12px 14px; margin: 12px 0 16px;
  border: 1.5px dashed #CFC5FB; border-radius: 16px; background: #F9F8FF;
}
.up-input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.up-pick { cursor: pointer; }
.up-input:focus-visible + .up-pick { outline: 2px solid #9B87F5; outline-offset: 2px; }
.up-name {
  max-width: 320px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  padding: 6px 12px; border-radius: 999px; background: #F4F2EE; color: var(--muted);
  font-size: 12.5px; font-weight: 700;
}
.up-name.on { background: #E8FBF3; color: #17865F; }
.set-logo-btns .inline-form { display: inline-block; }

/* ============================================================
   공휴일 - 고른 날 지우기 (v2.36)
   ============================================================ */
.hol-selbar {
  display: flex; align-items: center; flex-wrap: wrap; gap: 10px;
  padding: 8px 12px; margin: 4px 0 8px;
  border-radius: 14px; background: #FBFAF8; border: 1px solid var(--line);
}
.hol-check { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; font-weight: 800; cursor: pointer; }
.hol-table .hol-cb { width: 34px; text-align: center; }
.hol-cb input, .hol-check input { width: 16px; height: 16px; accent-color: #C23B3B; cursor: pointer; }
.hol-table tr.hol-picked td { background: #FFF6F6; }

/* ============================================================
   스크롤바 (v2.37 · 2026.09.17)
   ★ 회색 기본 막대 대신 파스텔 그라데이션(금색 → 라일락 → 하늘 → 민트).
     평소에는 가늘고 흐리게, 그 칸에 마우스를 올리면 또렷하게, 손잡이를 잡으면 굵게.
   ★ 크롬 · 엣지는 ::-webkit-scrollbar 로 그린다. 크롬 121 부터는 scrollbar-color 를 주면
     아래 모양이 통째로 무시되므로, 파이어폭스(웹킷 선택자가 없는 곳)에만 scrollbar-color 를 준다.
   ★ 폭은 늘 10px 이고, "가늘어 보이는" 것은 투명 테두리(background-clip)다 - 폭을 바꾸면
     내용이 옆으로 밀린다.
   ============================================================ */
:root {
  --sb-grad: linear-gradient(180deg, #F0C155 0%, #C9B8FF 38%, #9FD0FA 70%, #8FE3C8 100%);
  --sb-grad-x: linear-gradient(90deg, #F0C155 0%, #C9B8FF 38%, #9FD0FA 70%, #8FE3C8 100%);
}
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track,
::-webkit-scrollbar-corner { background: transparent; }

/* 평소(그 칸에 마우스가 없을 때) - 가운데 4px, 옅은 라일락 */
::-webkit-scrollbar-thumb {
  border-radius: 999px;
  border: 3px solid transparent;
  background: rgba(155, 135, 245, .22) padding-box;
  min-height: 36px; min-width: 36px;
}
/* 마우스를 올린 칸 - 파스텔 그라데이션 · 6px
   ★ 켜고 끄는 것은 common.js 가 붙이는 .sb-on 이다. CSS :hover 로 두면 크롬이 스크롤바를
     바로 다시 그리지 않아 색이 늦게 따라온다(들어갈 때 · 나갈 때 모두).
   ★ 페이지 스크롤바는 body 의 모양을 쓴다(크로미움에서 확인) - html 에 붙인 클래스는 안 먹는다.
     페이지는 스크롤하는 동안 · 오른쪽 가장자리에 마우스가 있는 동안 켠다. */
.sb-on::-webkit-scrollbar-thumb {
  border-width: 2px;
  background: var(--sb-grad) padding-box;
}
.sb-on::-webkit-scrollbar-thumb:horizontal { background: var(--sb-grad-x) padding-box; }
/* 손잡이 위 · 잡고 끌 때 - 굵게 */
::-webkit-scrollbar-thumb:hover { border-width: 1px; }
::-webkit-scrollbar-thumb:active { border-width: 0; }
.sb-on::-webkit-scrollbar-track { background: rgba(155, 135, 245, .06); border-radius: 999px; }

@supports not selector(::-webkit-scrollbar) {
  html, * { scrollbar-width: thin; scrollbar-color: #C9B8FF transparent; }
}


/* ============================================================
   설정 → 접속 구분 (v2.41 · 2026.09.21)
   ★ 접속 시간 제한 카드와 같은 결 - 넓은 카드 하나에 스위치 · 주소 칸 · 타일.
   ============================================================ */

.geo-card {
  /* ★ 스위치 색은 --acc-a 를 탄다(.acc-switch 가 --fx-a: var(--acc-a)). 여기서 안 주면
       켜도 색이 안 나 꺼진 것처럼 보인다. */
  --acc-a: #6BB5F5; --acc-soft: #EBF5FF; --acc-line: #CBE3FA;
  border: 1.5px solid var(--acc-line); border-radius: 18px; padding: 16px 18px;
  background: var(--card);
  box-shadow: 0 6px 18px rgba(35, 24, 21, .06);
  transition: opacity .15s ease, border-color .15s ease;
}
/* 꺼지면 흐리게. 값은 그대로 고치고 저장된다(접속 제한 카드와 같은 규칙). */
.geo-card:has(.acc-switch input:checked) { border-color: var(--acc-a); }
.geo-card.is-off { border-color: var(--line); box-shadow: none; }
.geo-card.is-off .geo-sec, .geo-card.is-off .geo-tiles { opacity: .45; }

.geo-sec { margin-top: 14px; }
.geo-sec-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 8px; }
.geo-note { font-size: 12px; color: var(--muted); font-weight: 600; margin: 6px 0 0; }
.geo-sec-head .geo-note { margin: 0; }

.geo-srcs { display: flex; flex-direction: column; gap: 6px; }
.geo-src { display: flex; align-items: center; gap: 8px; }
.geo-no {
  flex: none; width: 22px; height: 22px; border-radius: 999px;
  display: grid; place-items: center;
  background: var(--panel); color: var(--muted); font-size: 11px; font-weight: 800;
}
.geo-src input {
  flex: 1 1 auto; min-width: 0;
  padding: 8px 11px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--card); font-size: 12.5px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
.geo-src input:focus { outline: none; border-color: var(--c4); }
.geo-del { flex: none; }

.geo-tiles { margin-top: 14px; }
.geo-t-dir { grid-column: span 6; }
.geo-t-num { grid-column: span 3; }
.geo-dir {
  width: 100%; padding: 8px 11px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--card); font-size: 13px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
.geo-dir:focus { outline: none; border-color: var(--c4); }

@media (max-width: 760px) {
  .geo-t-dir, .geo-t-num { grid-column: span 2; }
}

/* 파일 표 - 이름과 주소는 길어서 줄바꿈을 막고 줄인다. */
.geo-table .geo-fname, .geo-table .geo-furl {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11.5px;
  max-width: 320px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* ★ .tag 는 글자가 흰색이다 - 배경을 안 주면 아무것도 안 보인다. */
.geo-tag-ok   { background: linear-gradient(135deg, #2FBF71, #1E9E58); }
.geo-tag-due  { background: linear-gradient(135deg, var(--gold), var(--gold-deep)); color: var(--ink); }
.geo-tag-none { background: #BDB7B4; }


/* ============================================================
   설정 → 접속 구분 차단 (v2.42 · 2026.09.21)
   ============================================================ */

.zone-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; }
.zone-item {
  display: flex; flex-direction: column; gap: 4px; cursor: pointer;
  padding: 12px 14px; border: 1.5px solid var(--line); border-radius: 14px;
  background: var(--panel); transition: border-color .15s ease, background .15s ease;
}
.zone-item input { position: absolute; opacity: 0; width: 0; height: 0; }
.zone-name { font-size: 13.5px; font-weight: 800; }
.zone-state { font-size: 11.5px; color: var(--muted); font-weight: 700; }
/* 막는 구분은 붉은 결로. 한눈에 "여기는 못 들어온다" 가 보여야 한다. */
.zone-item:has(input:checked) { border-color: #F08A6C; background: #FFF1EB; }
.zone-item:has(input:checked) .zone-state { color: #C2552D; }
.zone-item:has(input:focus-visible) { outline: 2px solid var(--c4); outline-offset: 2px; }

@media (max-width: 760px) {
  .zone-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* 예외 표 */
.allow-cidr {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  min-width: 190px;
}
.allow-note { min-width: 160px; }
.allow-table td { vertical-align: middle; }
/* 꺼 둔 예외는 흐리게 - 지우지 않고 잠깐 끈 줄이다. */
.allow-table tr.is-off { opacity: .5; }


/* ============================================================
   앵커로 돌아온 칸 (v2.43 · 2026.09.21)
   ★ 저장하면 #geo · #zone · #allow · #hol 로 돌아온다. 그냥 두면 칸의 머리가
     화면 맨 위에 딱 붙어 상단바에 가려 보인다. 위쪽 여백을 둔다.
   ============================================================ */
.set-panel[id] { scroll-margin-top: 84px; }


/* ============================================================
   긴 안내 알약 · 허용 예외 표 (v2.44 · 2026.09.21)
   ============================================================ */

/* ★ .acc-pill 은 inline-flex 다. 글자 사이에 <b> 가 끼면 그 토막마다 flex 아이템이 되어
     gap(6px)이 들어가고, 줄바꿈도 토막 단위로 일어난다 - 「자기 주소 / 만」 처럼 끊긴다.
     여러 줄짜리 설명 알약은 그냥 inline-block 으로 두어 글자가 흐르게 한다. */
.acc-pill-soft {
  display: inline-block; align-items: initial; gap: 0;
  line-height: 1.6;
}

/* 허용 예외 표 - 칸 너비를 정하고 입력을 칸에 맞춘다. 종전에는 입력의 min-width 때문에
   표가 넘쳐 [저장] 이 오른쪽에서 잘렸다. */
/* ★ 뒤 네 칸은 고정 폭이다. 비율(%)로 두면 화면이 좁아질 때 같이 줄어 [삭제] 가 잘린다.
     앞 세 칸(대역 · 이름 · 메모)만 남는 자리를 나눠 갖는다. */
.allow-table { table-layout: fixed; width: 100%; }
.allow-table th:nth-child(1), .allow-table td:nth-child(1) { width: auto; }
.allow-table th:nth-child(2), .allow-table td:nth-child(2) { width: auto; }
.allow-table th:nth-child(3), .allow-table td:nth-child(3) { width: auto; }
.allow-table th:nth-child(4), .allow-table td:nth-child(4) { width: 56px; }
.allow-table th:nth-child(5), .allow-table td:nth-child(5) { width: 64px; }
.allow-table th:nth-child(6), .allow-table td:nth-child(6) { width: 76px; }
.allow-table th:nth-child(7), .allow-table td:nth-child(7) { width: 116px; }
.allow-table .pick { width: 100%; min-width: 0; box-sizing: border-box; }
.allow-table td { padding: 7px 6px; }
.allow-table .hol-btns { white-space: nowrap; padding-right: 0; }
/* 줄의 [저장] · [삭제] 는 붙여 둔다. 좁은 화면에서 한 줄에 들어가야 한다. */
.allow-table .hol-btns .pbtn { padding: 5px 9px; }
.allow-table .hol-btns .inline-form { margin-left: 2px; }
.allow-table .hol-who { font-size: 11px; }


/* ============================================================
   주소 알아보기 · 대역표 파일 요약 (v2.46 · 2026.09.21)
   ============================================================ */

.probe-card {
  margin-top: 12px; padding: 14px 16px; border-radius: 16px;
  border: 1.5px solid var(--line); background: var(--panel);
}
.probe-card.is-pass  { border-color: #9FDDBE; background: #F1FBF6; }
.probe-card.is-block { border-color: #F08A6C; background: #FFF1EB; }
.probe-card.is-bad   { border-color: var(--line); }
.probe-head { display: flex; align-items: center; gap: 10px; }
.probe-ip {
  font-size: 16px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
.probe-tag-block { background: linear-gradient(135deg, #F08A6C, #D9583A); }
.probe-rows {
  display: grid; grid-template-columns: 56px 1fr; gap: 6px 10px;
  margin-top: 10px; font-size: 13px;
}
.probe-k { color: var(--muted); font-weight: 700; font-size: 12px; }
.probe-warn { color: #C2552D; font-weight: 800; }
.probe-reason { margin: 10px 0 0; font-size: 13px; font-weight: 600; }

/* 대역표 파일 - 이름을 누르면 요약이 펼쳐진다. 칸을 줄이지 않도록 줄바꿈을 허용한다. */
.geo-table .geo-fname { white-space: normal; overflow: visible; max-width: none; }
.geo-sum summary {
  cursor: pointer; list-style: none; color: var(--ink);
  text-decoration: underline dotted; text-underline-offset: 3px;
}
.geo-sum summary::-webkit-details-marker { display: none; }
.geo-sum summary::before { content: '\25B8  '; color: var(--muted); }
.geo-sum[open] summary::before { content: '\25BE  '; }
.geo-sum-body {
  display: flex; flex-direction: column; gap: 3px;
  margin-top: 6px; padding: 8px 10px; border-radius: 10px;
  background: var(--panel); font-size: 12px; color: var(--ink);
  font-family: 'Pretendard', 'Noto Sans KR', system-ui, sans-serif;   /* 칸은 고정폭이지만 요약은 읽는 글 */
}
/* 요약이 펼쳐져 줄이 높아져도 크기 · 받은 시각은 한 줄로 */
.geo-table td:nth-child(2), .geo-table td:nth-child(3) { white-space: nowrap; }
.geo-sum-body .geo-top { color: var(--muted); }


/* 대역표 지문이 안 맞을 때 (v2.47) - 눈에 띄게, 버튼은 문장 뒤에 */
.geo-tamper { margin: 0 0 12px; line-height: 1.7; }
.geo-tamper .inline-form { display: inline-block; margin-left: 6px; vertical-align: middle; }


/* 대역표 DB 사본 (v2.48)
   ★ .tag 는 글자가 흰색이다 - 배경 짝을 반드시 준다(.geo-tag-none 과 같은 결). */
.geo-tag-restored { background: linear-gradient(135deg, var(--c4, #6C8AE4), #4A68C0); }
/* 사본 칸 - 크기와 시각 두 줄. 표가 좁아도 시각이 안 잘리게 줄바꿈을 허용한다. */
.geo-copy { display: inline-block; font-size: 12px; font-weight: 700; line-height: 1.5; white-space: nowrap; }
.geo-copy-at { font-weight: 600; font-size: 11px; color: var(--muted); }
/* 사본 칸은 고정 폭 - 비율만 주면 좁은 화면에서 주소 칸에 밀려 잘린다(v2.45 와 같은 함정). */
.geo-table th:nth-child(5), .geo-table td:nth-child(5) { width: 108px; }
